Skip to content

Commit 863afa7

Browse files
authored
Extend the username uniqueness check to updates as well as creates (#1053)
* Add the check and tests * Update docs
1 parent ac16348 commit 863afa7

File tree

4 files changed

+73
-25
lines changed

4 files changed

+73
-25
lines changed

docs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ When a Token is updated, the following checks take place:
523523

524524
### Validation Checks
525525

526-
#### Create
526+
#### Create and Delete
527527

528528
Verifies there aren't any other users with the same username.
529529

pkg/resources/management.cattle.io/v3/users/User.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Validation Checks
22

3-
### Create
3+
### Create and Delete
44

55
Verifies there aren't any other users with the same username.
66

pkg/resources/management.cattle.io/v3/users/validator.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,8 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
8383
}
8484

8585
if request.Operation == admissionv1.Create && newUser.Username != "" {
86-
users, err := a.userCache.List(labels.Everything())
87-
if err != nil {
88-
return nil, fmt.Errorf("failed to get users %w", err)
89-
}
90-
for _, user := range users {
91-
if user.Username == newUser.Username {
92-
return admission.ResponseBadRequest("username already exists"), nil
93-
}
86+
if resp, err := a.checkUsernameUniqueness(newUser.Username); err != nil || resp != nil {
87+
return resp, err
9488
}
9589
return &admissionv1.AdmissionResponse{Allowed: true}, nil
9690
}
@@ -100,6 +94,12 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
10094
if err := validateUpdateFields(oldUser, newUser, fieldPath); err != nil {
10195
return admission.ResponseBadRequest(err.Error()), nil
10296
}
97+
if oldUser.Username == "" && newUser.Username != "" {
98+
if resp, err := a.checkUsernameUniqueness(newUser.Username); err != nil || resp != nil {
99+
return resp, err
100+
}
101+
}
102+
103103
oldUserEnabled := ptr.Deref(oldUser.Enabled, false)
104104
newUserEnabled := ptr.Deref(newUser.Enabled, false)
105105

@@ -156,6 +156,23 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
156156
return &admissionv1.AdmissionResponse{Allowed: true}, nil
157157
}
158158

159+
// checkUsernameUniqueness checks if a given username is already in use by another user.
160+
func (a *admitter) checkUsernameUniqueness(username string) (*admissionv1.AdmissionResponse, error) {
161+
if username == "" {
162+
return nil, nil
163+
}
164+
users, err := a.userCache.List(labels.Everything())
165+
if err != nil {
166+
return nil, fmt.Errorf("failed to get users: %w", err)
167+
}
168+
for _, user := range users {
169+
if user.Username == username {
170+
return admission.ResponseBadRequest("username already exists"), nil
171+
}
172+
}
173+
return nil, nil
174+
}
175+
159176
// getGroupsFromUserAttributes gets the list of group principals from a UserAttribute.
160177
//
161178
// Warning: UserAttributes are only updated when a user logs in, so this may not have the up to date Group Principals.

pkg/resources/management.cattle.io/v3/users/validator_test.go

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ const (
3030
managerUserName = "manage-user"
3131
defaultUserName = "test-user"
3232
sarErrorUser = "sar-error-user"
33-
ssrErrorUser = "ssr-error-user"
3433
requesterUserName = "requester-user"
3534
)
3635

@@ -222,20 +221,6 @@ func Test_Admit(t *testing.T) {
222221
requestUserName: requesterUserName,
223222
allowed: false,
224223
},
225-
{
226-
name: "adding a new username allowed",
227-
oldUser: &v3.User{
228-
ObjectMeta: metav1.ObjectMeta{
229-
Name: defaultUserName,
230-
},
231-
},
232-
newUser: defaultUser.DeepCopy(),
233-
requestUserName: requesterUserName,
234-
resolverRulesFor: func(string) ([]rbacv1.PolicyRule, error) {
235-
return getPods, nil
236-
},
237-
allowed: true,
238-
},
239224
{
240225
name: "removing username not allowed",
241226
oldUser: defaultUser.DeepCopy(),
@@ -308,6 +293,52 @@ func Test_Admit(t *testing.T) {
308293
allowed: false,
309294
wantErr: true,
310295
},
296+
{
297+
name: "username already exists on update",
298+
oldUser: &v3.User{
299+
ObjectMeta: metav1.ObjectMeta{
300+
Name: "another-user",
301+
},
302+
},
303+
newUser: &v3.User{
304+
ObjectMeta: metav1.ObjectMeta{
305+
Name: "another-user",
306+
},
307+
Username: defaultUserName,
308+
},
309+
requestUserName: managerUserName,
310+
mockUserCache: func() controllerv3.UserCache {
311+
mock := fake.NewMockNonNamespacedCacheInterface[*v3.User](ctrl)
312+
mock.EXPECT().List(labels.Everything()).Return([]*v3.User{
313+
&defaultUser,
314+
}, nil)
315+
return mock
316+
},
317+
allowed: false,
318+
},
319+
{
320+
name: "setting a unique username on update is allowed",
321+
oldUser: &v3.User{
322+
ObjectMeta: metav1.ObjectMeta{
323+
Name: "another-user",
324+
},
325+
},
326+
newUser: &v3.User{
327+
ObjectMeta: metav1.ObjectMeta{
328+
Name: "another-user",
329+
},
330+
Username: "a-different-username",
331+
},
332+
requestUserName: managerUserName,
333+
mockUserCache: func() controllerv3.UserCache {
334+
mock := fake.NewMockNonNamespacedCacheInterface[*v3.User](ctrl)
335+
mock.EXPECT().List(labels.Everything()).Return([]*v3.User{
336+
&defaultUser,
337+
}, nil)
338+
return mock
339+
},
340+
allowed: true,
341+
},
311342
}
312343
for _, tt := range tests {
313344
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)