From 9fd95a0bf99edbc46a2d78d49d5e48bf43d31a2b Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Fri, 19 Jun 2026 18:49:33 +0200 Subject: [PATCH] Fix missing cmid scope Signed-off-by: Bryan Frimin --- pkg/accessreview/oauth2_scopes.go | 63 +- pkg/agentrun/oauth2_scopes.go | 26 +- pkg/iam/authorizer.go | 21 +- pkg/iam/authorizer_batch_test.go | 11 +- pkg/iam/authorizer_decisionlog_test.go | 3 +- pkg/iam/authorizer_oauth2_scope_test.go | 14 +- pkg/iam/oauth2/cimd.go | 10 +- pkg/iam/oauth2/service.go | 11 +- pkg/iam/oauth2_scope_registrations_test.go | 26 +- pkg/iam/oauth2_scopes.go | 125 ++- pkg/iam/scope_set_test.go | 54 +- .../{scope_set.go => scopeset/scopeset.go} | 54 +- pkg/iam/scopeset/scopeset_test.go | 63 ++ pkg/iam/service.go | 13 +- pkg/probo/oauth2_scopes.go | 753 +++++++++--------- pkg/probo/service.go | 2 +- pkg/probod/probod.go | 4 +- pkg/server/api/connect/v1/base_resolvers.go | 2 +- .../v1/oauth2_access_token_resolvers.go | 2 +- 19 files changed, 611 insertions(+), 646 deletions(-) rename pkg/iam/{scope_set.go => scopeset/scopeset.go} (60%) create mode 100644 pkg/iam/scopeset/scopeset_test.go diff --git a/pkg/accessreview/oauth2_scopes.go b/pkg/accessreview/oauth2_scopes.go index de872ea11..13f531803 100644 --- a/pkg/accessreview/oauth2_scopes.go +++ b/pkg/accessreview/oauth2_scopes.go @@ -14,45 +14,38 @@ package accessreview -import ( - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/iam" -) +import "go.probo.inc/probo/pkg/coredata" const ( ScopeV1AccessReviewRead coredata.OAuth2Scope = "v1:access-review:read" ScopeV1AccessReview coredata.OAuth2Scope = "v1:access-review" ) -// OAuth2ScopeSet returns OAuth2 scope mappings for access-review actions. -func OAuth2ScopeSet() *iam.ScopeSet { - return iam.CreateScopeSet( - map[coredata.OAuth2Scope][]iam.Action{ - ScopeV1AccessReviewRead: { - ActionCampaignGet, - ActionCampaignList, - ActionEntryGet, - ActionEntryList, - ActionSourceGet, - ActionSourceList, - ActionDriverCatalogList, - }, - ScopeV1AccessReview: { - ActionCampaignCreate, - ActionCampaignUpdate, - ActionCampaignDelete, - ActionCampaignStart, - ActionCampaignClose, - ActionCampaignCancel, - ActionCampaignAddSource, - ActionCampaignRemoveSource, - ActionEntryDecide, - ActionEntryFlag, - ActionSourceCreate, - ActionSourceUpdate, - ActionSourceDelete, - ActionSourceSync, - }, - }, - ) +// OAuth2ScopeMappings maps OAuth2 scopes to access-review actions. +var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ + ScopeV1AccessReviewRead: { + ActionCampaignGet, + ActionCampaignList, + ActionEntryGet, + ActionEntryList, + ActionSourceGet, + ActionSourceList, + ActionDriverCatalogList, + }, + ScopeV1AccessReview: { + ActionCampaignCreate, + ActionCampaignUpdate, + ActionCampaignDelete, + ActionCampaignStart, + ActionCampaignClose, + ActionCampaignCancel, + ActionCampaignAddSource, + ActionCampaignRemoveSource, + ActionEntryDecide, + ActionEntryFlag, + ActionSourceCreate, + ActionSourceUpdate, + ActionSourceDelete, + ActionSourceSync, + }, } diff --git a/pkg/agentrun/oauth2_scopes.go b/pkg/agentrun/oauth2_scopes.go index 78c89fff7..d83baaabc 100644 --- a/pkg/agentrun/oauth2_scopes.go +++ b/pkg/agentrun/oauth2_scopes.go @@ -14,27 +14,19 @@ package agentrun -import ( - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/iam" -) +import "go.probo.inc/probo/pkg/coredata" const ( ScopeV1AgentRead coredata.OAuth2Scope = "v1:agent:read" ScopeV1Agent coredata.OAuth2Scope = "v1:agent" ) -// OAuth2ScopeSet returns OAuth2 scope mappings for agent-run actions. -func OAuth2ScopeSet() *iam.ScopeSet { - return iam.CreateScopeSet( - map[coredata.OAuth2Scope][]iam.Action{ - ScopeV1AgentRead: { - ActionAgentRunGet, - ActionAgentRunList, - }, - ScopeV1Agent: { - ActionAgentRunApprove, - }, - }, - ) +var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ + ScopeV1AgentRead: { + ActionAgentRunGet, + ActionAgentRunList, + }, + ScopeV1Agent: { + ActionAgentRunApprove, + }, } diff --git a/pkg/iam/authorizer.go b/pkg/iam/authorizer.go index 1c408178f..e68075b20 100644 --- a/pkg/iam/authorizer.go +++ b/pkg/iam/authorizer.go @@ -29,6 +29,7 @@ import ( "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam/oauth2" "go.probo.inc/probo/pkg/iam/policy" + "go.probo.inc/probo/pkg/iam/scopeset" ) // AuthorizationAttributer is implemented by entities that can provide @@ -86,17 +87,17 @@ type Authorizer struct { pg *pg.Client evaluator *policy.Evaluator policySet *PolicySet - oauth2ScopeSet *ScopeSet + oauth2ScopeSet *scopeset.ScopeSet logger *log.Logger } // NewAuthorizer creates a new Authorizer instance. -func NewAuthorizer(pgClient *pg.Client, logger *log.Logger) *Authorizer { +func NewAuthorizer(pgClient *pg.Client, logger *log.Logger, scopeSet *scopeset.ScopeSet) *Authorizer { return &Authorizer{ pg: pgClient, evaluator: policy.NewEvaluator(), policySet: NewPolicySet(), - oauth2ScopeSet: NewScopeSet(), + oauth2ScopeSet: scopeSet, logger: logger, } } @@ -106,20 +107,6 @@ func (a *Authorizer) RegisterPolicySet(ps *PolicySet) { a.policySet.Merge(ps) } -// RegisterScopes merges OAuth2 scope-to-action mappings into the authorizer. -func (a *Authorizer) RegisterScopes(ss *ScopeSet) { - a.oauth2ScopeSet.Merge(ss) -} - -// APIScopes returns OAuth2 API scopes advertised in discovery metadata. -func (a *Authorizer) APIScopes() []coredata.OAuth2Scope { - if a.oauth2ScopeSet == nil { - return []coredata.OAuth2Scope{} - } - - return a.oauth2ScopeSet.APIScopes() -} - func (a *Authorizer) checkOAuth2Scope( ctx context.Context, principal gid.GID, diff --git a/pkg/iam/authorizer_batch_test.go b/pkg/iam/authorizer_batch_test.go index deb9c32fd..40e5e3a86 100644 --- a/pkg/iam/authorizer_batch_test.go +++ b/pkg/iam/authorizer_batch_test.go @@ -32,6 +32,7 @@ import ( "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam/policy" + "go.probo.inc/probo/pkg/iam/scopeset" "go.probo.inc/probo/pkg/mail" ) @@ -216,7 +217,7 @@ func TestAuthorizer_AuthorizeBatch(t *testing.T) { t.Run("empty input", func(t *testing.T) { t.Parallel() - authorizer := iam.NewAuthorizer(nil, log.NewLogger(log.WithOutput(io.Discard))) + authorizer := iam.NewAuthorizer(nil, log.NewLogger(log.WithOutput(io.Discard)), scopeset.New()) _, err := authorizer.AuthorizeBatch( context.Background(), @@ -644,7 +645,7 @@ func TestAuthorizer_AuthorizeMulti(t *testing.T) { t.Run("rejects empty items", func(t *testing.T) { t.Parallel() - authorizer := iam.NewAuthorizer(nil, log.NewLogger(log.WithOutput(io.Discard))) + authorizer := iam.NewAuthorizer(nil, log.NewLogger(log.WithOutput(io.Discard)), scopeset.New()) scope, decisions, err := authorizer.AuthorizeMulti( context.Background(), @@ -663,7 +664,7 @@ func TestAuthorizer_AuthorizeMulti(t *testing.T) { t.Run("rejects unsupported principal type", func(t *testing.T) { t.Parallel() - authorizer := iam.NewAuthorizer(nil, log.NewLogger(log.WithOutput(io.Discard))) + authorizer := iam.NewAuthorizer(nil, log.NewLogger(log.WithOutput(io.Discard)), scopeset.New()) scope, decisions, err := authorizer.AuthorizeMulti( context.Background(), @@ -782,7 +783,7 @@ func newTestAuthorizer(client *pg.Client, action string, allowResourceID *gid.GI } func newTestAuthorizerWithStatements(client *pg.Client, statements ...policy.Statement) *iam.Authorizer { - authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(io.Discard))) + authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(io.Discard)), scopeset.New()) authorizer.RegisterPolicySet( iam.NewPolicySet().AddRolePolicy( string(coredata.MembershipRoleOwner), @@ -794,7 +795,7 @@ func newTestAuthorizerWithStatements(client *pg.Client, statements ...policy.Sta } func newTestAuthorizerWithIdentityScopedStatements(client *pg.Client, statements ...policy.Statement) *iam.Authorizer { - authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(io.Discard))) + authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(io.Discard)), scopeset.New()) authorizer.RegisterPolicySet( iam.NewPolicySet().AddIdentityScopedPolicy( policy.NewPolicy("batch-authorize-identity-test", "Batch Authorize Identity Test", statements...), diff --git a/pkg/iam/authorizer_decisionlog_test.go b/pkg/iam/authorizer_decisionlog_test.go index 0d896574e..640c8e2c8 100644 --- a/pkg/iam/authorizer_decisionlog_test.go +++ b/pkg/iam/authorizer_decisionlog_test.go @@ -30,6 +30,7 @@ import ( "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam/policy" + "go.probo.inc/probo/pkg/iam/scopeset" ) func TestAuthorizer_DecisionLogging(t *testing.T) { @@ -151,7 +152,7 @@ func newTestAuthorizerWithLogger( statements = append(statements, extraStatements...) - authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(logOutput))) + authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(logOutput)), scopeset.New()) authorizer.RegisterPolicySet( iam.NewPolicySet().AddRolePolicy( string(coredata.MembershipRoleOwner), diff --git a/pkg/iam/authorizer_oauth2_scope_test.go b/pkg/iam/authorizer_oauth2_scope_test.go index f3d8d4565..b5a5556ef 100644 --- a/pkg/iam/authorizer_oauth2_scope_test.go +++ b/pkg/iam/authorizer_oauth2_scope_test.go @@ -24,6 +24,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam/oauth2" + "go.probo.inc/probo/pkg/iam/scopeset" ) func TestAuthorizer_checkOAuth2Scope(t *testing.T) { @@ -65,15 +66,14 @@ func TestAuthorizer_checkOAuth2Scope(t *testing.T) { t.Run("allows when registered scopes authorize the action", func(t *testing.T) { t.Parallel() - a := NewAuthorizer(nil, nil) - a.RegisterScopes( - CreateScopeSet( - map[coredata.OAuth2Scope][]Action{ - scopeV1OrgRead: {action}, - }, - ), + scopeSet := scopeset.New().Register( + map[coredata.OAuth2Scope][]string{ + scopeV1OrgRead: {action}, + }, ) + a := NewAuthorizer(nil, nil, scopeSet) + ctx := oauth2.ContextWithAccessToken( context.Background(), &coredata.OAuth2AccessToken{Scopes: coredata.OAuth2Scopes{scopeV1OrgRead}}, diff --git a/pkg/iam/oauth2/cimd.go b/pkg/iam/oauth2/cimd.go index 02e449fc1..1e6965bb2 100644 --- a/pkg/iam/oauth2/cimd.go +++ b/pkg/iam/oauth2/cimd.go @@ -334,14 +334,6 @@ func (f *cimdFetcher) storeCache(clientIDURL string, doc *ClientMetadataDocument ) } -func (s *Service) ResolveClient( - ctx context.Context, - clientIDRaw string, - redirectURI string, -) (*coredata.OAuth2Client, error) { - return s.resolveClient(ctx, nil, clientIDRaw, redirectURI) -} - func (s *Service) resolveClient( ctx context.Context, tx pg.Tx, @@ -415,7 +407,7 @@ func (s *Service) upsertCIMDClient( ScopeEmail, ScopeOfflineAccess, }, - s.apiScopes, + s.scopeSet.APIScopes(), ) now := time.Now() diff --git a/pkg/iam/oauth2/service.go b/pkg/iam/oauth2/service.go index 8ecfb8c64..6d0325f7c 100644 --- a/pkg/iam/oauth2/service.go +++ b/pkg/iam/oauth2/service.go @@ -33,6 +33,7 @@ import ( "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/net" "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/iam/scopeset" "go.probo.inc/probo/pkg/uri" ) @@ -62,7 +63,7 @@ type ( gc *GarbageCollector cimd *cimdFetcher cimdAllowedClientIDs []string - apiScopes []coredata.OAuth2Scope + scopeSet *scopeset.ScopeSet accessTokenDuration time.Duration refreshTokenDuration time.Duration authorizationCodeDuration time.Duration @@ -159,9 +160,9 @@ func WithDeviceCodeDuration(d time.Duration) Option { } } -func WithAPIScopes(scopes []coredata.OAuth2Scope) Option { +func WithScopeSet(scopeSet *scopeset.ScopeSet) Option { return func(s *Service) { - s.apiScopes = scopes + s.scopeSet = scopeSet } } @@ -1438,6 +1439,8 @@ func (s *Service) Authorize( return err } + fmt.Printf("X: %+v\n", client) + if !client.IsRedirectURIAllowed(req.RedirectURI) { return ErrInvalidRedirectURI } @@ -1736,7 +1739,7 @@ func (s *Service) AuthenticateClient( clientIDRaw string, clientSecret string, ) (*coredata.OAuth2Client, error) { - client, err := s.ResolveClient(ctx, clientIDRaw, "") + client, err := s.resolveClient(ctx, nil, clientIDRaw, "") if err != nil { return nil, err } diff --git a/pkg/iam/oauth2_scope_registrations_test.go b/pkg/iam/oauth2_scope_registrations_test.go index fb07e251d..43ce1cee7 100644 --- a/pkg/iam/oauth2_scope_registrations_test.go +++ b/pkg/iam/oauth2_scope_registrations_test.go @@ -22,30 +22,21 @@ import ( "go.probo.inc/probo/pkg/agentrun" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/iam/scopeset" "go.probo.inc/probo/pkg/probo" ) -func allRegisteredOAuth2ScopeSets() *iam.ScopeSet { - return iam.NewScopeSet(). - Merge(iam.IAMOAuth2ScopeSet()). - Merge(probo.OAuth2ScopeSet()). - Merge(accessreview.OAuth2ScopeSet()). - Merge(agentrun.OAuth2ScopeSet()) -} - -func registerAllOAuth2ScopeSets(authorizer *iam.Authorizer) { - authorizer.RegisterScopes(iam.IAMOAuth2ScopeSet()) - authorizer.RegisterScopes(probo.OAuth2ScopeSet()) - authorizer.RegisterScopes(accessreview.OAuth2ScopeSet()) - authorizer.RegisterScopes(agentrun.OAuth2ScopeSet()) +func allRegisteredOAuth2ScopeSets() *scopeset.ScopeSet { + return scopeset.New(). + Register(iam.IAMOAuth2ScopeMappings). + Register(probo.OAuth2ScopeMappings). + Register(accessreview.OAuth2ScopeMappings). + Register(agentrun.OAuth2ScopeMappings) } func TestRegisteredOAuth2ScopeSets_OrganizationRead(t *testing.T) { t.Parallel() - authorizer := iam.NewAuthorizer(nil, nil) - registerAllOAuth2ScopeSets(authorizer) - scopeSet := allRegisteredOAuth2ScopeSets() tokenScopes := coredata.OAuth2Scopes{probo.ScopeV1OrgRead} @@ -57,9 +48,6 @@ func TestRegisteredOAuth2ScopeSets_OrganizationRead(t *testing.T) { func TestRegisteredOAuth2ScopeSets_UnmappedActionDenies(t *testing.T) { t.Parallel() - authorizer := iam.NewAuthorizer(nil, nil) - registerAllOAuth2ScopeSets(authorizer) - scopeSet := allRegisteredOAuth2ScopeSets() tokenScopes := coredata.OAuth2Scopes{ probo.ScopeV1OrgRead, diff --git a/pkg/iam/oauth2_scopes.go b/pkg/iam/oauth2_scopes.go index 7f68bab77..3ade54827 100644 --- a/pkg/iam/oauth2_scopes.go +++ b/pkg/iam/oauth2_scopes.go @@ -21,69 +21,64 @@ const ( ScopeV1IAM coredata.OAuth2Scope = "v1:iam" ) -// IAMOAuth2ScopeSet returns OAuth2 scope mappings for IAM actions. -func IAMOAuth2ScopeSet() *ScopeSet { - return CreateScopeSet( - map[coredata.OAuth2Scope][]Action{ - ScopeV1IAMRead: { - ActionOrganizationGet, - ActionOrganizationList, - ActionIdentityGet, - ActionSessionList, - ActionSessionGet, - ActionInvitationList, - ActionInvitationGet, - ActionMembershipGet, - ActionMembershipList, - ActionMembershipProfileGet, - ActionMembershipProfileList, - ActionPersonalAPIKeyGet, - ActionPersonalAPIKeyList, - ActionSAMLConfigurationGet, - ActionSAMLConfigurationList, - ActionSCIMConfigurationGet, - ActionSCIMEventList, - ActionSCIMEventGet, - ActionSCIMBridgeGet, - ActionOAuth2ConsentGet, - ActionAuditLogEntryGet, - ActionAuditLogEntryList, - ActionOAuth2AccessTokenGet, - ActionOAuth2AccessTokenList, - }, - ScopeV1IAM: { - ActionOrganizationCreate, - ActionOrganizationUpdate, - ActionOrganizationDelete, - ActionIdentityUpdate, - ActionIdentityDelete, - ActionSessionRevoke, - ActionSessionRevokeAll, - ActionInvitationCreate, - ActionInvitationAccept, - ActionInvitationDelete, - ActionMembershipUpdate, - ActionMembershipDelete, - ActionMembershipRoleSetOwner, - ActionMembershipProfileCreate, - ActionMembershipProfileUpdate, - ActionMembershipProfileDelete, - ActionMembershipProfileActivate, - ActionMembershipProfileDeactivate, - ActionPersonalAPIKeyCreate, - ActionPersonalAPIKeyUpdate, - ActionPersonalAPIKeyDelete, - ActionSAMLConfigurationCreate, - ActionSAMLConfigurationUpdate, - ActionSAMLConfigurationDelete, - ActionSCIMConfigurationCreate, - ActionSCIMConfigurationUpdate, - ActionSCIMConfigurationDelete, - ActionSCIMBridgeCreate, - ActionSCIMBridgeUpdate, - ActionSCIMBridgeDelete, - ActionOAuth2ConsentApprove, - }, - }, - ) +var IAMOAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ + ScopeV1IAMRead: { + ActionOrganizationGet, + ActionOrganizationList, + ActionIdentityGet, + ActionSessionList, + ActionSessionGet, + ActionInvitationList, + ActionInvitationGet, + ActionMembershipGet, + ActionMembershipList, + ActionMembershipProfileGet, + ActionMembershipProfileList, + ActionPersonalAPIKeyGet, + ActionPersonalAPIKeyList, + ActionSAMLConfigurationGet, + ActionSAMLConfigurationList, + ActionSCIMConfigurationGet, + ActionSCIMEventList, + ActionSCIMEventGet, + ActionSCIMBridgeGet, + ActionOAuth2ConsentGet, + ActionAuditLogEntryGet, + ActionAuditLogEntryList, + ActionOAuth2AccessTokenGet, + ActionOAuth2AccessTokenList, + }, + ScopeV1IAM: { + ActionOrganizationCreate, + ActionOrganizationUpdate, + ActionOrganizationDelete, + ActionIdentityUpdate, + ActionIdentityDelete, + ActionSessionRevoke, + ActionSessionRevokeAll, + ActionInvitationCreate, + ActionInvitationAccept, + ActionInvitationDelete, + ActionMembershipUpdate, + ActionMembershipDelete, + ActionMembershipRoleSetOwner, + ActionMembershipProfileCreate, + ActionMembershipProfileUpdate, + ActionMembershipProfileDelete, + ActionMembershipProfileActivate, + ActionMembershipProfileDeactivate, + ActionPersonalAPIKeyCreate, + ActionPersonalAPIKeyUpdate, + ActionPersonalAPIKeyDelete, + ActionSAMLConfigurationCreate, + ActionSAMLConfigurationUpdate, + ActionSAMLConfigurationDelete, + ActionSCIMConfigurationCreate, + ActionSCIMConfigurationUpdate, + ActionSCIMConfigurationDelete, + ActionSCIMBridgeCreate, + ActionSCIMBridgeUpdate, + ActionSCIMBridgeDelete, + ActionOAuth2ConsentApprove, + }, } diff --git a/pkg/iam/scope_set_test.go b/pkg/iam/scope_set_test.go index 3ea8ead45..1a9595dcd 100644 --- a/pkg/iam/scope_set_test.go +++ b/pkg/iam/scope_set_test.go @@ -20,65 +20,21 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam/scopeset" ) -func TestScopeSet_Allows(t *testing.T) { +func TestAuthorizer_UsesOAuth2ScopeSet(t *testing.T) { t.Parallel() const scopeV1OrgRead = coredata.OAuth2Scope("v1:org:read") - scopeSet := CreateScopeSet( - map[coredata.OAuth2Scope][]Action{ + scopeSet := scopeset.New().Register( + map[coredata.OAuth2Scope][]string{ scopeV1OrgRead: {"core:organization:get"}, }, ) - tokenScopes := coredata.OAuth2Scopes{scopeV1OrgRead} - - assert.True(t, scopeSet.Allows(tokenScopes, "core:organization:get")) - assert.False(t, scopeSet.Allows(tokenScopes, "core:organization:update")) -} - -func TestScopeSet_Merge(t *testing.T) { - t.Parallel() - - const scopeV1OrgRead = coredata.OAuth2Scope("v1:org:read") - - scopeSet := NewScopeSet(). - Merge( - CreateScopeSet( - map[coredata.OAuth2Scope][]Action{ - scopeV1OrgRead: {"core:organization:get"}, - }, - ), - ). - Merge( - CreateScopeSet( - map[coredata.OAuth2Scope][]Action{ - scopeV1OrgRead: {"core:organization-context:get"}, - }, - ), - ) - - tokenScopes := coredata.OAuth2Scopes{scopeV1OrgRead} - - assert.True(t, scopeSet.Allows(tokenScopes, "core:organization:get")) - assert.True(t, scopeSet.Allows(tokenScopes, "core:organization-context:get")) -} - -func TestAuthorizer_RegisterScopes(t *testing.T) { - t.Parallel() - - const scopeV1OrgRead = coredata.OAuth2Scope("v1:org:read") - - authorizer := NewAuthorizer(nil, nil) - authorizer.RegisterScopes( - CreateScopeSet( - map[coredata.OAuth2Scope][]Action{ - scopeV1OrgRead: {"core:organization:get"}, - }, - ), - ) + authorizer := NewAuthorizer(nil, nil, scopeSet) require.NotNil(t, authorizer.oauth2ScopeSet) diff --git a/pkg/iam/scope_set.go b/pkg/iam/scopeset/scopeset.go similarity index 60% rename from pkg/iam/scope_set.go rename to pkg/iam/scopeset/scopeset.go index e950ef58b..10cbc5788 100644 --- a/pkg/iam/scope_set.go +++ b/pkg/iam/scopeset/scopeset.go @@ -12,34 +12,32 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -package iam +package scopeset import ( "cmp" "maps" "slices" + "sync" "go.probo.inc/probo/pkg/coredata" ) -// ScopeSet holds OAuth2 scope to IAM action mappings. Services create their -// own ScopeSet and register it on the Authorizer at composition time. type ScopeSet struct { - scopeActions map[coredata.OAuth2Scope][]Action - actionScopes map[Action][]coredata.OAuth2Scope + mu sync.RWMutex + scopeActions map[coredata.OAuth2Scope][]string + actionScopes map[string][]coredata.OAuth2Scope } -// NewScopeSet creates an empty ScopeSet. -func NewScopeSet() *ScopeSet { +func New() *ScopeSet { return &ScopeSet{ - scopeActions: make(map[coredata.OAuth2Scope][]Action), + scopeActions: make(map[coredata.OAuth2Scope][]string), } } -// CreateScopeSet creates a ScopeSet from scope-to-action mappings. Entries with -// no actions are skipped. -func CreateScopeSet(mappings map[coredata.OAuth2Scope][]Action) *ScopeSet { - s := NewScopeSet() +func (s *ScopeSet) Register(mappings map[coredata.OAuth2Scope][]string) *ScopeSet { + s.mu.Lock() + defer s.mu.Unlock() for scope, actions := range mappings { if len(actions) == 0 { @@ -54,24 +52,17 @@ func CreateScopeSet(mappings map[coredata.OAuth2Scope][]Action) *ScopeSet { return s } -// Merge combines another ScopeSet into this one. -func (s *ScopeSet) Merge(other *ScopeSet) *ScopeSet { - for scope, actions := range other.scopeActions { - s.scopeActions[scope] = append(s.scopeActions[scope], actions...) - } - - s.rebuildActionScopes() - - return s -} - -// APIScopes returns every registered OAuth2 API scope in this set. func (s *ScopeSet) APIScopes() []coredata.OAuth2Scope { + s.mu.RLock() + defer s.mu.RUnlock() + return sortedScopes(slices.Collect(maps.Keys(s.scopeActions))) } -// Allows reports whether tokenScopes authorize action. -func (s *ScopeSet) Allows(tokenScopes coredata.OAuth2Scopes, action Action) bool { +func (s *ScopeSet) Allows(tokenScopes coredata.OAuth2Scopes, action string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + grantingScopes, ok := s.actionScopes[action] if !ok { return false @@ -81,7 +72,7 @@ func (s *ScopeSet) Allows(tokenScopes coredata.OAuth2Scopes, action Action) bool } func (s *ScopeSet) rebuildActionScopes() { - actionScopes := make(map[Action][]coredata.OAuth2Scope, len(s.scopeActions)*4) + actionScopes := make(map[string][]coredata.OAuth2Scope, len(s.scopeActions)*4) for scope, actions := range s.scopeActions { for _, action := range actions { @@ -94,9 +85,12 @@ func (s *ScopeSet) rebuildActionScopes() { func sortedScopes(scopes []coredata.OAuth2Scope) []coredata.OAuth2Scope { sorted := slices.Clone(scopes) - slices.SortFunc(sorted, func(a, b coredata.OAuth2Scope) int { - return cmp.Compare(string(a), string(b)) - }) + slices.SortFunc( + sorted, + func(a, b coredata.OAuth2Scope) int { + return cmp.Compare(string(a), string(b)) + }, + ) return sorted } diff --git a/pkg/iam/scopeset/scopeset_test.go b/pkg/iam/scopeset/scopeset_test.go new file mode 100644 index 000000000..c489707af --- /dev/null +++ b/pkg/iam/scopeset/scopeset_test.go @@ -0,0 +1,63 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package scopeset_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam/scopeset" +) + +func TestScopeSet_Allows(t *testing.T) { + t.Parallel() + + const scopeV1OrgRead = coredata.OAuth2Scope("v1:org:read") + + scopeSet := scopeset.New().Register( + map[coredata.OAuth2Scope][]string{ + scopeV1OrgRead: {"core:organization:get"}, + }, + ) + + tokenScopes := coredata.OAuth2Scopes{scopeV1OrgRead} + + assert.True(t, scopeSet.Allows(tokenScopes, "core:organization:get")) + assert.False(t, scopeSet.Allows(tokenScopes, "core:organization:update")) +} + +func TestScopeSet_Register(t *testing.T) { + t.Parallel() + + const scopeV1OrgRead = coredata.OAuth2Scope("v1:org:read") + + scopeSet := scopeset.New(). + Register( + map[coredata.OAuth2Scope][]string{ + scopeV1OrgRead: {"core:organization:get"}, + }, + ). + Register( + map[coredata.OAuth2Scope][]string{ + scopeV1OrgRead: {"core:organization-context:get"}, + }, + ) + + tokenScopes := coredata.OAuth2Scopes{scopeV1OrgRead} + + assert.True(t, scopeSet.Allows(tokenScopes, "core:organization:get")) + assert.True(t, scopeSet.Allows(tokenScopes, "core:organization-context:get")) +} diff --git a/pkg/iam/service.go b/pkg/iam/service.go index ba3d6113b..869189b81 100644 --- a/pkg/iam/service.go +++ b/pkg/iam/service.go @@ -37,6 +37,7 @@ import ( "go.probo.inc/probo/pkg/iam/oidc" "go.probo.inc/probo/pkg/iam/saml" "go.probo.inc/probo/pkg/iam/scim" + "go.probo.inc/probo/pkg/iam/scopeset" "go.probo.inc/probo/pkg/uri" ) @@ -69,6 +70,7 @@ type ( APIKeyService *APIKeyService OAuth2ServerService *oauth2.Service Authorizer *Authorizer + OAuth2ScopeSet *scopeset.ScopeSet samlDomainVerifier *SAMLDomainVerifier } @@ -157,12 +159,15 @@ func NewService( svc.AuthService = NewAuthService(svc) svc.APIKeyService = NewAPIKeyService(svc) + svc.OAuth2ScopeSet = scopeset.New() + svc.OAuth2ScopeSet.Register(IAMOAuth2ScopeMappings) + svc.Authorizer = NewAuthorizer( pgClient, cfg.Logger.Named("authorizer"), + svc.OAuth2ScopeSet, ) svc.Authorizer.RegisterPolicySet(IAMPolicySet()) - svc.Authorizer.RegisterScopes(IAMOAuth2ScopeSet()) samlService, err := saml.NewService(svc.pg, svc.baseURL, svc.certificate, svc.privateKey, cfg.Logger) if err != nil { @@ -201,7 +206,7 @@ func NewService( uri.URI(cfg.BaseURL.String()), cfg.Logger.Named("oauth2"), append( - []oauth2.Option{oauth2.WithAPIScopes(svc.Authorizer.APIScopes())}, + []oauth2.Option{oauth2.WithScopeSet(svc.OAuth2ScopeSet)}, cfg.OAuth2ServerOptions..., )..., ) @@ -219,12 +224,12 @@ func NewService( // OAuth2ServerMetadata returns the OIDC discovery document. func (s *Service) OAuth2ServerMetadata(endpoints oauth2.Endpoints) *oauth2.ServerMetadata { - return oauth2.NewMetadata(uri.URI(s.baseURL), endpoints, s.Authorizer.APIScopes()) + return oauth2.NewMetadata(uri.URI(s.baseURL), endpoints, s.OAuth2ScopeSet.APIScopes()) } // OAuth2ProtectedResourceMetadata returns the RFC 9728 protected resource metadata document. func (s *Service) OAuth2ProtectedResourceMetadata(resource uri.URI) *oauth2.ProtectedResourceMetadata { - return oauth2.NewProtectedResourceMetadata(resource, resource, s.Authorizer.APIScopes()) + return oauth2.NewProtectedResourceMetadata(resource, resource, s.OAuth2ScopeSet.APIScopes()) } func (s *Service) IsSignUpEnabled() bool { diff --git a/pkg/probo/oauth2_scopes.go b/pkg/probo/oauth2_scopes.go index 7805b5b01..ea32a783d 100644 --- a/pkg/probo/oauth2_scopes.go +++ b/pkg/probo/oauth2_scopes.go @@ -16,7 +16,6 @@ package probo import ( "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/iam" ) const ( @@ -66,383 +65,379 @@ const ( ScopeV1Webhook coredata.OAuth2Scope = "v1:webhook" ) -// OAuth2ScopeSet returns OAuth2 scope mappings for core probo actions. -func OAuth2ScopeSet() *iam.ScopeSet { - return iam.CreateScopeSet( - map[coredata.OAuth2Scope][]iam.Action{ +// OAuth2ScopeMappings maps OAuth2 scopes to core probo actions. +var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ - ScopeV1AssetRead: { - ActionAssetGet, - ActionAssetList, - }, - ScopeV1Asset: { - ActionAssetCreate, - ActionAssetUpdate, - ActionAssetDelete, - ActionAssetPublish, - }, - ScopeV1AuditRead: { - ActionAuditGet, - ActionAuditList, - ActionFindingGet, - ActionFindingList, - ActionReportGet, - ActionReportGetReportUrl, - ActionReportDownloadUrlGet, - }, - ScopeV1Audit: { - ActionAuditCreate, - ActionAuditUpdate, - ActionAuditDelete, - ActionAuditReportUpload, - ActionAuditReportDelete, - ActionFindingCreate, - ActionFindingUpdate, - ActionFindingDelete, - ActionFindingAuditMappingCreate, - ActionFindingAuditMappingDelete, - ActionFindingPublish, - }, - ScopeV1CommonThirdPartyRead: { - ActionCommonThirdPartyGet, - ActionCommonThirdPartyList, - }, - ScopeV1CompliancePageRead: { - ActionTrustCenterGet, - ActionTrustCenterGetNda, - ActionTrustCenterAccessGet, - ActionTrustCenterAccessList, - ActionTrustCenterFileGet, - ActionTrustCenterFileList, - ActionTrustCenterFileGetFileUrl, - ActionTrustCenterReferenceList, - ActionTrustCenterReferenceGetLogoUrl, - ActionTrustCenterDocumentAccessList, - ActionMailingListUpdateList, - ActionMailingListSubscriberList, - ActionComplianceFrameworkList, - ActionComplianceExternalURLList, - ActionCustomDomainGet, - }, - ScopeV1CompliancePage: { - ActionTrustCenterUpdate, - ActionTrustCenterNonDisclosureAgreementUpload, - ActionTrustCenterNonDisclosureAgreementDelete, - ActionTrustCenterAccessCreate, - ActionTrustCenterAccessUpdate, - ActionTrustCenterAccessDelete, - ActionTrustCenterFileUpdate, - ActionTrustCenterFileDelete, - ActionTrustCenterFileCreate, - ActionTrustCenterReferenceCreate, - ActionTrustCenterReferenceUpdate, - ActionTrustCenterReferenceDelete, - ActionMailingListUpdateCreate, - ActionMailingListUpdateUpdate, - ActionMailingListUpdateSend, - ActionMailingListUpdateDelete, - ActionMailingListUpdate, - ActionMailingListSubscriberCreate, - ActionMailingListSubscriberDelete, - ActionComplianceFrameworkCreate, - ActionComplianceFrameworkDelete, - ActionComplianceFrameworkUpdateRank, - ActionComplianceExternalURLCreate, - ActionComplianceExternalURLUpdate, - ActionComplianceExternalURLDelete, - ActionCustomDomainCreate, - ActionCustomDomainDelete, - }, - ScopeV1ConnectorRead: { - ActionConnectorList, - ActionConnectorGet, - }, - ScopeV1Connector: { - ActionConnectorCreate, - ActionConnectorDelete, - ActionConnectorInitiate, - }, - ScopeV1ControlRead: { - ActionControlGet, - ActionControlList, - ActionMeasureGet, - ActionMeasureList, - ActionFrameworkGet, - ActionFrameworkList, - ActionFrameworkExport, - ActionObligationGet, - ActionObligationList, - ActionStatementOfApplicabilityList, - ActionStatementOfApplicabilityGet, - ActionApplicabilityStatementGet, - ActionApplicabilityStatementList, - }, - ScopeV1Control: { - ActionControlCreate, - ActionControlUpdate, - ActionControlDelete, - ActionControlMeasureMappingCreate, - ActionControlMeasureMappingDelete, - ActionControlDocumentMappingCreate, - ActionControlDocumentMappingDelete, - ActionControlAuditMappingCreate, - ActionControlAuditMappingDelete, - ActionControlObligationMappingCreate, - ActionControlObligationMappingDelete, - ActionMeasureCreate, - ActionMeasureUpdate, - ActionMeasureDelete, - ActionMeasureEvidenceUpload, - ActionMeasureImport, - ActionMeasureDocumentMappingCreate, - ActionMeasureDocumentMappingDelete, - ActionMeasureThirdPartyMappingCreate, - ActionMeasureThirdPartyMappingDelete, - ActionFrameworkCreate, - ActionFrameworkUpdate, - ActionFrameworkDelete, - ActionFrameworkImport, - ActionObligationCreate, - ActionObligationUpdate, - ActionObligationDelete, - ActionObligationPublish, - ActionStatementOfApplicabilityCreate, - ActionStatementOfApplicabilityUpdate, - ActionStatementOfApplicabilityDelete, - ActionStatementOfApplicabilityPublish, - ActionApplicabilityStatementCreate, - ActionApplicabilityStatementUpdate, - ActionApplicabilityStatementDelete, - }, - ScopeV1DatumRead: { - ActionDatumGet, - ActionDatumList, - }, - ScopeV1Datum: { - ActionDatumCreate, - ActionDatumUpdate, - ActionDatumDelete, - ActionDatumPublish, - }, - ScopeV1DocumentRead: { - ActionDocumentGet, - ActionDocumentList, - ActionDocumentVersionGet, - ActionDocumentVersionList, - ActionDocumentVersionExportPDF, - ActionDocumentVersionApprovalList, - ActionDocumentVersionExport, - ActionEmployeeDocumentGet, - ActionEmployeeDocumentList, - ActionEmployeeDocumentVersionExportPDF, - ActionDocumentVersionSignatureGet, - ActionDocumentVersionSignatureList, - ActionElectronicSignatureGet, - ActionFileGet, - }, - ScopeV1Document: { - ActionDocumentCreate, - ActionDocumentUpdate, - ActionDocumentDelete, - ActionDocumentChangelogGenerate, - ActionDocumentArchive, - ActionDocumentUnarchive, - ActionDocumentDeleteDraft, - ActionDocumentVersionSign, - ActionDocumentVersionRequestApproval, - ActionDocumentVersionVoidApproval, - ActionDocumentVersionApprove, - ActionDocumentVersionReject, - ActionDocumentVersionPublish, - ActionDocumentVersionSignatureRequest, - ActionDocumentVersionCancelSignature, - }, - ScopeV1OrgRead: { - ActionOrganizationGet, - ActionOrganizationGetLogoUrl, - ActionOrganizationGetHorizontalLogoUrl, - ActionOrganizationContextGet, - }, - ScopeV1Org: { - ActionOrganizationUpdate, - ActionOrganizationContextUpdate, - }, - ScopeV1PrivacyRead: { - ActionProcessingActivityList, - ActionProcessingActivityGet, - ActionDataProtectionImpactAssessmentList, - ActionDataProtectionImpactAssessmentGet, - ActionTransferImpactAssessmentList, - ActionTransferImpactAssessmentGet, - ActionRightsRequestList, - ActionRightsRequestGet, - ActionCookieBannerGet, - ActionCookieBannerList, - ActionCookieBannerVersionGet, - ActionCookieBannerVersionList, - ActionCookieCategoryGet, - ActionCookieCategoryList, - ActionCookieGet, - ActionCookieList, - ActionCookieConsentRecordList, - ActionTrackerPatternGet, - ActionTrackerPatternList, - ActionTrackerResourceGet, - ActionTrackerResourceList, - }, - ScopeV1Privacy: { - ActionProcessingActivityCreate, - ActionProcessingActivityUpdate, - ActionProcessingActivityDelete, - ActionProcessingActivityPublish, - ActionDataProtectionImpactAssessmentCreate, - ActionDataProtectionImpactAssessmentUpdate, - ActionDataProtectionImpactAssessmentDelete, - ActionDataProtectionImpactAssessmentPublish, - ActionTransferImpactAssessmentCreate, - ActionTransferImpactAssessmentUpdate, - ActionTransferImpactAssessmentDelete, - ActionTransferImpactAssessmentPublish, - ActionRightsRequestCreate, - ActionRightsRequestUpdate, - ActionRightsRequestDelete, - ActionCookieBannerCreate, - ActionCookieBannerUpdate, - ActionCookieBannerDelete, - ActionCookieBannerActivate, - ActionCookieBannerDeactivate, - ActionCookieBannerRegeneratePolicy, - ActionCookieBannerVersionPublish, - ActionCookieCategoryCreate, - ActionCookieCategoryUpdate, - ActionCookieCategoryDelete, - ActionCookieCreate, - ActionCookieUpdate, - ActionCookieDelete, - ActionTrackerPatternCreate, - ActionTrackerPatternUpdate, - ActionTrackerPatternDelete, - ActionTrackerResourceCreate, - ActionTrackerResourceUpdate, - ActionTrackerResourceDelete, - }, - ScopeV1RiskRead: { - ActionRiskGet, - ActionRiskList, - ActionRiskAssessmentGet, - ActionRiskAssessmentList, - ActionRiskAssessmentScopeGet, - ActionRiskAssessmentScopeList, - ActionRiskAssessmentNodeGet, - ActionRiskAssessmentNodeList, - ActionRiskAssessmentBoundaryGet, - ActionRiskAssessmentBoundaryList, - ActionRiskAssessmentProcessGet, - ActionRiskAssessmentProcessList, - ActionRiskAssessmentThreatGet, - ActionRiskAssessmentThreatList, - ActionRiskAssessmentScenarioGet, - ActionRiskAssessmentScenarioList, - }, - ScopeV1Risk: { - ActionRiskCreate, - ActionRiskUpdate, - ActionRiskDelete, - ActionRiskMeasureMappingCreate, - ActionRiskMeasureMappingDelete, - ActionRiskDocumentMappingCreate, - ActionRiskDocumentMappingDelete, - ActionRiskObligationMappingCreate, - ActionRiskObligationMappingDelete, - ActionRiskPublish, - ActionRiskAssessmentCreate, - ActionRiskAssessmentUpdate, - ActionRiskAssessmentDelete, - ActionRiskAssessmentScopeCreate, - ActionRiskAssessmentScopeUpdate, - ActionRiskAssessmentScopeDelete, - ActionRiskAssessmentNodeCreate, - ActionRiskAssessmentNodeUpdate, - ActionRiskAssessmentNodeDelete, - ActionRiskAssessmentBoundaryCreate, - ActionRiskAssessmentBoundaryUpdate, - ActionRiskAssessmentBoundaryDelete, - ActionRiskAssessmentProcessCreate, - ActionRiskAssessmentProcessUpdate, - ActionRiskAssessmentProcessDelete, - ActionRiskAssessmentThreatCreate, - ActionRiskAssessmentThreatUpdate, - ActionRiskAssessmentThreatDelete, - ActionRiskAssessmentScenarioCreate, - ActionRiskAssessmentScenarioUpdate, - ActionRiskAssessmentScenarioDelete, - ActionRiskAssessmentScenarioThreatLink, - ActionRiskAssessmentScenarioThreatUnlink, - ActionRiskAssessmentScenarioRiskLink, - ActionRiskAssessmentScenarioRiskUnlink, - }, - ScopeV1SlackConnectionRead: { - ActionSlackConnectionList, - }, - ScopeV1TaskRead: { - ActionTaskGet, - ActionTaskList, - ActionEvidenceList, - }, - ScopeV1Task: { - ActionTaskCreate, - ActionTaskUpdate, - ActionTaskDelete, - ActionTaskAssign, - ActionTaskUnassign, - ActionEvidenceDelete, - }, - ScopeV1ThirdPartyRead: { - ActionThirdPartyList, - ActionThirdPartyGet, - ActionThirdPartyRelationList, - ActionThirdPartyContactGet, - ActionThirdPartyContactList, - ActionThirdPartyServiceGet, - ActionThirdPartyServiceList, - ActionThirdPartyComplianceReportGet, - ActionThirdPartyComplianceReportList, - ActionThirdPartyBusinessAssociateAgreementGet, - ActionThirdPartyDataPrivacyAgreementGet, - ActionThirdPartyRiskAssessmentList, - }, - ScopeV1ThirdParty: { - ActionThirdPartyCreate, - ActionThirdPartyUpdate, - ActionThirdPartyDelete, - ActionThirdPartyVet, - ActionThirdPartyPublish, - ActionThirdPartyRelationCreate, - ActionThirdPartyContactCreate, - ActionThirdPartyContactUpdate, - ActionThirdPartyContactDelete, - ActionThirdPartyServiceCreate, - ActionThirdPartyServiceUpdate, - ActionThirdPartyServiceDelete, - ActionThirdPartyComplianceReportUpload, - ActionThirdPartyComplianceReportDelete, - ActionThirdPartyBusinessAssociateAgreementUpload, - ActionThirdPartyBusinessAssociateAgreementUpdate, - ActionThirdPartyBusinessAssociateAgreementDelete, - ActionThirdPartyDataPrivacyAgreementUpload, - ActionThirdPartyDataPrivacyAgreementUpdate, - ActionThirdPartyDataPrivacyAgreementDelete, - ActionThirdPartyRiskAssessmentCreate, - }, - ScopeV1WebhookRead: { - ActionWebhookSubscriptionList, - ActionWebhookSubscriptionGet, - }, - ScopeV1Webhook: { - ActionWebhookSubscriptionCreate, - ActionWebhookSubscriptionUpdate, - ActionWebhookSubscriptionDelete, - }, - }, - ) + ScopeV1AssetRead: { + ActionAssetGet, + ActionAssetList, + }, + ScopeV1Asset: { + ActionAssetCreate, + ActionAssetUpdate, + ActionAssetDelete, + ActionAssetPublish, + }, + ScopeV1AuditRead: { + ActionAuditGet, + ActionAuditList, + ActionFindingGet, + ActionFindingList, + ActionReportGet, + ActionReportGetReportUrl, + ActionReportDownloadUrlGet, + }, + ScopeV1Audit: { + ActionAuditCreate, + ActionAuditUpdate, + ActionAuditDelete, + ActionAuditReportUpload, + ActionAuditReportDelete, + ActionFindingCreate, + ActionFindingUpdate, + ActionFindingDelete, + ActionFindingAuditMappingCreate, + ActionFindingAuditMappingDelete, + ActionFindingPublish, + }, + ScopeV1CommonThirdPartyRead: { + ActionCommonThirdPartyGet, + ActionCommonThirdPartyList, + }, + ScopeV1CompliancePageRead: { + ActionTrustCenterGet, + ActionTrustCenterGetNda, + ActionTrustCenterAccessGet, + ActionTrustCenterAccessList, + ActionTrustCenterFileGet, + ActionTrustCenterFileList, + ActionTrustCenterFileGetFileUrl, + ActionTrustCenterReferenceList, + ActionTrustCenterReferenceGetLogoUrl, + ActionTrustCenterDocumentAccessList, + ActionMailingListUpdateList, + ActionMailingListSubscriberList, + ActionComplianceFrameworkList, + ActionComplianceExternalURLList, + ActionCustomDomainGet, + }, + ScopeV1CompliancePage: { + ActionTrustCenterUpdate, + ActionTrustCenterNonDisclosureAgreementUpload, + ActionTrustCenterNonDisclosureAgreementDelete, + ActionTrustCenterAccessCreate, + ActionTrustCenterAccessUpdate, + ActionTrustCenterAccessDelete, + ActionTrustCenterFileUpdate, + ActionTrustCenterFileDelete, + ActionTrustCenterFileCreate, + ActionTrustCenterReferenceCreate, + ActionTrustCenterReferenceUpdate, + ActionTrustCenterReferenceDelete, + ActionMailingListUpdateCreate, + ActionMailingListUpdateUpdate, + ActionMailingListUpdateSend, + ActionMailingListUpdateDelete, + ActionMailingListUpdate, + ActionMailingListSubscriberCreate, + ActionMailingListSubscriberDelete, + ActionComplianceFrameworkCreate, + ActionComplianceFrameworkDelete, + ActionComplianceFrameworkUpdateRank, + ActionComplianceExternalURLCreate, + ActionComplianceExternalURLUpdate, + ActionComplianceExternalURLDelete, + ActionCustomDomainCreate, + ActionCustomDomainDelete, + }, + ScopeV1ConnectorRead: { + ActionConnectorList, + ActionConnectorGet, + }, + ScopeV1Connector: { + ActionConnectorCreate, + ActionConnectorDelete, + ActionConnectorInitiate, + }, + ScopeV1ControlRead: { + ActionControlGet, + ActionControlList, + ActionMeasureGet, + ActionMeasureList, + ActionFrameworkGet, + ActionFrameworkList, + ActionFrameworkExport, + ActionObligationGet, + ActionObligationList, + ActionStatementOfApplicabilityList, + ActionStatementOfApplicabilityGet, + ActionApplicabilityStatementGet, + ActionApplicabilityStatementList, + }, + ScopeV1Control: { + ActionControlCreate, + ActionControlUpdate, + ActionControlDelete, + ActionControlMeasureMappingCreate, + ActionControlMeasureMappingDelete, + ActionControlDocumentMappingCreate, + ActionControlDocumentMappingDelete, + ActionControlAuditMappingCreate, + ActionControlAuditMappingDelete, + ActionControlObligationMappingCreate, + ActionControlObligationMappingDelete, + ActionMeasureCreate, + ActionMeasureUpdate, + ActionMeasureDelete, + ActionMeasureEvidenceUpload, + ActionMeasureImport, + ActionMeasureDocumentMappingCreate, + ActionMeasureDocumentMappingDelete, + ActionMeasureThirdPartyMappingCreate, + ActionMeasureThirdPartyMappingDelete, + ActionFrameworkCreate, + ActionFrameworkUpdate, + ActionFrameworkDelete, + ActionFrameworkImport, + ActionObligationCreate, + ActionObligationUpdate, + ActionObligationDelete, + ActionObligationPublish, + ActionStatementOfApplicabilityCreate, + ActionStatementOfApplicabilityUpdate, + ActionStatementOfApplicabilityDelete, + ActionStatementOfApplicabilityPublish, + ActionApplicabilityStatementCreate, + ActionApplicabilityStatementUpdate, + ActionApplicabilityStatementDelete, + }, + ScopeV1DatumRead: { + ActionDatumGet, + ActionDatumList, + }, + ScopeV1Datum: { + ActionDatumCreate, + ActionDatumUpdate, + ActionDatumDelete, + ActionDatumPublish, + }, + ScopeV1DocumentRead: { + ActionDocumentGet, + ActionDocumentList, + ActionDocumentVersionGet, + ActionDocumentVersionList, + ActionDocumentVersionExportPDF, + ActionDocumentVersionApprovalList, + ActionDocumentVersionExport, + ActionEmployeeDocumentGet, + ActionEmployeeDocumentList, + ActionEmployeeDocumentVersionExportPDF, + ActionDocumentVersionSignatureGet, + ActionDocumentVersionSignatureList, + ActionElectronicSignatureGet, + ActionFileGet, + }, + ScopeV1Document: { + ActionDocumentCreate, + ActionDocumentUpdate, + ActionDocumentDelete, + ActionDocumentChangelogGenerate, + ActionDocumentArchive, + ActionDocumentUnarchive, + ActionDocumentDeleteDraft, + ActionDocumentVersionSign, + ActionDocumentVersionRequestApproval, + ActionDocumentVersionVoidApproval, + ActionDocumentVersionApprove, + ActionDocumentVersionReject, + ActionDocumentVersionPublish, + ActionDocumentVersionSignatureRequest, + ActionDocumentVersionCancelSignature, + }, + ScopeV1OrgRead: { + ActionOrganizationGet, + ActionOrganizationGetLogoUrl, + ActionOrganizationGetHorizontalLogoUrl, + ActionOrganizationContextGet, + }, + ScopeV1Org: { + ActionOrganizationUpdate, + ActionOrganizationContextUpdate, + }, + ScopeV1PrivacyRead: { + ActionProcessingActivityList, + ActionProcessingActivityGet, + ActionDataProtectionImpactAssessmentList, + ActionDataProtectionImpactAssessmentGet, + ActionTransferImpactAssessmentList, + ActionTransferImpactAssessmentGet, + ActionRightsRequestList, + ActionRightsRequestGet, + ActionCookieBannerGet, + ActionCookieBannerList, + ActionCookieBannerVersionGet, + ActionCookieBannerVersionList, + ActionCookieCategoryGet, + ActionCookieCategoryList, + ActionCookieGet, + ActionCookieList, + ActionCookieConsentRecordList, + ActionTrackerPatternGet, + ActionTrackerPatternList, + ActionTrackerResourceGet, + ActionTrackerResourceList, + }, + ScopeV1Privacy: { + ActionProcessingActivityCreate, + ActionProcessingActivityUpdate, + ActionProcessingActivityDelete, + ActionProcessingActivityPublish, + ActionDataProtectionImpactAssessmentCreate, + ActionDataProtectionImpactAssessmentUpdate, + ActionDataProtectionImpactAssessmentDelete, + ActionDataProtectionImpactAssessmentPublish, + ActionTransferImpactAssessmentCreate, + ActionTransferImpactAssessmentUpdate, + ActionTransferImpactAssessmentDelete, + ActionTransferImpactAssessmentPublish, + ActionRightsRequestCreate, + ActionRightsRequestUpdate, + ActionRightsRequestDelete, + ActionCookieBannerCreate, + ActionCookieBannerUpdate, + ActionCookieBannerDelete, + ActionCookieBannerActivate, + ActionCookieBannerDeactivate, + ActionCookieBannerRegeneratePolicy, + ActionCookieBannerVersionPublish, + ActionCookieCategoryCreate, + ActionCookieCategoryUpdate, + ActionCookieCategoryDelete, + ActionCookieCreate, + ActionCookieUpdate, + ActionCookieDelete, + ActionTrackerPatternCreate, + ActionTrackerPatternUpdate, + ActionTrackerPatternDelete, + ActionTrackerResourceCreate, + ActionTrackerResourceUpdate, + ActionTrackerResourceDelete, + }, + ScopeV1RiskRead: { + ActionRiskGet, + ActionRiskList, + ActionRiskAssessmentGet, + ActionRiskAssessmentList, + ActionRiskAssessmentScopeGet, + ActionRiskAssessmentScopeList, + ActionRiskAssessmentNodeGet, + ActionRiskAssessmentNodeList, + ActionRiskAssessmentBoundaryGet, + ActionRiskAssessmentBoundaryList, + ActionRiskAssessmentProcessGet, + ActionRiskAssessmentProcessList, + ActionRiskAssessmentThreatGet, + ActionRiskAssessmentThreatList, + ActionRiskAssessmentScenarioGet, + ActionRiskAssessmentScenarioList, + }, + ScopeV1Risk: { + ActionRiskCreate, + ActionRiskUpdate, + ActionRiskDelete, + ActionRiskMeasureMappingCreate, + ActionRiskMeasureMappingDelete, + ActionRiskDocumentMappingCreate, + ActionRiskDocumentMappingDelete, + ActionRiskObligationMappingCreate, + ActionRiskObligationMappingDelete, + ActionRiskPublish, + ActionRiskAssessmentCreate, + ActionRiskAssessmentUpdate, + ActionRiskAssessmentDelete, + ActionRiskAssessmentScopeCreate, + ActionRiskAssessmentScopeUpdate, + ActionRiskAssessmentScopeDelete, + ActionRiskAssessmentNodeCreate, + ActionRiskAssessmentNodeUpdate, + ActionRiskAssessmentNodeDelete, + ActionRiskAssessmentBoundaryCreate, + ActionRiskAssessmentBoundaryUpdate, + ActionRiskAssessmentBoundaryDelete, + ActionRiskAssessmentProcessCreate, + ActionRiskAssessmentProcessUpdate, + ActionRiskAssessmentProcessDelete, + ActionRiskAssessmentThreatCreate, + ActionRiskAssessmentThreatUpdate, + ActionRiskAssessmentThreatDelete, + ActionRiskAssessmentScenarioCreate, + ActionRiskAssessmentScenarioUpdate, + ActionRiskAssessmentScenarioDelete, + ActionRiskAssessmentScenarioThreatLink, + ActionRiskAssessmentScenarioThreatUnlink, + ActionRiskAssessmentScenarioRiskLink, + ActionRiskAssessmentScenarioRiskUnlink, + }, + ScopeV1SlackConnectionRead: { + ActionSlackConnectionList, + }, + ScopeV1TaskRead: { + ActionTaskGet, + ActionTaskList, + ActionEvidenceList, + }, + ScopeV1Task: { + ActionTaskCreate, + ActionTaskUpdate, + ActionTaskDelete, + ActionTaskAssign, + ActionTaskUnassign, + ActionEvidenceDelete, + }, + ScopeV1ThirdPartyRead: { + ActionThirdPartyList, + ActionThirdPartyGet, + ActionThirdPartyRelationList, + ActionThirdPartyContactGet, + ActionThirdPartyContactList, + ActionThirdPartyServiceGet, + ActionThirdPartyServiceList, + ActionThirdPartyComplianceReportGet, + ActionThirdPartyComplianceReportList, + ActionThirdPartyBusinessAssociateAgreementGet, + ActionThirdPartyDataPrivacyAgreementGet, + ActionThirdPartyRiskAssessmentList, + }, + ScopeV1ThirdParty: { + ActionThirdPartyCreate, + ActionThirdPartyUpdate, + ActionThirdPartyDelete, + ActionThirdPartyVet, + ActionThirdPartyPublish, + ActionThirdPartyRelationCreate, + ActionThirdPartyContactCreate, + ActionThirdPartyContactUpdate, + ActionThirdPartyContactDelete, + ActionThirdPartyServiceCreate, + ActionThirdPartyServiceUpdate, + ActionThirdPartyServiceDelete, + ActionThirdPartyComplianceReportUpload, + ActionThirdPartyComplianceReportDelete, + ActionThirdPartyBusinessAssociateAgreementUpload, + ActionThirdPartyBusinessAssociateAgreementUpdate, + ActionThirdPartyBusinessAssociateAgreementDelete, + ActionThirdPartyDataPrivacyAgreementUpload, + ActionThirdPartyDataPrivacyAgreementUpdate, + ActionThirdPartyDataPrivacyAgreementDelete, + ActionThirdPartyRiskAssessmentCreate, + }, + ScopeV1WebhookRead: { + ActionWebhookSubscriptionList, + ActionWebhookSubscriptionGet, + }, + ScopeV1Webhook: { + ActionWebhookSubscriptionCreate, + ActionWebhookSubscriptionUpdate, + ActionWebhookSubscriptionDelete, + }, } diff --git a/pkg/probo/service.go b/pkg/probo/service.go index dcb5f0e1c..a93084249 100644 --- a/pkg/probo/service.go +++ b/pkg/probo/service.go @@ -149,7 +149,7 @@ func NewService( } iamService.Authorizer.RegisterPolicySet(ProboPolicySet()) - iamService.Authorizer.RegisterScopes(OAuth2ScopeSet()) + iamService.OAuth2ScopeSet.Register(OAuth2ScopeMappings) svc := &Service{ pg: pgClient, diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index b4143db0d..308f5d93f 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -601,8 +601,8 @@ func (impl *Implm) Run( iamService.Authorizer.RegisterPolicySet(agentrun.PolicySet()) iamService.Authorizer.RegisterPolicySet(accessreview.PolicySet()) - iamService.Authorizer.RegisterScopes(agentrun.OAuth2ScopeSet()) - iamService.Authorizer.RegisterScopes(accessreview.OAuth2ScopeSet()) + iamService.OAuth2ScopeSet.Register(agentrun.OAuth2ScopeMappings) + iamService.OAuth2ScopeSet.Register(accessreview.OAuth2ScopeMappings) thirdPartyService := thirdparty.NewService(pgClient, fileManagerService, thirdPartyVetter) riskManagementService := riskmanagement.NewService(pgClient) diff --git a/pkg/server/api/connect/v1/base_resolvers.go b/pkg/server/api/connect/v1/base_resolvers.go index ba2c1db4a..092999ca1 100644 --- a/pkg/server/api/connect/v1/base_resolvers.go +++ b/pkg/server/api/connect/v1/base_resolvers.go @@ -268,7 +268,7 @@ func (r *queryResolver) SignUpEnabled(ctx context.Context) (bool, error) { // Oauth2ScopesSupported is the resolver for the oauth2ScopesSupported field. func (r *queryResolver) Oauth2ScopesSupported(ctx context.Context) ([]string, error) { - apiScopes := r.iam.Authorizer.APIScopes() + apiScopes := r.iam.OAuth2ScopeSet.APIScopes() scopes := make([]string, len(apiScopes)) for i, scope := range apiScopes { diff --git a/pkg/server/api/connect/v1/oauth2_access_token_resolvers.go b/pkg/server/api/connect/v1/oauth2_access_token_resolvers.go index c6a99c60b..1647367dc 100644 --- a/pkg/server/api/connect/v1/oauth2_access_token_resolvers.go +++ b/pkg/server/api/connect/v1/oauth2_access_token_resolvers.go @@ -40,7 +40,7 @@ func (r *mutationResolver) CreateOAuth2AccessToken(ctx context.Context, input ty Name: strings.TrimSpace(input.Name), ExpiresAt: input.ExpiresAt, Scopes: scopes, - AllowedAPIScopes: r.iam.Authorizer.APIScopes(), + AllowedAPIScopes: r.iam.OAuth2ScopeSet.APIScopes(), }, ) if err != nil {