@@ -436,6 +436,35 @@ var (
436436rbac .ResourceProvisionerJobs .Type : {policy .ActionRead ,policy .ActionUpdate ,policy .ActionCreate },
437437rbac .ResourceOauth2App .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate ,policy .ActionDelete },
438438rbac .ResourceOauth2AppSecret .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate ,policy .ActionDelete },
439+ rbac .ResourceOauth2AppCodeToken .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate ,policy .ActionDelete },
440+ }),
441+ Org :map [string ][]rbac.Permission {},
442+ User : []rbac.Permission {},
443+ },
444+ }),
445+ Scope :rbac .ScopeAll ,
446+ }.WithCachedASTValue ()
447+
448+ subjectSystemOAuth2 = rbac.Subject {
449+ Type :rbac .SubjectTypeSystemRestricted ,
450+ FriendlyName :"System OAuth2" ,
451+ ID :uuid .Nil .String (),
452+ Roles :rbac .Roles ([]rbac.Role {
453+ {
454+ Identifier : rbac.RoleIdentifier {Name :"system-oauth2" },
455+ DisplayName :"System OAuth2" ,
456+ Site :rbac .Permissions (map [string ][]policy.Action {
457+ // OAuth2 resources - full CRUD permissions
458+ rbac .ResourceOauth2App .Type :rbac .ResourceOauth2App .AvailableActions (),
459+ rbac .ResourceOauth2AppSecret .Type :rbac .ResourceOauth2AppSecret .AvailableActions (),
460+ rbac .ResourceOauth2AppCodeToken .Type :rbac .ResourceOauth2AppCodeToken .AvailableActions (),
461+
462+ // API key permissions needed for OAuth2 token revocation
463+ rbac .ResourceApiKey .Type : {policy .ActionRead ,policy .ActionDelete },
464+
465+ // Minimal read permissions that might be needed for OAuth2 operations
466+ rbac .ResourceUser .Type : {policy .ActionRead },
467+ rbac .ResourceOrganization .Type : {policy .ActionRead },
439468}),
440469User : []rbac.Permission {},
441470ByOrgID :map [string ]rbac.OrgPermissions {},
@@ -641,6 +670,12 @@ func AsSystemRestricted(ctx context.Context) context.Context {
641670return As (ctx ,subjectSystemRestricted )
642671}
643672
673+ // AsSystemOAuth2 returns a context with an actor that has permissions
674+ // required for OAuth2 provider operations (token revocation, device codes, registration).
675+ func AsSystemOAuth2 (ctx context.Context ) context.Context {
676+ return As (ctx ,subjectSystemOAuth2 )
677+ }
678+
644679// AsSystemReadProvisionerDaemons returns a context with an actor that has permissions
645680// to read provisioner daemons.
646681func AsSystemReadProvisionerDaemons (ctx context.Context ) context.Context {
@@ -1434,6 +1469,14 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
14341469return q .db .CleanTailnetTunnels (ctx )
14351470}
14361471
1472+ func (q * querier )ConsumeOAuth2ProviderAppCodeByPrefix (ctx context.Context ,secretPrefix []byte ) (database.OAuth2ProviderAppCode ,error ) {
1473+ return updateWithReturn (q .log ,q .auth ,q .db .GetOAuth2ProviderAppCodeByPrefix ,q .db .ConsumeOAuth2ProviderAppCodeByPrefix )(ctx ,secretPrefix )
1474+ }
1475+
1476+ func (q * querier )ConsumeOAuth2ProviderDeviceCodeByPrefix (ctx context.Context ,deviceCodePrefix string ) (database.OAuth2ProviderDeviceCode ,error ) {
1477+ return updateWithReturn (q .log ,q .auth ,q .db .GetOAuth2ProviderDeviceCodeByPrefix ,q .db .ConsumeOAuth2ProviderDeviceCodeByPrefix )(ctx ,deviceCodePrefix )
1478+ }
1479+
14371480func (q * querier )CountAuditLogs (ctx context.Context ,arg database.CountAuditLogsParams ) (int64 ,error ) {
14381481// Shortcut if the user is an owner. The SQL filter is noticeable,
14391482// and this is an easy win for owners. Which is the common case.
@@ -1577,7 +1620,7 @@ func (q *querier) DeleteExpiredOAuth2ProviderDeviceCodes(ctx context.Context) er
15771620func (q * querier )DeleteExternalAuthLink (ctx context.Context ,arg database.DeleteExternalAuthLinkParams )error {
15781621return fetchAndExec (q .log ,q .auth ,policy .ActionUpdatePersonal ,func (ctx context.Context ,arg database.DeleteExternalAuthLinkParams ) (database.ExternalAuthLink ,error ) {
15791622//nolint:gosimple
1580- return q .db .GetExternalAuthLink (ctx , database.GetExternalAuthLinkParams { UserID : arg . UserID , ProviderID : arg . ProviderID } )
1623+ return q .db .GetExternalAuthLink (ctx ,database .GetExternalAuthLinkParams ( arg ) )
15811624},q .db .DeleteExternalAuthLink )(ctx ,arg )
15821625}
15831626
@@ -1656,27 +1699,30 @@ func (q *querier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Contex
16561699return q .db .DeleteOAuth2ProviderAppTokensByAppAndUserID (ctx ,arg )
16571700}
16581701
1659- func (q * querier )DeleteOldAuditLogConnectionEvents (ctx context.Context ,threshold database.DeleteOldAuditLogConnectionEventsParams )error {
1660- // `ResourceSystem` is deprecated, but it doesn't make sense to add
1661- // `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1662- // only time we'll be deleting from the audit log.
1663- if err := q .authorizeContext (ctx ,policy .ActionDelete ,rbac .ResourceSystem );err != nil {
1664- return err
1665- }
1666- return q .db .DeleteOldAuditLogConnectionEvents (ctx ,threshold )
1667- }
1668-
16691702func (q * querier )DeleteOAuth2ProviderDeviceCodeByID (ctx context.Context ,id uuid.UUID )error {
16701703// Fetch the device code first to check authorization
16711704deviceCode ,err := q .db .GetOAuth2ProviderDeviceCodeByID (ctx ,id )
16721705if err != nil {
1673- return err
1706+ return xerrors . Errorf ( "get oauth2 provider device code: %w" , err )
16741707}
16751708if err := q .authorizeContext (ctx ,policy .ActionDelete ,deviceCode );err != nil {
1676- return err
1709+ return xerrors .Errorf ("authorize oauth2 provider device code deletion: %w" ,err )
1710+ }
1711+
1712+ if err := q .db .DeleteOAuth2ProviderDeviceCodeByID (ctx ,id );err != nil {
1713+ return xerrors .Errorf ("delete oauth2 provider device code: %w" ,err )
16771714}
1715+ return nil
1716+ }
16781717
1679- return q .db .DeleteOAuth2ProviderDeviceCodeByID (ctx ,id )
1718+ func (q * querier )DeleteOldAuditLogConnectionEvents (ctx context.Context ,threshold database.DeleteOldAuditLogConnectionEventsParams )error {
1719+ // `ResourceSystem` is deprecated, but it doesn't make sense to add
1720+ // `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1721+ // only time we'll be deleting from the audit log.
1722+ if err := q .authorizeContext (ctx ,policy .ActionDelete ,rbac .ResourceSystem );err != nil {
1723+ return err
1724+ }
1725+ return q .db .DeleteOldAuditLogConnectionEvents (ctx ,threshold )
16801726}
16811727
16821728func (q * querier )DeleteOldNotificationMessages (ctx context.Context )error {
@@ -1708,7 +1754,7 @@ func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {
17081754}
17091755
17101756func (q * querier )DeleteOrganizationMember (ctx context.Context ,arg database.DeleteOrganizationMemberParams )error {
1711- return deleteQ [database. OrganizationMember ] (q .log ,q .auth ,func (ctx context.Context ,arg database.DeleteOrganizationMemberParams ) (database.OrganizationMember ,error ) {
1757+ return deleteQ (q .log ,q .auth ,func (ctx context.Context ,arg database.DeleteOrganizationMemberParams ) (database.OrganizationMember ,error ) {
17121758member ,err := database .ExpectOne (q .OrganizationMembers (ctx , database.OrganizationMembersParams {
17131759OrganizationID :arg .OrganizationID ,
17141760UserID :arg .UserID ,
@@ -2371,7 +2417,7 @@ func (q *querier) GetLicenseByID(ctx context.Context, id int32) (database.Licens
23712417}
23722418
23732419func (q * querier )GetLicenses (ctx context.Context ) ([]database.License ,error ) {
2374- fetch := func (ctx context.Context ,_ interface {} ) ([]database.License ,error ) {
2420+ fetch := func (ctx context.Context ,_ any ) ([]database.License ,error ) {
23752421return q .db .GetLicenses (ctx )
23762422}
23772423return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -2527,8 +2573,8 @@ func (q *querier) GetOAuth2ProviderDeviceCodeByUserCode(ctx context.Context, use
25272573}
25282574
25292575func (q * querier )GetOAuth2ProviderDeviceCodesByClientID (ctx context.Context ,clientID uuid.UUID ) ([]database.OAuth2ProviderDeviceCode ,error ) {
2530- // This requires access to readthe OAuth2 app
2531- if err := q .authorizeContext (ctx ,policy .ActionRead ,rbac .ResourceOauth2App );err != nil {
2576+ // This requires access to read OAuth2 app code tokens
2577+ if err := q .authorizeContext (ctx ,policy .ActionRead ,rbac .ResourceOauth2AppCodeToken );err != nil {
25322578return []database.OAuth2ProviderDeviceCode {},err
25332579}
25342580return q .db .GetOAuth2ProviderDeviceCodesByClientID (ctx ,clientID )
@@ -2585,7 +2631,7 @@ func (q *querier) GetOrganizationResourceCountByID(ctx context.Context, organiza
25852631}
25862632
25872633func (q * querier )GetOrganizations (ctx context.Context ,args database.GetOrganizationsParams ) ([]database.Organization ,error ) {
2588- fetch := func (ctx context.Context ,_ interface {} ) ([]database.Organization ,error ) {
2634+ fetch := func (ctx context.Context ,_ any ) ([]database.Organization ,error ) {
25892635return q .db .GetOrganizations (ctx ,args )
25902636}
25912637return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -2713,7 +2759,7 @@ func (q *querier) GetPreviousTemplateVersion(ctx context.Context, arg database.G
27132759}
27142760
27152761func (q * querier )GetProvisionerDaemons (ctx context.Context ) ([]database.ProvisionerDaemon ,error ) {
2716- fetch := func (ctx context.Context ,_ interface {} ) ([]database.ProvisionerDaemon ,error ) {
2762+ fetch := func (ctx context.Context ,_ any ) ([]database.ProvisionerDaemon ,error ) {
27172763return q .db .GetProvisionerDaemons (ctx )
27182764}
27192765return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -3741,7 +3787,7 @@ func (q *querier) GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt
37413787}
37423788
37433789func (q * querier )GetWorkspaceProxies (ctx context.Context ) ([]database.WorkspaceProxy ,error ) {
3744- return fetchWithPostFilter (q .auth ,policy .ActionRead ,func (ctx context.Context ,_ interface {} ) ([]database.WorkspaceProxy ,error ) {
3790+ return fetchWithPostFilter (q .auth ,policy .ActionRead ,func (ctx context.Context ,_ any ) ([]database.WorkspaceProxy ,error ) {
37453791return q .db .GetWorkspaceProxies (ctx )
37463792})(ctx ,nil )
37473793}
@@ -4071,8 +4117,8 @@ func (q *querier) InsertOAuth2ProviderAppToken(ctx context.Context, arg database
40714117}
40724118
40734119func (q * querier )InsertOAuth2ProviderDeviceCode (ctx context.Context ,arg database.InsertOAuth2ProviderDeviceCodeParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4074- // Creating device codes requires OAuth2 app access
4075- if err := q .authorizeContext (ctx ,policy .ActionCreate ,rbac .ResourceOauth2App );err != nil {
4120+ // Creating device codes requires OAuth2 appcode token creation access
4121+ if err := q .authorizeContext (ctx ,policy .ActionCreate ,rbac .ResourceOauth2AppCodeToken );err != nil {
40764122return database.OAuth2ProviderDeviceCode {},err
40774123}
40784124return q .db .InsertOAuth2ProviderDeviceCode (ctx ,arg )
@@ -4386,10 +4432,11 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
43864432return xerrors .Errorf ("get workspace by id: %w" ,err )
43874433}
43884434
4389- var action policy.Action = policy .ActionWorkspaceStart
4390- if arg .Transition == database .WorkspaceTransitionDelete {
4435+ action := policy .ActionWorkspaceStart
4436+ switch arg .Transition {
4437+ case database .WorkspaceTransitionDelete :
43914438action = policy .ActionDelete
4392- } else if arg . Transition == database .WorkspaceTransitionStop {
4439+ case database .WorkspaceTransitionStop :
43934440action = policy .ActionWorkspaceStop
43944441}
43954442
@@ -4813,13 +4860,10 @@ func (q *querier) UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg dat
48134860}
48144861
48154862func (q * querier )UpdateOAuth2ProviderDeviceCodeAuthorization (ctx context.Context ,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4816- // Verify the user is authenticated for device code authorization
4817- _ ,ok := ActorFromContext (ctx )
4818- if ! ok {
4819- return database.OAuth2ProviderDeviceCode {},ErrNoActor
4863+ fetch := func (ctx context.Context ,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4864+ return q .db .GetOAuth2ProviderDeviceCodeByID (ctx ,arg .ID )
48204865}
4821-
4822- return q .db .UpdateOAuth2ProviderDeviceCodeAuthorization (ctx ,arg )
4866+ return updateWithReturn (q .log ,q .auth ,fetch ,q .db .UpdateOAuth2ProviderDeviceCodeAuthorization )(ctx ,arg )
48234867}
48244868
48254869func (q * querier )UpdateOrganization (ctx context.Context ,arg database.UpdateOrganizationParams ) (database.Organization ,error ) {