Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit24c3255

Browse files
committed
chore: implement delete organization member
Side effects of removing an organization member will orphan theiruser resources. These side effects are not addressed here
1 parent8e06ad4 commit24c3255

File tree

15 files changed

+321
-0
lines changed

15 files changed

+321
-0
lines changed

‎coderd/apidoc/docs.go

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ func New(options *Options) *API {
860860
r.Use(
861861
httpmw.ExtractOrganizationMemberParam(options.Database),
862862
)
863+
r.Delete("/",api.deleteOrganizationMember)
863864
r.Put("/roles",api.putMemberRoles)
864865
r.Post("/workspaces",api.postWorkspacesByOrganization)
865866
})

‎coderd/database/dbauthz/dbauthz.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,16 @@ func (q *querier) DeleteOrganization(ctx context.Context, id uuid.UUID) error {
10351035
returndeleteQ(q.log,q.auth,q.db.GetOrganizationByID,q.db.DeleteOrganization)(ctx,id)
10361036
}
10371037

1038+
func (q*querier)DeleteOrganizationMember(ctx context.Context,arg database.DeleteOrganizationMemberParams)error {
1039+
returndeleteQ[database.OrganizationMember](q.log,q.auth,func(ctx context.Context,arg database.DeleteOrganizationMemberParams) (database.OrganizationMember,error) {
1040+
member,err:=database.ExpectOne(q.OrganizationMembers(ctx,database.OrganizationMembersParams(arg)))
1041+
iferr!=nil {
1042+
return database.OrganizationMember{},err
1043+
}
1044+
returnmember.OrganizationMember,nil
1045+
},q.db.DeleteOrganizationMember)(ctx,arg)
1046+
}
1047+
10381048
func (q*querier)DeleteReplicasUpdatedBefore(ctx context.Context,updatedAt time.Time)error {
10391049
iferr:=q.authorizeContext(ctx,policy.ActionDelete,rbac.ResourceSystem);err!=nil {
10401050
returnerr

‎coderd/database/dbauthz/dbauthz_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,23 @@ func (s *MethodTestSuite) TestOrganization() {
628628
rbac.ResourceAssignOrgRole.InOrg(o.ID),policy.ActionAssign,
629629
rbac.ResourceOrganizationMember.InOrg(o.ID).WithID(u.ID),policy.ActionCreate)
630630
}))
631+
s.Run("DeleteOrganizationMember",s.Subtest(func(db database.Store,check*expects) {
632+
o:=dbgen.Organization(s.T(),db, database.Organization{})
633+
u:=dbgen.User(s.T(),db, database.User{})
634+
member:=dbgen.OrganizationMember(s.T(),db, database.OrganizationMember{UserID:u.ID,OrganizationID:o.ID})
635+
636+
check.Args(database.DeleteOrganizationMemberParams{
637+
OrganizationID:o.ID,
638+
UserID:u.ID,
639+
}).Asserts(
640+
// Reads the org member before it tries to delete it
641+
member,policy.ActionRead,
642+
member,policy.ActionDelete).
643+
// SQL Filter returns a 404
644+
WithNotAuthorized("no rows").
645+
WithCancelled("no rows").
646+
Errors(sql.ErrNoRows)
647+
}))
631648
s.Run("UpdateOrganization",s.Subtest(func(db database.Store,check*expects) {
632649
o:=dbgen.Organization(s.T(),db, database.Organization{
633650
Name:"something-unique",

‎coderd/database/dbmem/dbmem.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,24 @@ func (q *FakeQuerier) DeleteOrganization(_ context.Context, id uuid.UUID) error
16321632
returnsql.ErrNoRows
16331633
}
16341634

1635+
func (q*FakeQuerier)DeleteOrganizationMember(_ context.Context,arg database.DeleteOrganizationMemberParams)error {
1636+
err:=validateDatabaseType(arg)
1637+
iferr!=nil {
1638+
returnerr
1639+
}
1640+
1641+
q.mutex.Lock()
1642+
deferq.mutex.Unlock()
1643+
1644+
deleted:=slices.DeleteFunc(q.data.organizationMembers,func(member database.OrganizationMember)bool {
1645+
returnmember.OrganizationID==arg.OrganizationID&&member.UserID==arg.UserID
1646+
})
1647+
iflen(deleted)==0 {
1648+
returnsql.ErrNoRows
1649+
}
1650+
returnnil
1651+
}
1652+
16351653
func (q*FakeQuerier)DeleteReplicasUpdatedBefore(_ context.Context,before time.Time)error {
16361654
q.mutex.Lock()
16371655
deferq.mutex.Unlock()

‎coderd/database/dbmetrics/dbmetrics.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/dbmock/dbmock.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries.sql.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries/organizationmembers.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ INSERT INTO
3636
VALUES
3737
($1, $2, $3, $4, $5) RETURNING*;
3838

39+
-- name: DeleteOrganizationMember :exec
40+
DELETE
41+
FROM
42+
organization_members
43+
WHERE
44+
organization_id= @organization_idAND
45+
user_id= @user_id
46+
;
47+
3948

4049
-- name: GetOrganizationIDsByMemberIDs :many
4150
SELECT

‎coderd/members.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request)
6262
httpapi.Write(ctx,rw,http.StatusOK,resp[0])
6363
}
6464

65+
// @Summary Remove organization member
66+
// @ID remove-organization-member
67+
// @Security CoderSessionToken
68+
// @Produce json
69+
// @Tags Members
70+
// @Param organization path string true "Organization ID"
71+
// @Param user path string true "User ID, name, or me"
72+
// @Success 200 {object} codersdk.OrganizationMember
73+
// @Router /organizations/{organization}/members/{user} [delete]
74+
func (api*API)deleteOrganizationMember(rw http.ResponseWriter,r*http.Request) {
75+
var (
76+
ctx=r.Context()
77+
organization=httpmw.OrganizationParam(r)
78+
member=httpmw.OrganizationMemberParam(r)
79+
)
80+
81+
err:=api.Database.DeleteOrganizationMember(ctx, database.DeleteOrganizationMemberParams{
82+
OrganizationID:organization.ID,
83+
UserID:member.UserID,
84+
})
85+
ifhttpapi.Is404Error(err) {
86+
httpapi.ResourceNotFound(rw)
87+
return
88+
}
89+
iferr!=nil {
90+
httpapi.InternalServerError(rw,err)
91+
return
92+
}
93+
94+
httpapi.Write(ctx,rw,http.StatusOK,"organization member removed")
95+
}
96+
6597
// @Summary List organization members
6698
// @ID list-organization-members
6799
// @Security CoderSessionToken

‎coderd/members_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderd_test
22

33
import (
4+
"net/http"
45
"testing"
56

67
"github.com/google/uuid"
@@ -114,6 +115,63 @@ func TestListMembers(t *testing.T) {
114115
})
115116
}
116117

118+
funcTestRemoveMember(t*testing.T) {
119+
t.Parallel()
120+
121+
t.Run("OK",func(t*testing.T) {
122+
t.Parallel()
123+
owner:=coderdtest.New(t,nil)
124+
first:=coderdtest.CreateFirstUser(t,owner)
125+
orgAdminClient,orgAdmin:=coderdtest.CreateAnotherUser(t,owner,first.OrganizationID,rbac.ScopedRoleOrgAdmin(first.OrganizationID))
126+
_,user:=coderdtest.CreateAnotherUser(t,owner,first.OrganizationID)
127+
128+
ctx:=testutil.Context(t,testutil.WaitMedium)
129+
// Verify the org of 3 members
130+
members,err:=orgAdminClient.OrganizationMembers(ctx,first.OrganizationID)
131+
require.NoError(t,err)
132+
require.Len(t,members,3)
133+
require.ElementsMatch(t,
134+
[]uuid.UUID{first.UserID,user.ID,orgAdmin.ID},
135+
db2sdk.List(members,onlyIDs))
136+
137+
// Delete a member
138+
err=orgAdminClient.DeleteOrganizationMember(ctx,first.OrganizationID,user.Username)
139+
require.NoError(t,err)
140+
141+
members,err=orgAdminClient.OrganizationMembers(ctx,first.OrganizationID)
142+
require.NoError(t,err)
143+
require.Len(t,members,2)
144+
require.ElementsMatch(t,
145+
[]uuid.UUID{first.UserID,orgAdmin.ID},
146+
db2sdk.List(members,onlyIDs))
147+
})
148+
149+
t.Run("MemberNotInOrg",func(t*testing.T) {
150+
t.Parallel()
151+
owner:=coderdtest.New(t,nil)
152+
first:=coderdtest.CreateFirstUser(t,owner)
153+
orgAdminClient,_:=coderdtest.CreateAnotherUser(t,owner,first.OrganizationID,rbac.ScopedRoleOrgAdmin(first.OrganizationID))
154+
155+
ctx:=testutil.Context(t,testutil.WaitMedium)
156+
// nolint:gocritic // requires owner to make a new org
157+
org,_:=owner.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
158+
Name:"other",
159+
DisplayName:"",
160+
Description:"",
161+
Icon:"",
162+
})
163+
164+
_,user:=coderdtest.CreateAnotherUser(t,owner,org.ID)
165+
166+
// Delete a user that is not in the organization
167+
err:=orgAdminClient.DeleteOrganizationMember(ctx,first.OrganizationID,user.Username)
168+
require.Error(t,err)
169+
varapiError*codersdk.Error
170+
require.ErrorAs(t,err,&apiError)
171+
require.Equal(t,http.StatusNotFound,apiError.StatusCode())
172+
})
173+
}
174+
117175
funconlyIDs(u codersdk.OrganizationMemberWithName) uuid.UUID {
118176
returnu.UserID
119177
}

‎codersdk/users.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,19 @@ func (c *Client) PostOrganizationMember(ctx context.Context, organizationID uuid
393393
returnmember,json.NewDecoder(res.Body).Decode(&member)
394394
}
395395

396+
// DeleteOrganizationMember removes a user from an organization
397+
func (c*Client)DeleteOrganizationMember(ctx context.Context,organizationID uuid.UUID,userstring)error {
398+
res,err:=c.Request(ctx,http.MethodDelete,fmt.Sprintf("/api/v2/organizations/%s/members/%s",organizationID,user),nil)
399+
iferr!=nil {
400+
returnerr
401+
}
402+
deferres.Body.Close()
403+
ifres.StatusCode!=http.StatusOK {
404+
returnReadBodyAsError(res)
405+
}
406+
returnnil
407+
}
408+
396409
// OrganizationMembers lists all members in an organization
397410
func (c*Client)OrganizationMembers(ctx context.Context,organizationID uuid.UUID) ([]OrganizationMemberWithName,error) {
398411
res,err:=c.Request(ctx,http.MethodGet,fmt.Sprintf("/api/v2/organizations/%s/members/",organizationID),nil)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp