Skip to content

Commit a4b2e8d

Browse files
committed
chore: reorder prebuilt workspace authorization logic
1 parent ba08d38 commit a4b2e8d

File tree

4 files changed

+47
-28
lines changed

4 files changed

+47
-28
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,26 +151,28 @@ func (q *querier) authorizeContext(ctx context.Context, action policy.Action, ob
151151

152152
// authorizePrebuiltWorkspace handles authorization for workspace resource types.
153153
// prebuilt_workspaces are a subset of workspaces, currently limited to
154-
// supporting delete operations. Therefore, if the action is delete or
155-
// update and the workspace is a prebuild, a prebuilt-specific authorization
156-
// is attempted first. If that fails, it falls back to normal workspace
157-
// authorization.
154+
// supporting delete operations. This function first attempts normal workspace
155+
// authorization. If that fails, the action is delete or update and the workspace
156+
// is a prebuild, a prebuilt-specific authorization is attempted.
158157
// Note: Delete operations of workspaces requires both update and delete
159158
// permissions.
160159
func (q *querier) authorizePrebuiltWorkspace(ctx context.Context, action policy.Action, workspace database.Workspace) error {
161-
var prebuiltErr error
162-
// Special handling for prebuilt_workspace deletion authorization check
160+
// Try default workspace authorization first
161+
var workspaceErr error
162+
if workspaceErr = q.authorizeContext(ctx, action, workspace); workspaceErr == nil {
163+
return nil
164+
}
165+
166+
// Special handling for prebuilt workspace deletion
163167
if (action == policy.ActionUpdate || action == policy.ActionDelete) && workspace.IsPrebuild() {
164-
// Try prebuilt-specific authorization first
168+
var prebuiltErr error
165169
if prebuiltErr = q.authorizeContext(ctx, action, workspace.AsPrebuild()); prebuiltErr == nil {
166170
return nil
167171
}
172+
return xerrors.Errorf("authorize context: %w", errors.Join(workspaceErr, prebuiltErr))
168173
}
169-
// Fallback to normal workspace authorization check
170-
if err := q.authorizeContext(ctx, action, workspace); err != nil {
171-
return xerrors.Errorf("authorize context: %w", errors.Join(prebuiltErr, err))
172-
}
173-
return nil
174+
175+
return xerrors.Errorf("authorize context: %w", errors.Join(workspaceErr))
174176
}
175177

176178
type authContextKey struct{}

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5590,7 +5590,17 @@ func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() {
55905590
Reason: database.BuildReasonInitiator,
55915591
TemplateVersionID: tv.ID,
55925592
JobID: pj.ID,
5593-
}).Asserts(w.AsPrebuild(), policy.ActionDelete)
5593+
}).
5594+
// Simulate a fallback authorization flow:
5595+
// - First, the default workspace authorization fails (simulated by returning an error).
5596+
// - Then, authorization is retried using the prebuilt workspace object, which succeeds.
5597+
// The test asserts that both authorization attempts occur in the correct order.
5598+
WithSuccessAuthorizer(func(ctx context.Context, subject rbac.Subject, action policy.Action, obj rbac.Object) error {
5599+
if obj.Type == rbac.ResourceWorkspace.Type {
5600+
return xerrors.Errorf("not authorized for workspace type")
5601+
}
5602+
return nil
5603+
}).Asserts(w, policy.ActionDelete, w.AsPrebuild(), policy.ActionDelete)
55945604
}))
55955605
s.Run("PrebuildUpdate/InsertWorkspaceBuildParameters", s.Subtest(func(db database.Store, check *expects) {
55965606
u := dbgen.User(s.T(), db, database.User{})
@@ -5619,6 +5629,16 @@ func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() {
56195629
})
56205630
check.Args(database.InsertWorkspaceBuildParametersParams{
56215631
WorkspaceBuildID: wb.ID,
5622-
}).Asserts(w.AsPrebuild(), policy.ActionUpdate)
5632+
}).
5633+
// Simulate a fallback authorization flow:
5634+
// - First, the default workspace authorization fails (simulated by returning an error).
5635+
// - Then, authorization is retried using the prebuilt workspace object, which succeeds.
5636+
// The test asserts that both authorization attempts occur in the correct order.
5637+
WithSuccessAuthorizer(func(ctx context.Context, subject rbac.Subject, action policy.Action, obj rbac.Object) error {
5638+
if obj.Type == rbac.ResourceWorkspace.Type {
5639+
return xerrors.Errorf("not authorized for workspace type")
5640+
}
5641+
return nil
5642+
}).Asserts(w, policy.ActionUpdate, w.AsPrebuild(), policy.ActionUpdate)
56235643
}))
56245644
}

coderd/workspacebuilds.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,17 +393,16 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
393393
ctx,
394394
tx,
395395
func(action policy.Action, object rbac.Objecter) bool {
396+
if auth := api.Authorize(r, action, object); auth {
397+
return true
398+
}
396399
// Special handling for prebuilt workspace deletion
397400
if object.RBACObject().Type == rbac.ResourceWorkspace.Type && action == policy.ActionDelete {
398-
if workspaceObj, ok := object.(database.Workspace); ok {
399-
// Try prebuilt-specific authorization first
400-
if auth := api.Authorize(r, action, workspaceObj.AsPrebuild()); auth {
401-
return auth
402-
}
401+
if workspaceObj, ok := object.(database.Workspace); ok && workspaceObj.IsPrebuild() {
402+
return api.Authorize(r, action, workspaceObj.AsPrebuild())
403403
}
404404
}
405-
// Fallback to default authorization
406-
return api.Authorize(r, action, object)
405+
return false
407406
},
408407
audit.WorkspaceBuildBaggageFromRequest(r),
409408
)

coderd/wsbuilder/wsbuilder.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -963,14 +963,12 @@ func (b *Builder) authorize(authFunc func(action policy.Action, object rbac.Obje
963963
return BuildError{http.StatusBadRequest, msg, xerrors.New(msg)}
964964
}
965965

966+
// Try default workspace authorization first
967+
authorized := authFunc(action, b.workspace)
968+
966969
// Special handling for prebuilt workspace deletion
967-
authorized := false
968-
if action == policy.ActionDelete && b.workspace.IsPrebuild() && authFunc(action, b.workspace.AsPrebuild()) {
969-
authorized = true
970-
}
971-
// Fallback to default authorization
972-
if !authorized && authFunc(action, b.workspace) {
973-
authorized = true
970+
if !authorized && action == policy.ActionDelete && b.workspace.IsPrebuild() {
971+
authorized = authFunc(action, b.workspace.AsPrebuild())
974972
}
975973

976974
if !authorized {

0 commit comments

Comments
 (0)