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

Commitbf891a4

Browse files
committed
feat: remove site wide perms from creating a workspace
1 parent38124bd commitbf891a4

File tree

4 files changed

+145
-111
lines changed

4 files changed

+145
-111
lines changed

‎coderd/coderd.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,6 @@ func New(options *Options) *API {
11481148
})
11491149
r.Route("/{user}",func(r chi.Router) {
11501150
r.Group(func(r chi.Router) {
1151-
r.Use(httpmw.ExtractUserParam(options.Database))
11521151
// Creating workspaces does not require permissions on the user, only the
11531152
// organization member. This endpoint should match the authz story of
11541153
// postWorkspacesByOrganization

‎coderd/httpmw/organizationparam.go

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -110,51 +110,61 @@ type OrganizationMember struct {
110110
funcExtractOrganizationMemberParam(db database.Store)func(http.Handler) http.Handler {
111111
returnfunc(next http.Handler) http.Handler {
112112
returnhttp.HandlerFunc(func(rw http.ResponseWriter,r*http.Request) {
113-
ctx:=r.Context()
114-
// We need to resolve the `{user}` URL parameter so that we can get the userID and
115-
// username. We do this as SystemRestricted since the caller might have permission
116-
// to access the OrganizationMember object, but *not* the User object. So, it is
117-
// very important that we do not add the User object to the request context or otherwise
118-
// leak it to the API handler.
119-
// nolint:gocritic
120-
user,ok:=extractUserContext(dbauthz.AsSystemRestricted(ctx),db,rw,r)
121-
if!ok {
122-
return
123-
}
124113
organization:=OrganizationParam(r)
125-
126-
organizationMember,err:=database.ExpectOne(db.OrganizationMembers(ctx, database.OrganizationMembersParams{
127-
OrganizationID:organization.ID,
128-
UserID:user.ID,
129-
IncludeSystem:false,
130-
}))
131-
ifhttpapi.Is404Error(err) {
132-
httpapi.ResourceNotFound(rw)
133-
return
134-
}
135-
iferr!=nil {
136-
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
137-
Message:"Internal error fetching organization member.",
138-
Detail:err.Error(),
139-
})
114+
organizationMember,ok:=ExtractOrganizationMemberContext(rw,r,db,organization.ID)
115+
if!ok {
140116
return
141117
}
142118

143-
ctx=context.WithValue(ctx,organizationMemberParamContextKey{},OrganizationMember{
144-
OrganizationMember:organizationMember.OrganizationMember,
145-
// Here we're making two exceptions to the rule about not leaking data about the user
146-
// to the API handler, which is to include the username and avatar URL.
147-
// If the caller has permission to read the OrganizationMember, then we're explicitly
148-
// saying here that they also have permission to see the member's username and avatar.
149-
// This is OK!
150-
//
151-
// API handlers need this information for audit logging and returning the owner's
152-
// username in response to creating a workspace. Additionally, the frontend consumes
153-
// the Avatar URL and this allows the FE to avoid an extra request.
154-
Username:user.Username,
155-
AvatarURL:user.AvatarURL,
156-
})
119+
ctx:=r.Context()
120+
ctx=context.WithValue(ctx,organizationMemberParamContextKey{},organizationMember)
157121
next.ServeHTTP(rw,r.WithContext(ctx))
158122
})
159123
}
160124
}
125+
126+
funcExtractOrganizationMemberContext(rw http.ResponseWriter,r*http.Request,db database.Store,orgID uuid.UUID) (OrganizationMember,bool) {
127+
ctx:=r.Context()
128+
129+
// We need to resolve the `{user}` URL parameter so that we can get the userID and
130+
// username. We do this as SystemRestricted since the caller might have permission
131+
// to access the OrganizationMember object, but *not* the User object. So, it is
132+
// very important that we do not add the User object to the request context or otherwise
133+
// leak it to the API handler.
134+
// nolint:gocritic
135+
user,ok:=extractUserContext(dbauthz.AsSystemRestricted(ctx),db,rw,r)
136+
if!ok {
137+
returnOrganizationMember{},false
138+
}
139+
140+
organizationMember,err:=database.ExpectOne(db.OrganizationMembers(ctx, database.OrganizationMembersParams{
141+
OrganizationID:orgID,
142+
UserID:user.ID,
143+
IncludeSystem:false,
144+
}))
145+
ifhttpapi.Is404Error(err) {
146+
httpapi.ResourceNotFound(rw)
147+
returnOrganizationMember{},false
148+
}
149+
iferr!=nil {
150+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
151+
Message:"Internal error fetching organization member.",
152+
Detail:err.Error(),
153+
})
154+
returnOrganizationMember{},false
155+
}
156+
returnOrganizationMember{
157+
OrganizationMember:organizationMember.OrganizationMember,
158+
// Here we're making two exceptions to the rule about not leaking data about the user
159+
// to the API handler, which is to include the username and avatar URL.
160+
// If the caller has permission to read the OrganizationMember, then we're explicitly
161+
// saying here that they also have permission to see the member's username and avatar.
162+
// This is OK!
163+
//
164+
// API handlers need this information for audit logging and returning the owner's
165+
// username in response to creating a workspace. Additionally, the frontend consumes
166+
// the Avatar URL and this allows the FE to avoid an extra request.
167+
Username:user.Username,
168+
AvatarURL:user.AvatarURL,
169+
},true
170+
}

‎coderd/httpmw/userparam.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func extractUserContext(ctx context.Context, db database.Store, rw http.Response
5656
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
5757
Message:"\"user\" must be provided.",
5858
})
59-
return database.User{},true
59+
return database.User{},false
6060
}
6161

