mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
chore: reorder prebuilt workspace authorization logic (#18506)
## Description Follow-up from PR https://github.com/coder/coder/pull/18333 Related with: https://github.com/coder/coder/pull/18333#discussion_r2159300881 This changes the authorization logic to first try the normal workspace authorization check, and only if the resource is a prebuilt workspace, fall back to the prebuilt workspace authorization check. Since prebuilt workspaces are a subset of workspaces, the normal workspace check is more likely to succeed. This is a small optimization to reduce unnecessary prebuilt authorization calls.
This commit is contained in:
@ -151,26 +151,28 @@ func (q *querier) authorizeContext(ctx context.Context, action policy.Action, ob
|
||||
|
||||
// authorizePrebuiltWorkspace handles authorization for workspace resource types.
|
||||
// prebuilt_workspaces are a subset of workspaces, currently limited to
|
||||
// supporting delete operations. Therefore, if the action is delete or
|
||||
// update and the workspace is a prebuild, a prebuilt-specific authorization
|
||||
// is attempted first. If that fails, it falls back to normal workspace
|
||||
// authorization.
|
||||
// supporting delete operations. This function first attempts normal workspace
|
||||
// authorization. If that fails, the action is delete or update and the workspace
|
||||
// is a prebuild, a prebuilt-specific authorization is attempted.
|
||||
// Note: Delete operations of workspaces requires both update and delete
|
||||
// permissions.
|
||||
func (q *querier) authorizePrebuiltWorkspace(ctx context.Context, action policy.Action, workspace database.Workspace) error {
|
||||
var prebuiltErr error
|
||||
// Special handling for prebuilt_workspace deletion authorization check
|
||||
// Try default workspace authorization first
|
||||
var workspaceErr error
|
||||
if workspaceErr = q.authorizeContext(ctx, action, workspace); workspaceErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special handling for prebuilt workspace deletion
|
||||
if (action == policy.ActionUpdate || action == policy.ActionDelete) && workspace.IsPrebuild() {
|
||||
// Try prebuilt-specific authorization first
|
||||
var prebuiltErr error
|
||||
if prebuiltErr = q.authorizeContext(ctx, action, workspace.AsPrebuild()); prebuiltErr == nil {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("authorize context failed for workspace (%v) and prebuilt (%w)", workspaceErr, prebuiltErr)
|
||||
}
|
||||
// Fallback to normal workspace authorization check
|
||||
if err := q.authorizeContext(ctx, action, workspace); err != nil {
|
||||
return xerrors.Errorf("authorize context: %w", errors.Join(prebuiltErr, err))
|
||||
}
|
||||
return nil
|
||||
|
||||
return xerrors.Errorf("authorize context: %w", workspaceErr)
|
||||
}
|
||||
|
||||
type authContextKey struct{}
|
||||
|
@ -5650,7 +5650,17 @@ func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() {
|
||||
Reason: database.BuildReasonInitiator,
|
||||
TemplateVersionID: tv.ID,
|
||||
JobID: pj.ID,
|
||||
}).Asserts(w.AsPrebuild(), policy.ActionDelete)
|
||||
}).
|
||||
// Simulate a fallback authorization flow:
|
||||
// - First, the default workspace authorization fails (simulated by returning an error).
|
||||
// - Then, authorization is retried using the prebuilt workspace object, which succeeds.
|
||||
// The test asserts that both authorization attempts occur in the correct order.
|
||||
WithSuccessAuthorizer(func(ctx context.Context, subject rbac.Subject, action policy.Action, obj rbac.Object) error {
|
||||
if obj.Type == rbac.ResourceWorkspace.Type {
|
||||
return xerrors.Errorf("not authorized for workspace type")
|
||||
}
|
||||
return nil
|
||||
}).Asserts(w, policy.ActionDelete, w.AsPrebuild(), policy.ActionDelete)
|
||||
}))
|
||||
s.Run("PrebuildUpdate/InsertWorkspaceBuildParameters", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
@ -5679,6 +5689,16 @@ func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() {
|
||||
})
|
||||
check.Args(database.InsertWorkspaceBuildParametersParams{
|
||||
WorkspaceBuildID: wb.ID,
|
||||
}).Asserts(w.AsPrebuild(), policy.ActionUpdate)
|
||||
}).
|
||||
// Simulate a fallback authorization flow:
|
||||
// - First, the default workspace authorization fails (simulated by returning an error).
|
||||
// - Then, authorization is retried using the prebuilt workspace object, which succeeds.
|
||||
// The test asserts that both authorization attempts occur in the correct order.
|
||||
WithSuccessAuthorizer(func(ctx context.Context, subject rbac.Subject, action policy.Action, obj rbac.Object) error {
|
||||
if obj.Type == rbac.ResourceWorkspace.Type {
|
||||
return xerrors.Errorf("not authorized for workspace type")
|
||||
}
|
||||
return nil
|
||||
}).Asserts(w, policy.ActionUpdate, w.AsPrebuild(), policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
@ -199,6 +199,13 @@ func (gm GroupMember) RBACObject() rbac.Object {
|
||||
return rbac.ResourceGroupMember.WithID(gm.UserID).InOrg(gm.OrganizationID).WithOwner(gm.UserID.String())
|
||||
}
|
||||
|
||||
// PrebuiltWorkspaceResource defines the interface for types that can be identified as prebuilt workspaces
|
||||
// and converted to their corresponding prebuilt workspace RBAC object.
|
||||
type PrebuiltWorkspaceResource interface {
|
||||
IsPrebuild() bool
|
||||
AsPrebuild() rbac.Object
|
||||
}
|
||||
|
||||
// WorkspaceTable converts a Workspace to it's reduced version.
|
||||
// A more generalized solution is to use json marshaling to
|
||||
// consistently keep these two structs in sync.
|
||||
|
Reference in New Issue
Block a user