Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0abd55e

Browse files
committed
fix(scim): ensure scim users aren't created with their own org
1 parent 0b15b1b commit 0abd55e

File tree

8 files changed

+91
-22
lines changed

8 files changed

+91
-22
lines changed

coderd/apidoc/docs.go

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/userauth.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,8 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
921921
Username: params.Username,
922922
OrganizationID: organizationID,
923923
},
924-
LoginType: params.LoginType,
924+
CreateOrganization: len(organizations) == 0,
925+
LoginType: params.LoginType,
925926
})
926927
if err != nil {
927928
return xerrors.Errorf("create user: %w", err)

coderd/users.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
128128
// Create an org for the first user.
129129
OrganizationID: uuid.Nil,
130130
},
131-
LoginType: database.LoginTypePassword,
131+
CreateOrganization: true,
132+
LoginType: database.LoginTypePassword,
132133
})
133134
if err != nil {
134135
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -313,19 +314,41 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
313314
return
314315
}
315316

316-
_, err = api.Database.GetOrganizationByID(ctx, req.OrganizationID)
317-
if httpapi.Is404Error(err) {
318-
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
319-
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID),
320-
})
321-
return
322-
}
323-
if err != nil {
324-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
325-
Message: "Internal error fetching organization.",
326-
Detail: err.Error(),
327-
})
328-
return
317+
if req.OrganizationID != uuid.Nil {
318+
// If an organization was provided, make sure it exists.
319+
_, err := api.Database.GetOrganizationByID(ctx, req.OrganizationID)
320+
if err != nil {
321+
if httpapi.Is404Error(err) {
322+
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
323+
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID),
324+
})
325+
return
326+
}
327+
328+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
329+
Message: "Internal error fetching organization.",
330+
Detail: err.Error(),
331+
})
332+
return
333+
}
334+
} else {
335+
// If no organization is provided, add the user to the first
336+
// organization.
337+
organizations, err := api.Database.GetOrganizations(ctx)
338+
if err != nil {
339+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
340+
Message: "Internal error fetching user.",
341+
Detail: err.Error(),
342+
})
343+
return
344+
}
345+
346+
if len(organizations) > 0 {
347+
// Add the user to the first organization. Once multi-organization
348+
// support is added, we should enable a configuration map of user
349+
// email to organization.
350+
req.OrganizationID = organizations[0].ID
351+
}
329352
}
330353

331354
err = userpassword.Validate(req.Password)
@@ -955,7 +978,8 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
955978

956979
type CreateUserRequest struct {
957980
codersdk.CreateUserRequest
958-
LoginType database.LoginType
981+
CreateOrganization bool
982+
LoginType database.LoginType
959983
}
960984

961985
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, uuid.UUID, error) {
@@ -964,6 +988,10 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
964988
orgRoles := make([]string, 0)
965989
// If no organization is provided, create a new one for the user.
966990
if req.OrganizationID == uuid.Nil {
991+
if !req.CreateOrganization {
992+
return xerrors.Errorf("organization ID must be provided")
993+
}
994+
967995
organization, err := tx.InsertOrganization(ctx, database.InsertOrganizationParams{
968996
ID: uuid.New(),
969997
Name: req.Username,

coderd/users_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,31 @@ func TestPostUsers(t *testing.T) {
478478
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
479479
})
480480

481+
t.Run("CreateWithoutOrg", func(t *testing.T) {
482+
t.Parallel()
483+
auditor := audit.NewMock()
484+
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
485+
numLogs := len(auditor.AuditLogs())
486+
487+
_ = coderdtest.CreateFirstUser(t, client)
488+
numLogs++ // add an audit log for user create
489+
numLogs++ // add an audit log for login
490+
491+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
492+
defer cancel()
493+
494+
_, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
495+
496+
Username: "someone-else",
497+
Password: "SomeSecurePassword!",
498+
})
499+
require.NoError(t, err)
500+
501+
require.Len(t, auditor.AuditLogs(), numLogs)
502+
require.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[numLogs-1].Action)
503+
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-2].Action)
504+
})
505+
481506
t.Run("Create", func(t *testing.T) {
482507
t.Parallel()
483508
auditor := audit.NewMock()

codersdk/users.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ type CreateUserRequest struct {
6969
Email string `json:"email" validate:"required,email" format:"email"`
7070
Username string `json:"username" validate:"required,username"`
7171
Password string `json:"password" validate:"required"`
72-
OrganizationID uuid.UUID `json:"organization_id" validate:"required" format:"uuid"`
72+
OrganizationID uuid.UUID `json:"organization_id" validate:"" format:"uuid"`
7373
}
7474

7575
type UpdateUserProfileRequest struct {

docs/api/schemas.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
15641564
| Name | Type | Required | Restrictions | Description |
15651565
| ----------------- | ------ | -------- | ------------ | ----------- |
15661566
| `email` | string | true | | |
1567-
| `organization_id` | string | true | | |
1567+
| `organization_id` | string | false | | |
15681568
| `password` | string | true | | |
15691569
| `username` | string | true | | |
15701570

enterprise/coderd/scim.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,27 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
156156
return
157157
}
158158

159+
var organizationID uuid.UUID
160+
//nolint:gocritic
161+
organizations, err := api.Database.GetOrganizations(dbauthz.AsSystemRestricted(ctx))
162+
if err != nil {
163+
_ = handlerutil.WriteError(rw, err)
164+
return
165+
}
166+
167+
if len(organizations) > 0 {
168+
// Add the user to the first organization. Once multi-organization
169+
// support is added, we should enable a configuration map of user
170+
// email to organization.
171+
organizationID = organizations[0].ID
172+
}
173+
159174
//nolint:gocritic // needed for SCIM
160175
user, _, err := api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
161176
CreateUserRequest: codersdk.CreateUserRequest{
162-
Username: sUser.UserName,
163-
Email: email,
177+
Username: sUser.UserName,
178+
Email: email,
179+
OrganizationID: organizationID,
164180
},
165181
LoginType: database.LoginTypeOIDC,
166182
})

0 commit comments

Comments
 (0)