Skip to content

Commit c8eacc6

Browse files
authored
chore!: allow CreateUser to accept multiple organizations (coder#14383)
* chore: allow CreateUser to accept multiple organizations In a multi-org deployment, it makes more sense to allow for multiple org memberships to be assigned at create. The legacy param will still be honored. * Handle sdk deprecation better by maintaining cli functions
1 parent af125c3 commit c8eacc6

28 files changed

+597
-367
lines changed

cli/clitest/golden.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212
"testing"
1313

14+
"github.com/google/uuid"
1415
"github.com/stretchr/testify/require"
1516

1617
"github.com/coder/coder/v2/cli/config"
@@ -183,11 +184,11 @@ func prepareTestData(t *testing.T) (*codersdk.Client, map[string]string) {
183184
IncludeProvisionerDaemon: true,
184185
})
185186
firstUser := coderdtest.CreateFirstUser(t, rootClient)
186-
secondUser, err := rootClient.CreateUser(ctx, codersdk.CreateUserRequest{
187-
Email: "testuser2@coder.com",
188-
Username: "testuser2",
189-
Password: coderdtest.FirstUserParams.Password,
190-
OrganizationID: firstUser.OrganizationID,
187+
secondUser, err := rootClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
188+
Email: "testuser2@coder.com",
189+
Username: "testuser2",
190+
Password: coderdtest.FirstUserParams.Password,
191+
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
191192
})
192193
require.NoError(t, err)
193194
version := coderdtest.CreateTemplateVersion(t, rootClient, firstUser.OrganizationID, nil)

cli/schedule_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func setupTestSchedule(t *testing.T, sched *cron.Schedule) (ownerClient, memberC
3535

3636
ownerClient, db = coderdtest.NewWithDatabase(t, nil)
3737
owner := coderdtest.CreateFirstUser(t, ownerClient)
38-
memberClient, memberUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
38+
memberClient, memberUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
3939
r.Username = "testuser2" // ensure deterministic ordering
4040
})
4141
_ = dbfake.WorkspaceBuild(t, db, database.Workspace{

cli/server_createadminuser.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ func (r *RootCmd) newCreateAdminUserCommand() *serpent.Command {
8383

8484
validateInputs := func(username, email, password string) error {
8585
// Use the validator tags so we match the API's validation.
86-
req := codersdk.CreateUserRequest{
87-
Username: "username",
88-
Name: "Admin User",
89-
Email: "email@coder.com",
90-
Password: "ValidPa$$word123!",
91-
OrganizationID: uuid.New(),
86+
req := codersdk.CreateUserRequestWithOrgs{
87+
Username: "username",
88+
Name: "Admin User",
89+
Email: "email@coder.com",
90+
Password: "ValidPa$$word123!",
91+
OrganizationIDs: []uuid.UUID{uuid.New()},
9292
}
9393
if username != "" {
9494
req.Username = username

cli/user_delete_test.go

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"testing"
66

7+
"github.com/google/uuid"
78
"github.com/stretchr/testify/require"
89

910
"github.com/coder/coder/v2/cli/clitest"
@@ -26,13 +27,12 @@ func TestUserDelete(t *testing.T) {
2627
pw, err := cryptorand.String(16)
2728
require.NoError(t, err)
2829

29-
_, err = client.CreateUser(ctx, codersdk.CreateUserRequest{
30-
Email: "colin5@coder.com",
31-
Username: "coolin",
32-
Password: pw,
33-
UserLoginType: codersdk.LoginTypePassword,
34-
OrganizationID: owner.OrganizationID,
35-
DisableLogin: false,
30+
_, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
31+
Email: "colin5@coder.com",
32+
Username: "coolin",
33+
Password: pw,
34+
UserLoginType: codersdk.LoginTypePassword,
35+
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
3636
})
3737
require.NoError(t, err)
3838

@@ -57,13 +57,12 @@ func TestUserDelete(t *testing.T) {
5757
pw, err := cryptorand.String(16)
5858
require.NoError(t, err)
5959

60-
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
61-
Email: "colin5@coder.com",
62-
Username: "coolin",
63-
Password: pw,
64-
UserLoginType: codersdk.LoginTypePassword,
65-
OrganizationID: owner.OrganizationID,
66-
DisableLogin: false,
60+
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
61+
Email: "colin5@coder.com",
62+
Username: "coolin",
63+
Password: pw,
64+
UserLoginType: codersdk.LoginTypePassword,
65+
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
6766
})
6867
require.NoError(t, err)
6968

@@ -88,13 +87,12 @@ func TestUserDelete(t *testing.T) {
8887
pw, err := cryptorand.String(16)
8988
require.NoError(t, err)
9089

91-
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
92-
Email: "colin5@coder.com",
93-
Username: "coolin",
94-
Password: pw,
95-
UserLoginType: codersdk.LoginTypePassword,
96-
OrganizationID: owner.OrganizationID,
97-
DisableLogin: false,
90+
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
91+
Email: "colin5@coder.com",
92+
Username: "coolin",
93+
Password: pw,
94+
UserLoginType: codersdk.LoginTypePassword,
95+
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
9896
})
9997
require.NoError(t, err)
10098

@@ -121,13 +119,12 @@ func TestUserDelete(t *testing.T) {
121119
// pw, err := cryptorand.String(16)
122120
// require.NoError(t, err)
123121

124-
// toDelete, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
122+
// toDelete, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
125123
// Email: "colin5@coder.com",
126124
// Username: "coolin",
127125
// Password: pw,
128126
// UserLoginType: codersdk.LoginTypePassword,
129127
// OrganizationID: aUser.OrganizationID,
130-
// DisableLogin: false,
131128
// })
132129
// require.NoError(t, err)
133130

cli/usercreate.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
"github.com/go-playground/validator/v10"
8+
"github.com/google/uuid"
89
"golang.org/x/xerrors"
910

1011
"github.com/coder/pretty"
@@ -94,13 +95,13 @@ func (r *RootCmd) userCreate() *serpent.Command {
9495
}
9596
}
9697

97-
_, err = client.CreateUser(inv.Context(), codersdk.CreateUserRequest{
98-
Email: email,
99-
Username: username,
100-
Name: name,
101-
Password: password,
102-
OrganizationID: organization.ID,
103-
UserLoginType: userLoginType,
98+
_, err = client.CreateUserWithOrgs(inv.Context(), codersdk.CreateUserRequestWithOrgs{
99+
Email: email,
100+
Username: username,
101+
Name: name,
102+
Password: password,
103+
OrganizationIDs: []uuid.UUID{organization.ID},
104+
UserLoginType: userLoginType,
104105
})
105106
if err != nil {
106107
return err

coderd/apidoc/docs.go

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

coderd/apidoc/swagger.json

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

coderd/coderdtest/coderdtest.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -648,11 +648,11 @@ func CreateFirstUser(t testing.TB, client *codersdk.Client) codersdk.CreateFirst
648648
// CreateAnotherUser creates and authenticates a new user.
649649
// Roles can include org scoped roles with 'roleName:<organization_id>'
650650
func CreateAnotherUser(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles ...rbac.RoleIdentifier) (*codersdk.Client, codersdk.User) {
651-
return createAnotherUserRetry(t, client, organizationID, 5, roles)
651+
return createAnotherUserRetry(t, client, []uuid.UUID{organizationID}, 5, roles)
652652
}
653653

654-
func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
655-
return createAnotherUserRetry(t, client, organizationID, 5, roles, mutators...)
654+
func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequestWithOrgs)) (*codersdk.Client, codersdk.User) {
655+
return createAnotherUserRetry(t, client, []uuid.UUID{organizationID}, 5, roles, mutators...)
656656
}
657657

658658
// AuthzUserSubject does not include the user's groups.
@@ -678,31 +678,31 @@ func AuthzUserSubject(user codersdk.User, orgID uuid.UUID) rbac.Subject {
678678
}
679679
}
680680

681-
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, retries int, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
682-
req := codersdk.CreateUserRequest{
683-
Email: namesgenerator.GetRandomName(10) + "@coder.com",
684-
Username: RandomUsername(t),
685-
Name: RandomName(t),
686-
Password: "SomeSecurePassword!",
687-
OrganizationID: organizationID,
681+
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationIDs []uuid.UUID, retries int, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequestWithOrgs)) (*codersdk.Client, codersdk.User) {
682+
req := codersdk.CreateUserRequestWithOrgs{
683+
Email: namesgenerator.GetRandomName(10) + "@coder.com",
684+
Username: RandomUsername(t),
685+
Name: RandomName(t),
686+
Password: "SomeSecurePassword!",
687+
OrganizationIDs: organizationIDs,
688688
}
689689
for _, m := range mutators {
690690
m(&req)
691691
}
692692

693-
user, err := client.CreateUser(context.Background(), req)
693+
user, err := client.CreateUserWithOrgs(context.Background(), req)
694694
var apiError *codersdk.Error
695695
// If the user already exists by username or email conflict, try again up to "retries" times.
696696
if err != nil && retries >= 0 && xerrors.As(err, &apiError) {
697697
if apiError.StatusCode() == http.StatusConflict {
698698
retries--
699-
return createAnotherUserRetry(t, client, organizationID, retries, roles)
699+
return createAnotherUserRetry(t, client, organizationIDs, retries, roles)
700700
}
701701
}
702702
require.NoError(t, err)
703703

704704
var sessionToken string
705-
if req.DisableLogin || req.UserLoginType == codersdk.LoginTypeNone {
705+
if req.UserLoginType == codersdk.LoginTypeNone {
706706
// Cannot log in with a disabled login user. So make it an api key from
707707
// the client making this user.
708708
token, err := client.CreateToken(context.Background(), user.ID.String(), codersdk.CreateTokenRequest{
@@ -765,8 +765,9 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI
765765
require.NoError(t, err, "update site roles")
766766

767767
// isMember keeps track of which orgs the user was added to as a member
768-
isMember := map[uuid.UUID]bool{
769-
organizationID: true,
768+
isMember := make(map[uuid.UUID]bool)
769+
for _, orgID := range organizationIDs {
770+
isMember[orgID] = true
770771
}
771772

772773
// Update org roles

coderd/insights_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ func TestTemplateInsights_Golden(t *testing.T) {
523523

524524
// Prepare all test users.
525525
for _, user := range users {
526-
user.client, user.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
526+
user.client, user.sdk = coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
527527
r.Username = user.name
528528
})
529529
user.client.SetLogger(logger.Named("user").With(slog.Field{Name: "name", Value: user.name}))

coderd/userauth.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,11 +1436,11 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
14361436
}
14371437

14381438
//nolint:gocritic
1439-
user, _, err = api.CreateUser(dbauthz.AsSystemRestricted(ctx), tx, CreateUserRequest{
1440-
CreateUserRequest: codersdk.CreateUserRequest{
1441-
Email: params.Email,
1442-
Username: params.Username,
1443-
OrganizationID: defaultOrganization.ID,
1439+
user, err = api.CreateUser(dbauthz.AsSystemRestricted(ctx), tx, CreateUserRequest{
1440+
CreateUserRequestWithOrgs: codersdk.CreateUserRequestWithOrgs{
1441+
Email: params.Email,
1442+
Username: params.Username,
1443+
OrganizationIDs: []uuid.UUID{defaultOrganization.ID},
14441444
},
14451445
LoginType: params.LoginType,
14461446
})

coderd/userauth_test.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,28 +106,12 @@ func TestUserLogin(t *testing.T) {
106106
require.ErrorAs(t, err, &apiErr)
107107
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
108108
})
109-
// Password auth should fail if the user is made without password login.
110-
t.Run("DisableLoginDeprecatedField", func(t *testing.T) {
111-
t.Parallel()
112-
client := coderdtest.New(t, nil)
113-
user := coderdtest.CreateFirstUser(t, client)
114-
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
115-
r.Password = ""
116-
r.DisableLogin = true
117-
})
118-
119-
_, err := anotherClient.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
120-
Email: anotherUser.Email,
121-
Password: "SomeSecurePassword!",
122-
})
123-
require.Error(t, err)
124-
})
125109

126110
t.Run("LoginTypeNone", func(t *testing.T) {
127111
t.Parallel()
128112
client := coderdtest.New(t, nil)
129113
user := coderdtest.CreateFirstUser(t, client)
130-
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
114+
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
131115
r.Password = ""
132116
r.UserLoginType = codersdk.LoginTypeNone
133117
})
@@ -1470,11 +1454,11 @@ func TestUserLogout(t *testing.T) {
14701454
//nolint:gosec
14711455
password = "SomeSecurePassword123!"
14721456
)
1473-
newUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
1474-
Email: email,
1475-
Username: username,
1476-
Password: password,
1477-
OrganizationID: firstUser.OrganizationID,
1457+
newUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1458+
Email: email,
1459+
Username: username,
1460+
Password: password,
1461+
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
14781462
})
14791463
require.NoError(t, err)
14801464

0 commit comments

Comments
 (0)