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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions cli/server_createadminuser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,19 @@ func TestServerCreateAdminUser(t *testing.T) {
org1Name, org1ID := "org1", uuid.New()
org2Name, org2ID := "org2", uuid.New()
_, err = db.InsertOrganization(ctx, database.InsertOrganizationParams{
ID: org1ID,
Name: org1Name,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
ID: org1ID,
Name: org1Name,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
DefaultOrgMemberRoles: rbac.DefaultOrgMemberRoles(),
})
require.NoError(t, err)
_, err = db.InsertOrganization(ctx, database.InsertOrganizationParams{
ID: org2ID,
Name: org2Name,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
ID: org2ID,
Name: org2Name,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
DefaultOrgMemberRoles: rbac.DefaultOrgMemberRoles(),
})
require.NoError(t, err)

Expand Down
2 changes: 1 addition & 1 deletion cli/testdata/coder_organizations_list_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ USAGE:
read.

OPTIONS:
-c, --column [id|name|display name|icon|description|created at|updated at|default] (default: name,display name,id,default)
-c, --column [id|name|display name|icon|description|created at|updated at|default|default org member roles] (default: name,display name,id,default)
Columns to display in table output.

-o, --output table|json (default: table)
Expand Down
2 changes: 1 addition & 1 deletion cli/testdata/coder_organizations_show_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ USAGE:
$ Show organization with the given ID.

OPTIONS:
-c, --column [id|name|display name|icon|description|created at|updated at|default] (default: id,name,default)
-c, --column [id|name|display name|icon|description|created at|updated at|default|default org member roles] (default: id,name,default)
Columns to display in table output.

--only-id bool
Expand Down
24 changes: 21 additions & 3 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 21 additions & 3 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions coderd/database/db2sdk/db2sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,10 +902,11 @@ func Organization(organization database.Organization) codersdk.Organization {
DisplayName: organization.DisplayName,
Icon: organization.Icon,
},
Description: organization.Description,
CreatedAt: organization.CreatedAt,
UpdatedAt: organization.UpdatedAt,
IsDefault: organization.IsDefault,
Description: organization.Description,
CreatedAt: organization.CreatedAt,
UpdatedAt: organization.UpdatedAt,
IsDefault: organization.IsDefault,
DefaultOrgMemberRoles: organization.DefaultOrgMemberRoles,
}
}

Expand Down
66 changes: 63 additions & 3 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,19 @@ func (q *querier) authorizeProvisionerJob(ctx context.Context, job database.Prov
return nil
}

// scopedOrgRoleIdentifiers wraps each role name as a RoleIdentifier scoped
// to orgID. Used to feed rbac.ChangeRoleSet from a stored []string.
func scopedOrgRoleIdentifiers(names []string, orgID uuid.UUID) []rbac.RoleIdentifier {
if len(names) == 0 {
return nil
}
out := make([]rbac.RoleIdentifier, len(names))
for i, name := range names {
out[i] = rbac.RoleIdentifier{Name: name, OrganizationID: orgID}
}
return out
}

func (q *querier) AcquireChats(ctx context.Context, arg database.AcquireChatsParams) ([]database.Chat, error) {
// AcquireChats is a system-level operation used by the chat processor.
// Authorization is done at the system level, not per-user.
Expand Down Expand Up @@ -5779,9 +5792,23 @@ func (q *querier) InsertOrganizationMember(ctx context.Context, arg database.Ins
return database.OrganizationMember{}, xerrors.Errorf("converting to organization roles: %w", err)
}

// The org's default_org_member_roles are implied at request time by
// GetAuthorizationUserRoles. Include them in canAssignRoles so the
// caller is required to be authorized to grant the full effective set
// (the explicit roles, organization-member, plus the defaults).
org, err := q.db.GetOrganizationByID(ctx, arg.OrganizationID)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("get organization: %w", err)
}
defaultRoles, err := q.convertToOrganizationRoles(arg.OrganizationID, org.DefaultOrgMemberRoles)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("convert default member roles: %w", err)
}

// All roles are added roles. Org member is always implied.
//nolint:gocritic
addedRoles := append(orgRoles, rbac.ScopedRoleOrgMember(arg.OrganizationID))
addedRoles = append(addedRoles, defaultRoles...)
err = q.canAssignRoles(ctx, arg.OrganizationID, addedRoles, []rbac.RoleIdentifier{})
if err != nil {
return database.OrganizationMember{}, err
Expand Down Expand Up @@ -7049,9 +7076,23 @@ func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemb
return database.OrganizationMember{}, err
}

