@@ -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.
@@ -1481,7 +1524,7 @@ func (q *querier) DeleteExpiredOAuth2ProviderDeviceCodes(ctx context.Context) er
14811524func (q * querier )DeleteExternalAuthLink (ctx context.Context ,arg database.DeleteExternalAuthLinkParams )error {
14821525return fetchAndExec (q .log ,q .auth ,policy .ActionUpdatePersonal ,func (ctx context.Context ,arg database.DeleteExternalAuthLinkParams ) (database.ExternalAuthLink ,error ) {
14831526//nolint:gosimple
1484- return q .db .GetExternalAuthLink (ctx , database.GetExternalAuthLinkParams { UserID : arg . UserID , ProviderID : arg . ProviderID } )
1527+ return q .db .GetExternalAuthLink (ctx ,database .GetExternalAuthLinkParams ( arg ) )
14851528},q .db .DeleteExternalAuthLink )(ctx ,arg )
14861529}
14871530
@@ -1560,27 +1603,30 @@ func (q *querier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Contex
15601603return q .db .DeleteOAuth2ProviderAppTokensByAppAndUserID (ctx ,arg )
15611604}
15621605
1563- func (q * querier )DeleteOldAuditLogConnectionEvents (ctx context.Context ,threshold database.DeleteOldAuditLogConnectionEventsParams )error {
1564- // `ResourceSystem` is deprecated, but it doesn't make sense to add
1565- // `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1566- // only time we'll be deleting from the audit log.
1567- if err := q .authorizeContext (ctx ,policy .ActionDelete ,rbac .ResourceSystem );err != nil {
1568- return err
1569- }
1570- return q .db .DeleteOldAuditLogConnectionEvents (ctx ,threshold )
1571- }
1572-
15731606func (q * querier )DeleteOAuth2ProviderDeviceCodeByID (ctx context.Context ,id uuid.UUID )error {
15741607// Fetch the device code first to check authorization
15751608deviceCode ,err := q .db .GetOAuth2ProviderDeviceCodeByID (ctx ,id )
15761609if err != nil {
1577- return err
1610+ return xerrors . Errorf ( "get oauth2 provider device code: %w" , err )
15781611}
15791612if err := q .authorizeContext (ctx ,policy .ActionDelete ,deviceCode );err != nil {
1580- return err
1613+ return xerrors . Errorf ( "authorize oauth2 provider device code deletion: %w" , err )
15811614}
15821615
1583- return q .db .DeleteOAuth2ProviderDeviceCodeByID (ctx ,id )
1616+ if err := q .db .DeleteOAuth2ProviderDeviceCodeByID (ctx ,id );err != nil {
1617+ return xerrors .Errorf ("delete oauth2 provider device code: %w" ,err )
1618+ }
1619+ return nil
1620+ }
1621+
1622+ func (q * querier )DeleteOldAuditLogConnectionEvents (ctx context.Context ,threshold database.DeleteOldAuditLogConnectionEventsParams )error {
1623+ // `ResourceSystem` is deprecated, but it doesn't make sense to add
1624+ // `policy.ActionDelete` to `ResourceAuditLog`, since this is the one and
1625+ // only time we'll be deleting from the audit log.
1626+ if err := q .authorizeContext (ctx ,policy .ActionDelete ,rbac .ResourceSystem );err != nil {
1627+ return err
1628+ }
1629+ return q .db .DeleteOldAuditLogConnectionEvents (ctx ,threshold )
15841630}
15851631
15861632func (q * querier )DeleteOldNotificationMessages (ctx context.Context )error {
@@ -1612,7 +1658,7 @@ func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {
16121658}
16131659
16141660func (q * querier )DeleteOrganizationMember (ctx context.Context ,arg database.DeleteOrganizationMemberParams )error {
1615- return deleteQ [database. OrganizationMember ] (q .log ,q .auth ,func (ctx context.Context ,arg database.DeleteOrganizationMemberParams ) (database.OrganizationMember ,error ) {
1661+ return deleteQ (q .log ,q .auth ,func (ctx context.Context ,arg database.DeleteOrganizationMemberParams ) (database.OrganizationMember ,error ) {
16161662member ,err := database .ExpectOne (q .OrganizationMembers (ctx , database.OrganizationMembersParams {
16171663OrganizationID :arg .OrganizationID ,
16181664UserID :arg .UserID ,
@@ -2203,7 +2249,7 @@ func (q *querier) GetLicenseByID(ctx context.Context, id int32) (database.Licens
22032249}
22042250
22052251func (q * querier )GetLicenses (ctx context.Context ) ([]database.License ,error ) {
2206- fetch := func (ctx context.Context ,_ interface {} ) ([]database.License ,error ) {
2252+ fetch := func (ctx context.Context ,_ any ) ([]database.License ,error ) {
22072253return q .db .GetLicenses (ctx )
22082254}
22092255return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -2367,8 +2413,8 @@ func (q *querier) GetOAuth2ProviderDeviceCodeByUserCode(ctx context.Context, use
23672413}
23682414
23692415func (q * querier )GetOAuth2ProviderDeviceCodesByClientID (ctx context.Context ,clientID uuid.UUID ) ([]database.OAuth2ProviderDeviceCode ,error ) {
2370- // This requires access to readthe OAuth2 app
2371- if err := q .authorizeContext (ctx ,policy .ActionRead ,rbac .ResourceOauth2App );err != nil {
2416+ // This requires access to read OAuth2 app code tokens
2417+ if err := q .authorizeContext (ctx ,policy .ActionRead ,rbac .ResourceOauth2AppCodeToken );err != nil {
23722418return []database.OAuth2ProviderDeviceCode {},err
23732419}
23742420return q .db .GetOAuth2ProviderDeviceCodesByClientID (ctx ,clientID )
@@ -2425,7 +2471,7 @@ func (q *querier) GetOrganizationResourceCountByID(ctx context.Context, organiza
24252471}
24262472
24272473func (q * querier )GetOrganizations (ctx context.Context ,args database.GetOrganizationsParams ) ([]database.Organization ,error ) {
2428- fetch := func (ctx context.Context ,_ interface {} ) ([]database.Organization ,error ) {
2474+ fetch := func (ctx context.Context ,_ any ) ([]database.Organization ,error ) {
24292475return q .db .GetOrganizations (ctx ,args )
24302476}
24312477return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -2553,7 +2599,7 @@ func (q *querier) GetPreviousTemplateVersion(ctx context.Context, arg database.G
25532599}
25542600
25552601func (q * querier )GetProvisionerDaemons (ctx context.Context ) ([]database.ProvisionerDaemon ,error ) {
2556- fetch := func (ctx context.Context ,_ interface {} ) ([]database.ProvisionerDaemon ,error ) {
2602+ fetch := func (ctx context.Context ,_ any ) ([]database.ProvisionerDaemon ,error ) {
25572603return q .db .GetProvisionerDaemons (ctx )
25582604}
25592605return fetchWithPostFilter (q .auth ,policy .ActionRead ,fetch )(ctx ,nil )
@@ -3511,7 +3557,7 @@ func (q *querier) GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt
35113557}
35123558
35133559func (q * querier )GetWorkspaceProxies (ctx context.Context ) ([]database.WorkspaceProxy ,error ) {
3514- return fetchWithPostFilter (q .auth ,policy .ActionRead ,func (ctx context.Context ,_ interface {} ) ([]database.WorkspaceProxy ,error ) {
3560+ return fetchWithPostFilter (q .auth ,policy .ActionRead ,func (ctx context.Context ,_ any ) ([]database.WorkspaceProxy ,error ) {
35153561return q .db .GetWorkspaceProxies (ctx )
35163562})(ctx ,nil )
35173563}
@@ -3810,8 +3856,8 @@ func (q *querier) InsertOAuth2ProviderAppToken(ctx context.Context, arg database
38103856}
38113857
38123858func (q * querier )InsertOAuth2ProviderDeviceCode (ctx context.Context ,arg database.InsertOAuth2ProviderDeviceCodeParams ) (database.OAuth2ProviderDeviceCode ,error ) {
3813- // Creating device codes requires OAuth2 app access
3814- if err := q .authorizeContext (ctx ,policy .ActionCreate ,rbac .ResourceOauth2App );err != nil {
3859+ // Creating device codes requires OAuth2 appcode token creation access
3860+ if err := q .authorizeContext (ctx ,policy .ActionCreate ,rbac .ResourceOauth2AppCodeToken );err != nil {
38153861return database.OAuth2ProviderDeviceCode {},err
38163862}
38173863return q .db .InsertOAuth2ProviderDeviceCode (ctx ,arg )
@@ -4118,10 +4164,11 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
41184164return xerrors .Errorf ("get workspace by id: %w" ,err )
41194165}
41204166
4121- var action policy.Action = policy .ActionWorkspaceStart
4122- if arg .Transition == database .WorkspaceTransitionDelete {
4167+ action := policy .ActionWorkspaceStart
4168+ switch arg .Transition {
4169+ case database .WorkspaceTransitionDelete :
41234170action = policy .ActionDelete
4124- } else if arg . Transition == database .WorkspaceTransitionStop {
4171+ case database .WorkspaceTransitionStop :
41254172action = policy .ActionWorkspaceStop
41264173}
41274174
@@ -4490,13 +4537,10 @@ func (q *querier) UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg dat
44904537}
44914538
44924539func (q * querier )UpdateOAuth2ProviderDeviceCodeAuthorization (ctx context.Context ,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4493- // Verify the user is authenticated for device code authorization
4494- _ ,ok := ActorFromContext (ctx )
4495- if ! ok {
4496- return database.OAuth2ProviderDeviceCode {},ErrNoActor
4540+ fetch := func (ctx context.Context ,arg database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams ) (database.OAuth2ProviderDeviceCode ,error ) {
4541+ return q .db .GetOAuth2ProviderDeviceCodeByID (ctx ,arg .ID )
44974542}
4498-
4499- return q .db .UpdateOAuth2ProviderDeviceCodeAuthorization (ctx ,arg )
4543+ return updateWithReturn (q .log ,q .auth ,fetch ,q .db .UpdateOAuth2ProviderDeviceCodeAuthorization )(ctx ,arg )
45004544}
45014545
45024546func (q * querier )UpdateOrganization (ctx context.Context ,arg database.UpdateOrganizationParams ) (database.Organization ,error ) {