@@ -417,6 +417,35 @@ var (
417417rbac .ResourceProvisionerJobs .Type : {policy .ActionRead ,policy .ActionUpdate ,policy .ActionCreate },
418418rbac .ResourceOauth2App .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate ,policy .ActionDelete },
419419rbac .ResourceOauth2AppSecret .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate ,policy .ActionDelete },
420+ rbac .ResourceOauth2AppCodeToken .Type : {policy .ActionCreate ,policy .ActionRead ,policy .ActionUpdate ,policy .ActionDelete },
421+ }),
422+ Org :map [string ][]rbac.Permission {},
423+ User : []rbac.Permission {},
424+ },
425+ }),
426+ Scope :rbac .ScopeAll ,
427+ }.WithCachedASTValue ()
428+
429+ subjectSystemOAuth2 = rbac.Subject {
430+ Type :rbac .SubjectTypeSystemRestricted ,
431+ FriendlyName :"System OAuth2" ,
432+ ID :uuid .Nil .String (),
433+ Roles :rbac .Roles ([]rbac.Role {
434+ {
435+ Identifier : rbac.RoleIdentifier {Name :"system-oauth2" },
436+ DisplayName :"System OAuth2" ,
437+ Site :rbac .Permissions (map [string ][]policy.Action {
438+ // OAuth2 resources - full CRUD permissions
439+ rbac .ResourceOauth2App .Type :rbac .ResourceOauth2App .AvailableActions (),
440+ rbac .ResourceOauth2AppSecret .Type :rbac .ResourceOauth2AppSecret .AvailableActions (),
441+ rbac .ResourceOauth2AppCodeToken .Type :rbac .ResourceOauth2AppCodeToken .AvailableActions (),
442+
443+ // API key permissions needed for OAuth2 token revocation
444+ rbac .ResourceApiKey .Type : {policy .ActionRead ,policy .ActionDelete },
445+
446+ // Minimal read permissions that might be needed for OAuth2 operations
447+ rbac .ResourceUser .Type : {policy .ActionRead },
448+ rbac .ResourceOrganization .Type : {policy .ActionRead },
420449}),
421450Org :map [string ][]rbac.Permission {},
422451User : []rbac.Permission {},
@@ -567,6 +596,12 @@ func AsSystemRestricted(ctx context.Context) context.Context {
567596return As (ctx ,subjectSystemRestricted )
568597}
569598
599+ // AsSystemOAuth2 returns a context with an actor that has permissions
600+ // required for OAuth2 provider operations (token revocation, device codes, registration).
601+ func AsSystemOAuth2 (ctx context.Context ) context.Context {
602+ return As (ctx ,subjectSystemOAuth2 )
603+ }
604+
570605// AsSystemReadProvisionerDaemons returns a context with an actor that has permissions
571606// to read provisioner daemons.
572607func AsSystemReadProvisionerDaemons (ctx context.Context ) context.Context {
@@ -1346,6 +1381,14 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
13461381return q .db .CleanTailnetTunnels (ctx )
13471382}
13481383
1384+ func (q * querier )ConsumeOAuth2ProviderAppCodeByPrefix (ctx context.Context ,secretPrefix []byte ) (database.OAuth2ProviderAppCode ,error ) {
1385+ return updateWithReturn (q .log ,q .auth ,q .db .GetOAuth2ProviderAppCodeByPrefix ,q .db .ConsumeOAuth2ProviderAppCodeByPrefix )(ctx ,secretPrefix )
1386+ }
1387+
1388+ func (q * querier )ConsumeOAuth2ProviderDeviceCodeByPrefix (ctx context.Context ,deviceCodePrefix string ) (database.OAuth2ProviderDeviceCode ,error ) {
1389+ return updateWithReturn (q .log ,q .auth ,q .db .GetOAuth2ProviderDeviceCodeByPrefix ,q .db .ConsumeOAuth2ProviderDeviceCodeByPrefix )(ctx ,deviceCodePrefix )
1390+ }
1391+
13491392func (q * querier )CountAuditLogs (ctx context.Context ,arg database.CountAuditLogsParams ) (int64 ,error ) {
13501393// Shortcut if the user is an owner. The SQL filter is noticeable,
13511394// and this is an easy win for owners. Which is the common case.
@@ -1489,7 +1532,7 @@ func (q *querier) DeleteExpiredOAuth2ProviderDeviceCodes(ctx context.Context) er
14891532func (q * querier )DeleteExternalAuthLink (ctx context.Context ,arg database.DeleteExternalAuthLinkParams )error {
14901533return fetchAndExec (q .log ,q .auth ,policy .ActionUpdatePersonal ,func (ctx context.Context ,arg database.DeleteExternalAuthLinkParams ) (database.ExternalAuthLink ,error ) {
14911534//nolint:gosimple
1492- return q .db .GetExternalAuthLink (ctx , database.GetExternalAuthLinkParams { UserID : arg . UserID , ProviderID : arg . ProviderID } )
1535+ return q .db .GetExternalAuthLink (ctx ,database .GetExternalAuthLinkParams ( arg ) )
14931536},q .db .DeleteExternalAuthLink )(ctx ,arg )
14941537}
14951538
@@ -1568,27 +1611,30 @@ func (q *querier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Contex
15681611return q .db .DeleteOAuth2ProviderAppTokensByAppAndUserID (ctx ,arg )
15691612}
15701613
1571- func (q * querier )DeleteOldAuditLogConnectionEvents (ctx context.Context ,threshold database.DeleteOldAuditLogConnectionEventsParams )error {
1572- // `ResourceSystem` is deprecated, but it doesn't make sense to add
1573- // `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1574- // only time we'll be deleting from the audit log.
1575- if err := q .authorizeContext (ctx ,policy .ActionDelete ,rbac .ResourceSystem );err != nil {
1576- return err
1577- }
1578- return q .db .DeleteOldAuditLogConnectionEvents (ctx ,threshold )
1579- }
1580-
15811614func (q * querier )DeleteOAuth2ProviderDeviceCodeByID (ctx context.Context ,id uuid.UUID )error {
15821615// Fetch the device code first to check authorization
15831616deviceCode ,err := q .db .GetOAuth2ProviderDeviceCodeByID (ctx ,id )
15841617if err != nil {
1585- return err
1618+ return xerrors . Errorf ( "get oauth2 provider device code: %w" , err )
15861619}
15871620if err := q .authorizeContext (ctx ,policy .ActionDelete ,deviceCode );err != nil {
1588- return err
1621+ return xerrors . Errorf ( "authorize oauth2 provider device code deletion: %w" , err )
15891622}
15901623
1591- return q .db .DeleteOAuth2ProviderDeviceCodeByID (ctx ,id )
1624+ if err := q .db .DeleteOAuth2ProviderDeviceCodeByID (ctx ,id );err != nil {
1625+ return xerrors .Errorf ("delete oauth2 provider device code: %w" ,err )
1626+ }
1627+ return nil
1628+ }
1629+
1630+ func (q * querier )DeleteOldAuditLogConnectionEvents (ctx context.Context ,threshold database.DeleteOldAuditLogConnectionEventsParams )error {
1631+ // `ResourceSystem` is deprecated, but it doesn't make sense to add
1632+ // `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1633+ // only time we'll be deleting from the audit log.
1634+ if err := q .authorizeContext (ctx ,policy .ActionDelete ,rbac .ResourceSystem );err != nil {
1635+ return err
1636+ }
1637+ return q .db .DeleteOldAuditLogConnectionEvents (ctx ,threshold )
15921638}
15931639
15941640func (q * querier )DeleteOldNotificationMessages (ctx context.Context )error {
@@ -1620,7 +1666,7 @@ func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {
16201666}
16211667
16221668func (q * querier )DeleteOrganizationMember (ctx context.Context ,arg database.DeleteOrganizationMemberParams )error {
1623- return deleteQ [database. OrganizationMember ] (q .log ,q .auth ,func (ctx context.Context ,arg database.DeleteOrganizationMemberParams ) (database.OrganizationMember ,error ) {
1669+ return deleteQ (q .log ,q .auth ,func (ctx context.Context ,arg database.DeleteOrganizationMemberParams ) (database.OrganizationMember ,error ) {
16241670member ,err := database .ExpectOne (q .OrganizationMembers (ctx , database.OrganizationMembersParams {
16251671OrganizationID :arg .OrganizationID ,
16261672UserID :arg .UserID ,
@@ -2213,7 +2259,7 @@ func (q *querier) GetLicenseByID(ctx context.Context, id int32) (database.Licens
22132259}
22142260
22152261func (q * querier )GetLicenses (ctx context.Context ) ([]database.License ,error ) {
2216- fetch := func (ctx context.Context ,_ interface {} ) ([]database.License ,error ) {
2262+ fetch := func (ctx context.Context ,_ any ) ([]database.License ,error ) {
22172263return q .db .GetLicenses (ctx )
22182264}
22192265return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -2377,8 +2423,8 @@ func (q *querier) GetOAuth2ProviderDeviceCodeByUserCode(ctx context.Context, use
23772423}
23782424
23792425func (q * querier )GetOAuth2ProviderDeviceCodesByClientID (ctx context.Context ,clientID uuid.UUID ) ([]database.OAuth2ProviderDeviceCode ,error ) {
2380- // This requires access to readthe OAuth2 app
2381- if err := q .authorizeContext (ctx ,policy .ActionRead ,rbac .ResourceOauth2App );err != nil {
2426+ // This requires access to read OAuth2 app code tokens
2427+ if err := q .authorizeContext (ctx ,policy .ActionRead ,rbac .ResourceOauth2AppCodeToken );err != nil {
23822428return []database.OAuth2ProviderDeviceCode {},err
23832429}
23842430return q .db .GetOAuth2ProviderDeviceCodesByClientID (ctx ,clientID )
@@ -2435,7 +2481,7 @@ func (q *querier) GetOrganizationResourceCountByID(ctx context.Context, organiza
24352481}
24362482
24372483func (q * querier )GetOrganizations (ctx context.Context ,args database.GetOrganizationsParams ) ([]database.Organization ,error ) {
2438- fetch := func (ctx context.Context ,_ interface {} ) ([]database.Organization ,error ) {
2484+ fetch := func (ctx context.Context ,_ any ) ([]database.Organization ,error ) {
24392485return q .db .GetOrganizations (ctx ,args )
24402486}
24412487return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -2563,7 +2609,7 @@ func (q *querier) GetPreviousTemplateVersion(ctx context.Context, arg database.G
25632609}
25642610
25652611func (q * querier )GetProvisionerDaemons (ctx context.Context ) ([]database.ProvisionerDaemon ,error ) {
2566- fetch := func (ctx context.Context ,_ interface {} ) ([]database.ProvisionerDaemon ,error ) {
2612+ fetch := func (ctx context.Context ,_ any ) ([]database.ProvisionerDaemon ,error ) {
25672613return q .db .GetProvisionerDaemons (ctx )
25682614}
25692615return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -3554,7 +3600,7 @@ func (q *querier) GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt
35543600}
35553601
35563602func (q * querier )GetWorkspaceProxies (ctx context.Context ) ([]database.WorkspaceProxy ,error ) {
3557- return fetchWithPostFilter (q .auth ,policy .ActionRead ,func (ctx context.Context ,_ interface {} ) ([]database.WorkspaceProxy ,error ) {
3603+ return fetchWithPostFilter (q .auth ,policy .ActionRead ,func (ctx context.Context ,_ any ) ([]database.WorkspaceProxy ,error ) {
35583604return q .db .GetWorkspaceProxies (ctx )
35593605})(ctx ,nil )
35603606}
@@ -3848,8 +3894,8 @@ func (q *querier) InsertOAuth2ProviderAppToken(ctx context.Context, arg database
38483894}
38493895
38503896func (q * querier )InsertOAuth2ProviderDeviceCode (ctx context.Context ,arg database.InsertOAuth2ProviderDeviceCodeParams ) (database.OAuth2ProviderDeviceCode ,error ) {
3851- // Creating device codes requires OAuth2 app access
3852- if err := q .authorizeContext (ctx ,policy .ActionCreate ,rbac .ResourceOauth2App );err != nil {
3897+ // Creating device codes requires OAuth2 appcode token creation access
3898+ if err := q .authorizeContext (ctx ,policy .ActionCreate ,rbac .ResourceOauth2AppCodeToken );err != nil {
38533899return database.OAuth2ProviderDeviceCode {},err
38543900}
38553901return q .db .InsertOAuth2ProviderDeviceCode (ctx ,arg )
@@ -4156,10 +4202,11 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
41564202return xerrors .Errorf ("get workspace by id: %w" ,err )
41574203}
41584204
4159- var action policy.Action = policy .ActionWorkspaceStart
4160- if arg .Transition == database .WorkspaceTransitionDelete {
4205+ action := policy .ActionWorkspaceStart
4206+ switch arg .Transition {
4207+ case database .WorkspaceTransitionDelete :
41614208action = policy .ActionDelete
4162- } else if arg . Transition == database .WorkspaceTransitionStop {
4209+ case database .WorkspaceTransitionStop :
41634210action = policy .ActionWorkspaceStop
41644211}
41654212
@@ -4536,13 +4583,10 @@ func (q *querier) UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg dat
45364583}
45374584
45384585func (q * querier )UpdateOAuth2ProviderDeviceCodeAuthorization (ctx context.Context ,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4539- // Verify the user is authenticated for device code authorization
4540- _ ,ok := ActorFromContext (ctx )
4541- if ! ok {
4542- return database.OAuth2ProviderDeviceCode {},ErrNoActor
4586+ fetch := func (ctx context.Context ,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4587+ return q .db .GetOAuth2ProviderDeviceCodeByID (ctx ,arg .ID )
45434588}
4544-
4545- return q .db .UpdateOAuth2ProviderDeviceCodeAuthorization (ctx ,arg )
4589+ return updateWithReturn (q .log ,q .auth ,fetch ,q .db .UpdateOAuth2ProviderDeviceCodeAuthorization )(ctx ,arg )
45464590}
45474591
45484592func (q * querier )UpdateOrganization (ctx context.Context ,arg database.UpdateOrganizationParams ) (database.Organization ,error ) {