// The org's default_org_member_roles are implied at request time by
// GetAuthorizationUserRoles. Include them in the implied set so
// canAssignRoles validates the caller can grant the full effective set
// (the granted roles, organization-member, plus the defaults).
org, err := q.db.GetOrganizationByID(ctx, arg.OrgID)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("get organization: %w", err)
}
defaultRoles, err := q.convertToOrganizationRoles(arg.OrgID, org.DefaultOrgMemberRoles)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("convert default member roles: %w", err)
}

// The org member role is always implied.
//nolint:gocritic
impliedTypes := append(scopedGranted, rbac.ScopedRoleOrgMember(arg.OrgID))
impliedTypes = append(impliedTypes, defaultRoles...)

added, removed := rbac.ChangeRoleSet(originalRoles, impliedTypes)
err = q.canAssignRoles(ctx, arg.OrgID, added, removed)
Expand Down Expand Up @@ -7092,10 +7133,29 @@ func (q *querier) UpdateOAuth2ProviderAppByID(ctx context.Context, arg database.
}

func (q *querier) UpdateOrganization(ctx context.Context, arg database.UpdateOrganizationParams) (database.Organization, error) {
fetch := func(ctx context.Context, arg database.UpdateOrganizationParams) (database.Organization, error) {
return q.db.GetOrganizationByID(ctx, arg.ID)
existing, err := q.db.GetOrganizationByID(ctx, arg.ID)
if err != nil {
return database.Organization{}, err
}
if err := q.authorizeContext(ctx, policy.ActionUpdate, existing); err != nil {
return database.Organization{}, err
}
// Treat a change to default_org_member_roles as assigning the added
// roles, and unassigning the removed roles, for every member of the
// org. Mirror the InsertOrganizationMember and UpdateMemberRoles
// guard so the caller cannot grant roles they could not grant
// individually, nor inject a malformed role name that would later
// break RoleNameFromString.
if !slices.Equal(existing.DefaultOrgMemberRoles, arg.DefaultOrgMemberRoles) {
added, removed := rbac.ChangeRoleSet(
scopedOrgRoleIdentifiers(existing.DefaultOrgMemberRoles, arg.ID),
scopedOrgRoleIdentifiers(arg.DefaultOrgMemberRoles, arg.ID),
)
if err := q.canAssignRoles(ctx, arg.ID, added, removed); err != nil {
return database.Organization{}, err
}
}
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateOrganization)(ctx, arg)
return q.db.UpdateOrganization(ctx, arg)
}

func (q *querier) UpdateOrganizationDeletedByID(ctx context.Context, arg database.UpdateOrganizationDeletedByIDParams) error {
Expand Down
17 changes: 12 additions & 5 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2266,9 +2266,10 @@ func (s *MethodTestSuite) TestOrganization() {
check.Args(arg).Asserts(org, policy.ActionUpdate).Returns(org)
}))
s.Run("InsertOrganizationMember", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{})
o := testutil.Fake(s.T(), faker, database.Organization{DefaultOrgMemberRoles: []string{}})
u := testutil.Fake(s.T(), faker, database.User{})
arg := database.InsertOrganizationMemberParams{OrganizationID: o.ID, UserID: u.ID, Roles: []string{codersdk.RoleOrganizationAdmin}}
dbm.EXPECT().GetOrganizationByID(gomock.Any(), o.ID).Return(o, nil).AnyTimes()
dbm.EXPECT().InsertOrganizationMember(gomock.Any(), arg).Return(database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID, Roles: arg.Roles}, nil).AnyTimes()
check.Args(arg).Asserts(
rbac.ResourceAssignOrgRole.InOrg(o.ID), policy.ActionAssign,
Expand Down Expand Up @@ -2305,12 +2306,17 @@ func (s *MethodTestSuite) TestOrganization() {
).WithNotAuthorized("no rows").WithCancelled(sql.ErrNoRows.Error())
}))
s.Run("UpdateOrganization", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{Name: "something-unique"})
arg := database.UpdateOrganizationParams{ID: o.ID, Name: "something-different"}
o := testutil.Fake(s.T(), faker, database.Organization{Name: "something-unique", DefaultOrgMemberRoles: []string{}})
// Change DefaultOrgMemberRoles so canAssignRoles fires alongside the
// ActionUpdate check; mirrors the InsertOrganizationMember pattern.
arg := database.UpdateOrganizationParams{ID: o.ID, Name: "something-different", DefaultOrgMemberRoles: []string{codersdk.RoleOrganizationAdmin}}