6262
ifuserQuery=="me" {

‎coderd/workspaces.go

Lines changed: 94 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -406,30 +406,46 @@ func (api *API) postUserWorkspaces(rw http.ResponseWriter, r *http.Request) {
406406
ctx=r.Context()
407407
apiKey=httpmw.APIKey(r)
408408
auditor=api.Auditor.Load()
409-
user=httpmw.UserParam(r)
410409
)
411410

411+
varreq codersdk.CreateWorkspaceRequest
412+
if!httpapi.Read(ctx,rw,r,&req) {
413+
return
414+
}
415+
416+
// No middleware exists to fetch the user from. This endpoint needs to fetch
417+
// the organization member, which requires the organization. Which can be
418+
// sourced from the template.
419+
//
420+
// TODO: This code gets called twice for each workspace build request.
421+
// This is inefficient and costs at most 2 extra RTTs to the DB.
422+
// This can be optimized. It exists as it is now for code simplicity.
423+
template,ok:=requestTemplate(ctx,rw,req,api.Database)
424+
if!ok {
425+
return
426+
}
427+
428+
member,ok:=httpmw.ExtractOrganizationMemberContext(rw,r,api.Database,template.OrganizationID)
429+
if!ok {
430+
return
431+
}
432+
412433
aReq,commitAudit:=audit.InitRequest[database.WorkspaceTable](rw,&audit.RequestParams{
413434
Audit:*auditor,
414435
Log:api.Logger,
415436
Request:r,
416437
Action:database.AuditActionCreate,
417438
AdditionalFields: audit.AdditionalFields{
418-
WorkspaceOwner:user.Username,
439+
WorkspaceOwner:member.Username,
419440
},
420441
})
421442

422443
defercommitAudit()
423444

424-
varreq codersdk.CreateWorkspaceRequest
425-
if!httpapi.Read(ctx,rw,r,&req) {
426-
return
427-
}
428-
429445
owner:=workspaceOwner{
430-
ID:user.ID,
431-
Username:user.Username,
432-
AvatarURL:user.AvatarURL,
446+
ID:member.UserID,
447+
Username:member.Username,
448+
AvatarURL:member.AvatarURL,
433449
}
434450
createWorkspace(ctx,aReq,apiKey.UserID,api,owner,req,rw,r)
435451
}
@@ -450,65 +466,8 @@ func createWorkspace(
450466
rw http.ResponseWriter,
451467
r*http.Request,
452468
) {
453-
// If we were given a `TemplateVersionID`, we need to determine the `TemplateID` from it.
454-
templateID:=req.TemplateID
455-
iftemplateID==uuid.Nil {
456-
templateVersion,err:=api.Database.GetTemplateVersionByID(ctx,req.TemplateVersionID)
457-
ifhttpapi.Is404Error(err) {
458-
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
459-
Message:fmt.Sprintf("Template version %q doesn't exist.",templateID.String()),
460-
Validations: []codersdk.ValidationError{{
461-
Field:"template_version_id",
462-
Detail:"template not found",
463-
}},
464-
})
465-
return
466-
}
467-
iferr!=nil {
468-
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
469-
Message:"Internal error fetching template version.",
470-
Detail:err.Error(),
471-
})
472-
return
473-
}
474-
iftemplateVersion.Archived {
475-
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
476-
Message:"Archived template versions cannot be used to make a workspace.",
477-
Validations: []codersdk.ValidationError{
478-
{
479-
Field:"template_version_id",
480-
Detail:"template version archived",
481-
},
482-
},
483-
})
484-
return
485-
}
486-
487-
templateID=templateVersion.TemplateID.UUID
488-
}
489-
490-
template,err:=api.Database.GetTemplateByID(ctx,templateID)
491-
ifhttpapi.Is404Error(err) {
492-
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
493-
Message:fmt.Sprintf("Template %q doesn't exist.",templateID.String()),
494-
Validations: []codersdk.ValidationError{{
495-
Field:"template_id",
496-
Detail:"template not found",
497-
}},
498-
})
499-
return
500-
}
501-
iferr!=nil {
502-
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
503-
Message:"Internal error fetching template.",
504-
Detail:err.Error(),
505-
})
506-
return
507-
}
508-
iftemplate.Deleted {
509-
httpapi.Write(ctx,rw,http.StatusNotFound, codersdk.Response{
510-
Message:fmt.Sprintf("Template %q has been deleted!",template.Name),
511-
})
469+
template,ok:=requestTemplate(ctx,rw,req,api.Database)
470+
if!ok {
512471
return
513472
}
514473

@@ -776,6 +735,72 @@ func createWorkspace(
776735
httpapi.Write(ctx,rw,http.StatusCreated,w)
777736
}
778737

738+
funcrequestTemplate(ctx context.Context,rw http.ResponseWriter,req codersdk.CreateWorkspaceRequest,db database.Store) (database.Template,bool) {
739+
// If we were given a `TemplateVersionID`, we need to determine the `TemplateID` from it.
740+
templateID:=req.TemplateID
741+
742+
iftemplateID==uuid.Nil {
743+
templateVersion,err:=db.GetTemplateVersionByID(ctx,req.TemplateVersionID)
744+
ifhttpapi.Is404Error(err) {
745+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
746+
Message:fmt.Sprintf("Template version %q doesn't exist.",req.TemplateVersionID),
747+
Validations: []codersdk.ValidationError{{
748+
Field:"template_version_id",
749+
Detail:"template not found",
750+
}},
751+
})
752+
return database.Template{},false
753+
}
754+
iferr!=nil {
755+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
756+
Message:"Internal error fetching template version.",
757+
Detail:err.Error(),
758+
})
759+
return database.Template{},false
760+
}
761+
iftemplateVersion.Archived {
762+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
763+
Message:"Archived template versions cannot be used to make a workspace.",
764+
Validations: []codersdk.ValidationError{
765+
{
766+
Field:"template_version_id",
767+
Detail:"template version archived",
768+
},
769+
},
770+
})
771+
return database.Template{},false
772+
}
773+
774+
templateID=templateVersion.TemplateID.UUID
775+
}
776+
777+
template,err:=db.GetTemplateByID(ctx,templateID)
778+
ifhttpapi.Is404Error(err) {
779+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
780+
Message:fmt.Sprintf("Template %q doesn't exist.",templateID),
781+
Validations: []codersdk.ValidationError{{
782+
Field:"template_id",
783+
Detail:"template not found",
784+
}},
785+
})
786+
return database.Template{},false
787+
}
788+
iferr!=nil {
789+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
790+
Message:"Internal error fetching template.",
791+
Detail:err.Error(),
792+
})
793+
return database.Template{},false
794+
}
795+
iftemplate.Deleted {
796+
httpapi.Write(ctx,rw,http.StatusNotFound, codersdk.Response{
797+
Message:fmt.Sprintf("Template %q has been deleted!",template.Name),
798+
})
799+
return database.Template{},false
800+
}
801+
returntemplate,true
802+
}
803+
779804
func (api*API)notifyWorkspaceCreated(
780805
ctx context.Context,
781806
receiverID uuid.UUID,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp