Skip to content

Commit f30ec21

Browse files
committed
fix(scim): ensure usernames are properly validated
1 parent fa8153a commit f30ec21

File tree

4 files changed

+47
-18
lines changed

4 files changed

+47
-18
lines changed

coderd/userauth.go

-14
Original file line numberDiff line numberDiff line change
@@ -681,20 +681,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
681681
api.Logger.Debug(ctx, "'groups' claim was returned, but 'oidc-group-field' is not set, check your coder oidc settings.")
682682
}
683683

684-
// The username is a required property in Coder. We make a best-effort
685-
// attempt at using what the claims provide, but if that fails we will
686-
// generate a random username.
687-
usernameValid := httpapi.NameValid(username)
688-
if usernameValid != nil {
689-
// If no username is provided, we can default to use the email address.
690-
// This will be converted in the from function below, so it's safe
691-
// to keep the domain.
692-
if username == "" {
693-
username = email
694-
}
695-
username = httpapi.UsernameFrom(username)
696-
}
697-
698684
if len(api.OIDCConfig.EmailDomain) > 0 {
699685
ok = false
700686
for _, domain := range api.OIDCConfig.EmailDomain {

coderd/users.go

+14
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,20 @@ type CreateUserRequest struct {
983983
}
984984

985985
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, uuid.UUID, error) {
986+
// The username is a required property in Coder. We make a best-effort
987+
// attempt at using what the claims provide, but if that fails we will
988+
// generate a random username.
989+
usernameValid := httpapi.NameValid(req.Username)
990+
if usernameValid != nil {
991+
// If no username is provided, we can default to use the email address.
992+
// This will be converted in the from function below, so it's safe
993+
// to keep the domain.
994+
if req.Username == "" {
995+
req.Username = req.Email
996+
}
997+
req.Username = httpapi.UsernameFrom(req.Username)
998+
}
999+
9861000
var user database.User
9871001
return user, req.OrganizationID, store.InTx(func(tx database.Store) error {
9881002
orgRoles := make([]string, 0)

enterprise/coderd/scim.go

-4
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@ func (api *API) scimGetUsers(rw http.ResponseWriter, r *http.Request) {
7171
// This is done to always force Okta to try and create the user, this way we
7272
// don't need to implement fetching users twice.
7373
//
74-
// scimGetUsers intentionally always returns no users. This is done to always force
75-
// Okta to try and create each user individually, this way we don't need to
76-
// implement fetching users twice.
77-
//
7874
// @Summary SCIM 2.0: Get user by ID
7975
// @ID scim-get-user-by-id
8076
// @Security CoderSessionToken

enterprise/coderd/scim_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,39 @@ func TestScim(t *testing.T) {
128128
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
129129
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
130130
})
131+
132+
t.Run("DomainStrips", func(t *testing.T) {
133+
t.Parallel()
134+
135+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
136+
defer cancel()
137+
138+
scimAPIKey := []byte("hi")
139+
client := coderdenttest.New(t, &coderdenttest.Options{SCIMAPIKey: scimAPIKey})
140+
_ = coderdtest.CreateFirstUser(t, client)
141+
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
142+
AccountID: "coolin",
143+
Features: license.Features{
144+
codersdk.FeatureSCIM: 1,
145+
},
146+
})
147+
148+
sUser := makeScimUser(t)
149+
sUser.UserName = sUser.UserName + "@coder.com"
150+
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
151+
require.NoError(t, err)
152+
defer res.Body.Close()
153+
assert.Equal(t, http.StatusOK, res.StatusCode)
154+
155+
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})
156+
require.NoError(t, err)
157+
require.Len(t, userRes.Users, 1)
158+
159+
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
160+
// Username should be the same as the given name. They all use the
161+
// same string before we modified it above.
162+
assert.Equal(t, sUser.Name.GivenName, userRes.Users[0].Username)
163+
})
131164
})
132165

133166
t.Run("patchUser", func(t *testing.T) {

0 commit comments

Comments
 (0)