Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit fd1fe42

Browse files
[Backport 5.4.5099] scim: Fix random suffix added to every user on resync (#63131)
In the current implementation, we check if there&#39;s a user in the database already, and if so we add a random suffix to increase the chances of the insert/update to succeed. However, we didn&#39;t check if the user that already exists is _the same user_. So for a first sync, usernames would look nice and tidy, but then on a second sync from the SCIM provider, every user would get a suffix. This PR fixes that by adding a check for the user ID. Test plan: Added tests, verified SCIM sync doesn&#39;t modify usernames anymore locally. <br> Backport 614375e from #63122 Co-authored-by: Erik Seliger <[email protected]>
1 parent bcc4367 commit fd1fe42

File tree

6 files changed

+89
-7
lines changed

6 files changed

+89
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ All notable changes to Sourcegraph are documented in this file.
3434
- Pressing the numpad `Enter` key will now cycle through in-file search results [#62665](https://github.com/sourcegraph/sourcegraph/pull/62665)
3535
- Providing an access token via the [`SRC_ACCESS_TOKEN`](https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token) environment variable is now mandatory for uploading SCIP indexes using [src-cli](https://sourcegraph.com/docs/cli). [#62573](https://github.com/sourcegraph/sourcegraph/pull/62573)
3636
- Fixed several conditions that could cause a repository being incorrectly marked as modified during code host syncing. For these cases, unnecessary git fetches were triggered. [#62837](https://github.com/sourcegraph/sourcegraph/pull/62837)
37+
- Fixed usernames getting random suffixes added during SCIM provisioning. [#63122](https://github.com/sourcegraph/sourcegraph/pull/63122)
3738

3839
## 5.4.2198
3940

cmd/frontend/internal/auth/scim/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ go_test(
5454
"user_get_test.go",
5555
"user_patch_test.go",
5656
"user_replace_test.go",
57+
"user_test.go",
5758
"util_test.go",
5859
],
5960
embed = [":scim"],

cmd/frontend/internal/auth/scim/user.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func diffEmails(beforeUpdateUserData, afterUpdateUserData scim.ResourceAttribute
228228
// and adding a random suffix to make it unique in case there one without a suffix already exists in the DB.
229229
// This is meant to be done inside a transaction so that the user creation/update is guaranteed to be
230230
// coherent with the evaluation of this function.
231-
func getUniqueUsername(ctx context.Context, tx database.UserStore, requestedUsername string) (string, error) {
231+
func getUniqueUsername(ctx context.Context, tx database.UserStore, existingUserID int32, requestedUsername string) (string, error) {
232232
// Process requested username
233233
normalizedUsername, err := auth.NormalizeUsername(requestedUsername)
234234
if err != nil {
@@ -238,15 +238,23 @@ func getUniqueUsername(ctx context.Context, tx database.UserStore, requestedUser
238238
return "", scimerrors.ScimErrorBadParams([]string{"invalid username"})
239239
}
240240
}
241-
_, err = tx.GetByUsername(ctx, normalizedUsername)
242-
if err == nil { // Username exists, try to add random suffix
241+
u, err := tx.GetByUsername(ctx, normalizedUsername)
242+
if err != nil {
243+
if !database.IsUserNotFoundErr(err) {
244+
return "", scimerrors.ScimError{Status: http.StatusInternalServerError, Detail: errors.Wrap(err, "could not check if username exists").Error()}
245+
}
246+
// User doesn't exist, we're good to go
247+
return normalizedUsername, nil
248+
}
249+
250+
// Username exists and is not the same user, try to add random suffix to make it unique
251+
if u.ID != existingUserID {
243252
normalizedUsername, err = userpasswd.AddRandomSuffix(normalizedUsername)
244253
if err != nil {
245254
return "", scimerrors.ScimError{Status: http.StatusInternalServerError, Detail: errors.Wrap(err, "could not normalize username").Error()}
246255
}
247-
} else if !database.IsUserNotFoundErr(err) {
248-
return "", scimerrors.ScimError{Status: http.StatusInternalServerError, Detail: errors.Wrap(err, "could not check if username exists").Error()}
249256
}
257+
250258
return normalizedUsername, nil
251259
}
252260

cmd/frontend/internal/auth/scim/user_service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func (u *UserSCIMService) Create(ctx context.Context, attributes scim.ResourceAt
185185
// Make sure the username is unique, then create user with/without an external account ID
186186
var user *types.User
187187
err = u.db.WithTransact(ctx, func(tx database.DB) error {
188-
uniqueUsername, err := getUniqueUsername(ctx, tx.Users(), extractStringAttribute(attributes, AttrUserName))
188+
uniqueUsername, err := getUniqueUsername(ctx, tx.Users(), 0, extractStringAttribute(attributes, AttrUserName))
189189
if err != nil {
190190
return err
191191
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package scim
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/sourcegraph/sourcegraph/internal/database"
8+
"github.com/sourcegraph/sourcegraph/internal/database/dbmocks"
9+
"github.com/sourcegraph/sourcegraph/internal/types"
10+
)
11+
12+
func TestGetUniqueUsername(t *testing.T) {
13+
t.Parallel()
14+
15+
tx := dbmocks.NewMockUserStore()
16+
tx.GetByUsernameFunc.SetDefaultReturn(nil, database.NewUserNotFoundError(1))
17+
ctx := context.Background()
18+
19+
t.Run("valid username", func(t *testing.T) {
20+
username, err := getUniqueUsername(ctx, tx, 0, "validusername")
21+
if err != nil {
22+
t.Fatalf("unexpected error: %v", err)
23+
}
24+
25+
if username != "validusername" {
26+
t.Errorf("expected username 'validusername', got '%s'", username)
27+
}
28+
})
29+
30+
t.Run("invalid username", func(t *testing.T) {
31+
username, err := getUniqueUsername(ctx, tx, 0, "invalid username")
32+
if err != nil {
33+
t.Fatalf("unexpected error: %v", err)
34+
}
35+
36+
if len(username) == 0 {
37+
t.Error("expected non-empty username")
38+
}
39+
})
40+
41+
t.Run("existing username", func(t *testing.T) {
42+
tx := dbmocks.NewMockUserStore()
43+
tx.GetByUsernameFunc.SetDefaultReturn(&types.User{
44+
ID: 1,
45+
}, nil)
46+
47+
username, err := getUniqueUsername(ctx, tx, 1, "existinguser")
48+
if err != nil {
49+
t.Fatalf("unexpected error: %v", err)
50+
}
51+
52+
if username != "existinguser" {
53+
t.Errorf("expected unaltered username, got '%s'", username)
54+
}
55+
})
56+
57+
t.Run("existing username for different user", func(t *testing.T) {
58+
tx := dbmocks.NewMockUserStore()
59+
tx.GetByUsernameFunc.SetDefaultReturn(&types.User{
60+
ID: 1,
61+
}, nil)
62+
63+
username, err := getUniqueUsername(ctx, tx, 2, "existinguser")
64+
if err != nil {
65+
t.Fatalf("unexpected error: %v", err)
66+
}
67+
68+
if username == "existinguser" {
69+
t.Errorf("expected unique username, got '%s'", username)
70+
}
71+
})
72+
}

cmd/frontend/internal/auth/scim/user_update_action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (u *updateUserProfileData) Update(ctx context.Context, before, after *scim.
6666
return nil
6767
}
6868

69-
usernameUpdate, err := getUniqueUsername(ctx, u.tx.Users(), extractStringAttribute(after.Attributes, AttrUserName))
69+
usernameUpdate, err := getUniqueUsername(ctx, u.tx.Users(), u.userID, requestedUsername)
7070
if err != nil {
7171
return scimerrors.ScimError{Status: http.StatusBadRequest, Detail: errors.Wrap(err, "invalid username").Error()}
7272
}

0 commit comments

Comments
 (0)