mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
fix(enterprise): ensure scim usernames are validated (#7925)
This commit is contained in:
@ -983,6 +983,12 @@ type CreateUserRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, uuid.UUID, error) {
|
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, uuid.UUID, error) {
|
||||||
|
// Ensure the username is valid. It's the caller's responsibility to ensure
|
||||||
|
// the username is valid and unique.
|
||||||
|
if usernameValid := httpapi.NameValid(req.Username); usernameValid != nil {
|
||||||
|
return database.User{}, uuid.Nil, xerrors.Errorf("invalid username %q: %w", req.Username, usernameValid)
|
||||||
|
}
|
||||||
|
|
||||||
var user database.User
|
var user database.User
|
||||||
return user, req.OrganizationID, store.InTx(func(tx database.Store) error {
|
return user, req.OrganizationID, store.InTx(func(tx database.Store) error {
|
||||||
orgRoles := make([]string, 0)
|
orgRoles := make([]string, 0)
|
||||||
|
@ -71,10 +71,6 @@ func (api *API) scimGetUsers(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// This is done to always force Okta to try and create the user, this way we
|
// This is done to always force Okta to try and create the user, this way we
|
||||||
// don't need to implement fetching users twice.
|
// don't need to implement fetching users twice.
|
||||||
//
|
//
|
||||||
// scimGetUsers intentionally always returns no users. This is done to always force
|
|
||||||
// Okta to try and create each user individually, this way we don't need to
|
|
||||||
// implement fetching users twice.
|
|
||||||
//
|
|
||||||
// @Summary SCIM 2.0: Get user by ID
|
// @Summary SCIM 2.0: Get user by ID
|
||||||
// @ID scim-get-user-by-id
|
// @ID scim-get-user-by-id
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
@ -156,6 +152,20 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The username is a required property in Coder. We make a best-effort
|
||||||
|
// attempt at using what the claims provide, but if that fails we will
|
||||||
|
// generate a random username.
|
||||||
|
usernameValid := httpapi.NameValid(sUser.UserName)
|
||||||
|
if usernameValid != nil {
|
||||||
|
// If no username is provided, we can default to use the email address.
|
||||||
|
// This will be converted in the from function below, so it's safe
|
||||||
|
// to keep the domain.
|
||||||
|
if sUser.UserName == "" {
|
||||||
|
sUser.UserName = email
|
||||||
|
}
|
||||||
|
sUser.UserName = httpapi.UsernameFrom(sUser.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
var organizationID uuid.UUID
|
var organizationID uuid.UUID
|
||||||
//nolint:gocritic
|
//nolint:gocritic
|
||||||
organizations, err := api.Database.GetOrganizations(dbauthz.AsSystemRestricted(ctx))
|
organizations, err := api.Database.GetOrganizations(dbauthz.AsSystemRestricted(ctx))
|
||||||
|
@ -128,6 +128,39 @@ func TestScim(t *testing.T) {
|
|||||||
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
|
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
|
||||||
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
|
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("DomainStrips", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
scimAPIKey := []byte("hi")
|
||||||
|
client := coderdenttest.New(t, &coderdenttest.Options{SCIMAPIKey: scimAPIKey})
|
||||||
|
_ = coderdtest.CreateFirstUser(t, client)
|
||||||
|
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||||
|
AccountID: "coolin",
|
||||||
|
Features: license.Features{
|
||||||
|
codersdk.FeatureSCIM: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
sUser := makeScimUser(t)
|
||||||
|
sUser.UserName = sUser.UserName + "@coder.com"
|
||||||
|
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer res.Body.Close()
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, userRes.Users, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
|
||||||
|
// Username should be the same as the given name. They all use the
|
||||||
|
// same string before we modified it above.
|
||||||
|
assert.Equal(t, sUser.Name.GivenName, userRes.Users[0].Username)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("patchUser", func(t *testing.T) {
|
t.Run("patchUser", func(t *testing.T) {
|
||||||
|
Reference in New Issue
Block a user