dbm.EXPECT().GetOrganizationByID(gomock.Any(), o.ID).Return(o, nil).AnyTimes()
dbm.EXPECT().UpdateOrganization(gomock.Any(), arg).Return(o, nil).AnyTimes()
check.Args(arg).Asserts(o, policy.ActionUpdate)
check.Args(arg).Asserts(
o, policy.ActionUpdate,
rbac.ResourceAssignOrgRole.InOrg(o.ID), policy.ActionAssign,
)
}))
s.Run("UpdateOrganizationDeletedByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{Name: "doomed"})
Expand Down Expand Up @@ -2347,13 +2353,14 @@ func (s *MethodTestSuite) TestOrganization() {
check.Args(arg).Asserts(rbac.ResourceOrganizationMember.InOrg(o.ID), policy.ActionRead).Returns(rows)
}))
s.Run("UpdateMemberRoles", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{})
o := testutil.Fake(s.T(), faker, database.Organization{DefaultOrgMemberRoles: []string{}})
u := testutil.Fake(s.T(), faker, database.User{})
mem := testutil.Fake(s.T(), faker, database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID, Roles: []string{codersdk.RoleOrganizationAdmin}})
out := mem
out.Roles = []string{}

dbm.EXPECT().OrganizationMembers(gomock.Any(), database.OrganizationMembersParams{OrganizationID: o.ID, UserID: u.ID, IncludeSystem: false}).Return([]database.OrganizationMembersRow{{OrganizationMember: mem}}, nil).AnyTimes()
dbm.EXPECT().GetOrganizationByID(gomock.Any(), o.ID).Return(o, nil).AnyTimes()
arg := database.UpdateMemberRolesParams{GrantedRoles: []string{}, UserID: u.ID, OrgID: o.ID}
dbm.EXPECT().UpdateMemberRoles(gomock.Any(), arg).Return(out, nil).AnyTimes()

Expand Down
15 changes: 8 additions & 7 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1034,13 +1034,14 @@ func GitSSHKey(t testing.TB, db database.Store, orig database.GitSSHKey) databas

func Organization(t testing.TB, db database.Store, orig database.Organization) database.Organization {
org, err := db.InsertOrganization(genCtx, database.InsertOrganizationParams{
ID: takeFirst(orig.ID, uuid.New()),
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
DisplayName: takeFirst(orig.Name, testutil.GetRandomName(t)),
Description: takeFirst(orig.Description, testutil.GetRandomName(t)),
Icon: takeFirst(orig.Icon, ""),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
ID: takeFirst(orig.ID, uuid.New()),
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
DisplayName: takeFirst(orig.Name, testutil.GetRandomName(t)),
Description: takeFirst(orig.Description, testutil.GetRandomName(t)),
Icon: takeFirst(orig.Icon, ""),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
DefaultOrgMemberRoles: takeFirstSlice(orig.DefaultOrgMemberRoles, rbac.DefaultOrgMemberRoles()),
})
require.NoError(t, err, "insert organization")

Expand Down
5 changes: 4 additions & 1 deletion coderd/database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE organizations DROP COLUMN IF EXISTS default_org_member_roles;
16 changes: 16 additions & 0 deletions coderd/database/migrations/000516_org_default_member_roles.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ALTER TABLE organizations
Comment thread
Emyrk marked this conversation as resolved.
ADD COLUMN default_org_member_roles text[];

UPDATE organizations
SET default_org_member_roles = ARRAY['organization-workspace-access']::text[];
Comment on lines +4 to +5

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backfill all existing organizations


ALTER TABLE organizations
ALTER COLUMN default_org_member_roles SET NOT NULL;

COMMENT ON COLUMN organizations.default_org_member_roles IS
'Roles granted to every member of this organization at request time. '
'The set is unioned into each member''s effective roles when '
'GetAuthorizationUserRoles runs, so changes propagate to all members '
'on the next request. Deployments can use this column to revoke '
'capabilities that would otherwise be considered normal organization '
'member permissions.';
Comment on lines +1 to +16

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing orgs are backfilled this way

Loading
Loading