@@ -13,6 +13,8 @@ import (
13
13
"time"
14
14
15
15
"github.com/cockroachdb/cockroach/pkg/base"
16
+ "github.com/cockroachdb/cockroach/pkg/security/username"
17
+ "github.com/cockroachdb/cockroach/pkg/sql"
16
18
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descs"
17
19
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
18
20
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
@@ -163,3 +165,127 @@ COMMIT`,
163
165
})
164
166
}
165
167
}
168
+
169
+ func TestEnsureUserOnlyBelongsToRoles (t * testing.T ) {
170
+ defer leaktest .AfterTest (t )()
171
+ defer log .Scope (t ).Close (t )
172
+
173
+ ctx := context .Background ()
174
+ s , db , _ := serverutils .StartServer (t , base.TestServerArgs {})
175
+ defer s .Stopper ().Stop (ctx )
176
+
177
+ execCfg := s .ExecutorConfig ().(sql.ExecutorConfig )
178
+ tdb := sqlutils .MakeSQLRunner (db )
179
+
180
+ // Helper function to create a username.SQLUsername from a string.
181
+ makeRole := func (name string ) username.SQLUsername {
182
+ r , err := username .MakeSQLUsernameFromUserInput (name , username .PurposeValidation )
183
+ require .NoError (t , err )
184
+ return r
185
+ }
186
+
187
+ // Helper function to get the current roles of a user and return them as a sorted string slice.
188
+ getRoles := func (user username.SQLUsername ) []string {
189
+ rows := tdb .QueryStr (t , `SELECT role FROM system.role_members WHERE member = $1 ORDER BY role` , user .Normalized ())
190
+ actualRoles := make ([]string , len (rows ))
191
+ for i , r := range rows {
192
+ actualRoles [i ] = r [0 ]
193
+ }
194
+ return actualRoles
195
+ }
196
+
197
+ // Setup initial users and roles for the tests.
198
+ tdb .Exec (t , `
199
+ CREATE USER test_user;
200
+ CREATE ROLE role1;
201
+ CREATE ROLE role2;
202
+ CREATE ROLE role3;
203
+ CREATE ROLE role4;
204
+ GRANT role1, role2 TO test_user;
205
+ ` )
206
+
207
+ testUser := makeRole ("test_user" )
208
+ role1 := makeRole ("role1" )
209
+ role2 := makeRole ("role2" )
210
+ role3 := makeRole ("role3" )
211
+ role4 := makeRole ("role4" )
212
+ nonExistentRole := makeRole ("non_existent_role" )
213
+
214
+ t .Run ("no role is granted or revoked" , func (t * testing.T ) {
215
+ // The user currently has role1 and role2. Syncing with the same set should do nothing.
216
+ desiredRoles := []username.SQLUsername {role1 , role2 }
217
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
218
+ require .NoError (t , err )
219
+ require .Equal (t , []string {"role1" , "role2" }, getRoles (testUser ))
220
+ })
221
+
222
+ t .Run ("single role is granted" , func (t * testing.T ) {
223
+ // Grant role3. User should now have role1, role2, role3.
224
+ desiredRoles := []username.SQLUsername {role1 , role2 , role3 }
225
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
226
+ require .NoError (t , err )
227
+ require .Equal (t , []string {"role1" , "role2" , "role3" }, getRoles (testUser ))
228
+ })
229
+
230
+ t .Run ("single role is revoked" , func (t * testing.T ) {
231
+ // Revoke role1. User should now have role2, role3.
232
+ desiredRoles := []username.SQLUsername {role2 , role3 }
233
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
234
+ require .NoError (t , err )
235
+ require .Equal (t , []string {"role2" , "role3" }, getRoles (testUser ))
236
+ })
237
+
238
+ t .Run ("multiple roles are granted" , func (t * testing.T ) {
239
+ // Grant role1 and role4. User should now have role1, role2, role3, role4.
240
+ desiredRoles := []username.SQLUsername {role1 , role2 , role3 , role4 }
241
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
242
+ require .NoError (t , err )
243
+ require .Equal (t , []string {"role1" , "role2" , "role3" , "role4" }, getRoles (testUser ))
244
+ })
245
+
246
+ t .Run ("multiple roles are revoked" , func (t * testing.T ) {
247
+ // Revoke role3 and role4. User should now have role1, role2.
248
+ desiredRoles := []username.SQLUsername {role1 , role2 }
249
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
250
+ require .NoError (t , err )
251
+ require .Equal (t , []string {"role1" , "role2" }, getRoles (testUser ))
252
+ })
253
+
254
+ t .Run ("multiple roles are granted and revoked" , func (t * testing.T ) {
255
+ // Revoke role1, grant role3. User should now have role2, role3.
256
+ desiredRoles := []username.SQLUsername {role2 , role3 }
257
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
258
+ require .NoError (t , err )
259
+ require .Equal (t , []string {"role2" , "role3" }, getRoles (testUser ))
260
+ })
261
+
262
+ t .Run ("grant includes non-existent roles" , func (t * testing.T ) {
263
+ // Attempt to grant role4 and a non-existent role.
264
+ // User should end up with role2, role3, role4. The non-existent role should be ignored.
265
+ desiredRoles := []username.SQLUsername {role2 , role3 , role4 , nonExistentRole }
266
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
267
+ require .NoError (t , err )
268
+ require .Equal (t , []string {"role2" , "role3" , "role4" }, getRoles (testUser ))
269
+ })
270
+
271
+ t .Run ("all desired roles are non-existent" , func (t * testing.T ) {
272
+ // Attempt to sync with only a non-existent role.
273
+ // All existing roles (role2, role3, role4) should be revoked, and nothing new granted.
274
+ desiredRoles := []username.SQLUsername {nonExistentRole }
275
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
276
+ require .NoError (t , err )
277
+ require .Empty (t , getRoles (testUser ))
278
+ })
279
+
280
+ t .Run ("empty desired roles list revokes all" , func (t * testing.T ) {
281
+ // First, grant some roles back to the user.
282
+ tdb .Exec (t , "GRANT role1, role2 TO test_user" )
283
+ require .Equal (t , []string {"role1" , "role2" }, getRoles (testUser ))
284
+
285
+ // Now, sync with an empty list. All roles should be revoked.
286
+ desiredRoles := []username.SQLUsername {}
287
+ err := sql .EnsureUserOnlyBelongsToRoles (ctx , & execCfg , testUser , desiredRoles )
288
+ require .NoError (t , err )
289
+ require .Empty (t , getRoles (testUser ))
290
+ })
291
+ }
0 commit comments