From 20b9321de3662f163d2ce265547257ff88ae262d Mon Sep 17 00:00:00 2001 From: Ludovic Vielle Date: Tue, 23 Jun 2026 10:36:32 +0200 Subject: [PATCH] Add read actions to all unprefixed scopes Signed-off-by: Ludovic Vielle --- pkg/accessreview/oauth2_scopes.go | 7 ++ pkg/agentrun/oauth2_scopes.go | 2 + pkg/coredata/migrations/20260623T085014Z.sql | 42 +++++++ pkg/coredata/oauth2_scope.go | 4 + pkg/coredata/oauth2_scope_test.go | 49 ++++++++ pkg/iam/oauth2/cimd.go | 2 +- pkg/iam/oauth2_scopes.go | 24 ++++ pkg/iam/oauth2scope/registry.go | 14 +++ pkg/iam/oauth2scope/registry_test.go | 19 ++++ pkg/probo/oauth2_scopes.go | 113 +++++++++++++++++++ pkg/resourcealias/oauth2_scopes.go | 1 + 11 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 pkg/coredata/migrations/20260623T085014Z.sql create mode 100644 pkg/coredata/oauth2_scope_test.go diff --git a/pkg/accessreview/oauth2_scopes.go b/pkg/accessreview/oauth2_scopes.go index 13f531803..8b3843b2c 100644 --- a/pkg/accessreview/oauth2_scopes.go +++ b/pkg/accessreview/oauth2_scopes.go @@ -33,6 +33,13 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionDriverCatalogList, }, ScopeV1AccessReview: { + ActionCampaignGet, + ActionCampaignList, + ActionEntryGet, + ActionEntryList, + ActionSourceGet, + ActionSourceList, + ActionDriverCatalogList, ActionCampaignCreate, ActionCampaignUpdate, ActionCampaignDelete, diff --git a/pkg/agentrun/oauth2_scopes.go b/pkg/agentrun/oauth2_scopes.go index d83baaabc..a9b19e89e 100644 --- a/pkg/agentrun/oauth2_scopes.go +++ b/pkg/agentrun/oauth2_scopes.go @@ -27,6 +27,8 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionAgentRunList, }, ScopeV1Agent: { + ActionAgentRunGet, + ActionAgentRunList, ActionAgentRunApprove, }, } diff --git a/pkg/coredata/migrations/20260623T085014Z.sql b/pkg/coredata/migrations/20260623T085014Z.sql new file mode 100644 index 000000000..7d6ee8fad --- /dev/null +++ b/pkg/coredata/migrations/20260623T085014Z.sql @@ -0,0 +1,42 @@ +-- 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. + +-- Narrow prb CLI OAuth2 client registration to unprefixed v1:* scopes. +UPDATE iam_oauth2_clients +SET scopes = '{ + openid, + profile, + email, + offline_access, + v1:access-review, + v1:agent, + v1:asset, + v1:audit, + v1:common-third-party, + v1:compliance-page, + v1:connector, + v1:control, + v1:datum, + v1:document, + v1:iam, + v1:org, + v1:privacy, + v1:risk, + v1:slack-connection, + v1:task, + v1:third-party, + v1:webhook +}'::TEXT[], + updated_at = NOW() +WHERE id = 'AAAAAAAAAAAASwAAAAAAAAAAcHJiY2xp'; diff --git a/pkg/coredata/oauth2_scope.go b/pkg/coredata/oauth2_scope.go index be6e08471..45af4eff6 100644 --- a/pkg/coredata/oauth2_scope.go +++ b/pkg/coredata/oauth2_scope.go @@ -37,6 +37,10 @@ func (v OAuth2Scope) String() string { return string(v) } +func (v OAuth2Scope) IsRead() bool { + return strings.HasSuffix(string(v), ":read") +} + func (v OAuth2Scope) MarshalText() ([]byte, error) { return []byte(v.String()), nil } diff --git a/pkg/coredata/oauth2_scope_test.go b/pkg/coredata/oauth2_scope_test.go new file mode 100644 index 000000000..b920060c9 --- /dev/null +++ b/pkg/coredata/oauth2_scope_test.go @@ -0,0 +1,49 @@ +// 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 coredata_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.probo.inc/probo/pkg/coredata" +) + +func TestOAuth2Scope_IsRead(t *testing.T) { + t.Parallel() + + tests := []struct { + scope coredata.OAuth2Scope + want bool + }{ + {scope: "v1:org:read", want: true}, + {scope: "v1:document:read", want: true}, + {scope: "v1:org", want: false}, + {scope: "v1:privacy", want: false}, + {scope: "openid", want: false}, + {scope: "offline_access", want: false}, + } + + for _, tt := range tests { + t.Run( + tt.scope.String(), + func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.want, tt.scope.IsRead()) + }, + ) + } +} diff --git a/pkg/iam/oauth2/cimd.go b/pkg/iam/oauth2/cimd.go index b9c185a4c..4b2dd673b 100644 --- a/pkg/iam/oauth2/cimd.go +++ b/pkg/iam/oauth2/cimd.go @@ -400,7 +400,7 @@ func (s *Service) upsertCIMDClient( clientURI = &doc.ClientURI } - scopes := coredata.OAuth2Scopes(authorizationServerScopes(s.registry.RegisteredScopes())) + scopes := coredata.OAuth2Scopes(authorizationServerScopes(s.registry.AllWriteScopes())) now := time.Now() diff --git a/pkg/iam/oauth2_scopes.go b/pkg/iam/oauth2_scopes.go index 3ade54827..7ab59e82c 100644 --- a/pkg/iam/oauth2_scopes.go +++ b/pkg/iam/oauth2_scopes.go @@ -49,6 +49,30 @@ var IAMOAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionOAuth2AccessTokenList, }, ScopeV1IAM: { + ActionOrganizationGet, + ActionOrganizationList, + ActionIdentityGet, + ActionSessionList, + ActionSessionGet, + ActionInvitationList, + ActionInvitationGet, + ActionMembershipGet, + ActionMembershipList, + ActionMembershipProfileGet, + ActionMembershipProfileList, + ActionPersonalAPIKeyGet, + ActionPersonalAPIKeyList, + ActionSAMLConfigurationGet, + ActionSAMLConfigurationList, + ActionSCIMConfigurationGet, + ActionSCIMEventList, + ActionSCIMEventGet, + ActionSCIMBridgeGet, + ActionOAuth2ConsentGet, + ActionAuditLogEntryGet, + ActionAuditLogEntryList, + ActionOAuth2AccessTokenGet, + ActionOAuth2AccessTokenList, ActionOrganizationCreate, ActionOrganizationUpdate, ActionOrganizationDelete, diff --git a/pkg/iam/oauth2scope/registry.go b/pkg/iam/oauth2scope/registry.go index 98e919ab5..b508f5249 100644 --- a/pkg/iam/oauth2scope/registry.go +++ b/pkg/iam/oauth2scope/registry.go @@ -60,6 +60,20 @@ func (r *Registry) RegisteredScopes() []coredata.OAuth2Scope { return sortedScopes(slices.Collect(maps.Keys(r.scopeActions))) } +func (r *Registry) AllWriteScopes() []coredata.OAuth2Scope { + r.mu.RLock() + defer r.mu.RUnlock() + + writeScopes := make([]coredata.OAuth2Scope, 0, len(r.scopeActions)) + for scope := range r.scopeActions { + if !scope.IsRead() { + writeScopes = append(writeScopes, scope) + } + } + + return sortedScopes(writeScopes) +} + func (r *Registry) Allows(tokenScopes coredata.OAuth2Scopes, action string) bool { r.mu.RLock() defer r.mu.RUnlock() diff --git a/pkg/iam/oauth2scope/registry_test.go b/pkg/iam/oauth2scope/registry_test.go index 7c2be3c1e..34cd86146 100644 --- a/pkg/iam/oauth2scope/registry_test.go +++ b/pkg/iam/oauth2scope/registry_test.go @@ -105,6 +105,25 @@ func TestRegistry_ScopesForAction(t *testing.T) { assert.Nil(t, reg.ScopesForAction("core:organization:delete")) } +func TestRegistry_AllWriteScopes(t *testing.T) { + t.Parallel() + + const ( + scopeV1OrgRead = coredata.OAuth2Scope("v1:org:read") + scopeV1OrgWrite = coredata.OAuth2Scope("v1:org") + ) + + reg := oauth2scope.NewRegistry(). + Register( + map[coredata.OAuth2Scope][]string{ + scopeV1OrgWrite: {"core:organization:update"}, + scopeV1OrgRead: {"core:organization:get"}, + }, + ) + + assert.Equal(t, []coredata.OAuth2Scope{scopeV1OrgWrite}, reg.AllWriteScopes()) +} + func TestRegistry_RegisteredScopes(t *testing.T) { t.Parallel() diff --git a/pkg/probo/oauth2_scopes.go b/pkg/probo/oauth2_scopes.go index ea32a783d..dce2e9656 100644 --- a/pkg/probo/oauth2_scopes.go +++ b/pkg/probo/oauth2_scopes.go @@ -73,6 +73,8 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionAssetList, }, ScopeV1Asset: { + ActionAssetGet, + ActionAssetList, ActionAssetCreate, ActionAssetUpdate, ActionAssetDelete, @@ -88,6 +90,13 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionReportDownloadUrlGet, }, ScopeV1Audit: { + ActionAuditGet, + ActionAuditList, + ActionFindingGet, + ActionFindingList, + ActionReportGet, + ActionReportGetReportUrl, + ActionReportDownloadUrlGet, ActionAuditCreate, ActionAuditUpdate, ActionAuditDelete, @@ -122,6 +131,21 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionCustomDomainGet, }, ScopeV1CompliancePage: { + ActionTrustCenterGet, + ActionTrustCenterGetNda, + ActionTrustCenterAccessGet, + ActionTrustCenterAccessList, + ActionTrustCenterFileGet, + ActionTrustCenterFileList, + ActionTrustCenterFileGetFileUrl, + ActionTrustCenterReferenceList, + ActionTrustCenterReferenceGetLogoUrl, + ActionTrustCenterDocumentAccessList, + ActionMailingListUpdateList, + ActionMailingListSubscriberList, + ActionComplianceFrameworkList, + ActionComplianceExternalURLList, + ActionCustomDomainGet, ActionTrustCenterUpdate, ActionTrustCenterNonDisclosureAgreementUpload, ActionTrustCenterNonDisclosureAgreementDelete, @@ -155,6 +179,8 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionConnectorGet, }, ScopeV1Connector: { + ActionConnectorList, + ActionConnectorGet, ActionConnectorCreate, ActionConnectorDelete, ActionConnectorInitiate, @@ -175,6 +201,19 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionApplicabilityStatementList, }, ScopeV1Control: { + ActionControlGet, + ActionControlList, + ActionMeasureGet, + ActionMeasureList, + ActionFrameworkGet, + ActionFrameworkList, + ActionFrameworkExport, + ActionObligationGet, + ActionObligationList, + ActionStatementOfApplicabilityList, + ActionStatementOfApplicabilityGet, + ActionApplicabilityStatementGet, + ActionApplicabilityStatementList, ActionControlCreate, ActionControlUpdate, ActionControlDelete, @@ -216,6 +255,8 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionDatumList, }, ScopeV1Datum: { + ActionDatumGet, + ActionDatumList, ActionDatumCreate, ActionDatumUpdate, ActionDatumDelete, @@ -238,6 +279,20 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionFileGet, }, ScopeV1Document: { + ActionDocumentGet, + ActionDocumentList, + ActionDocumentVersionGet, + ActionDocumentVersionList, + ActionDocumentVersionExportPDF, + ActionDocumentVersionApprovalList, + ActionDocumentVersionExport, + ActionEmployeeDocumentGet, + ActionEmployeeDocumentList, + ActionEmployeeDocumentVersionExportPDF, + ActionDocumentVersionSignatureGet, + ActionDocumentVersionSignatureList, + ActionElectronicSignatureGet, + ActionFileGet, ActionDocumentCreate, ActionDocumentUpdate, ActionDocumentDelete, @@ -261,6 +316,10 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionOrganizationContextGet, }, ScopeV1Org: { + ActionOrganizationGet, + ActionOrganizationGetLogoUrl, + ActionOrganizationGetHorizontalLogoUrl, + ActionOrganizationContextGet, ActionOrganizationUpdate, ActionOrganizationContextUpdate, }, @@ -288,6 +347,27 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionTrackerResourceList, }, ScopeV1Privacy: { + ActionProcessingActivityList, + ActionProcessingActivityGet, + ActionDataProtectionImpactAssessmentList, + ActionDataProtectionImpactAssessmentGet, + ActionTransferImpactAssessmentList, + ActionTransferImpactAssessmentGet, + ActionRightsRequestList, + ActionRightsRequestGet, + ActionCookieBannerGet, + ActionCookieBannerList, + ActionCookieBannerVersionGet, + ActionCookieBannerVersionList, + ActionCookieCategoryGet, + ActionCookieCategoryList, + ActionCookieGet, + ActionCookieList, + ActionCookieConsentRecordList, + ActionTrackerPatternGet, + ActionTrackerPatternList, + ActionTrackerResourceGet, + ActionTrackerResourceList, ActionProcessingActivityCreate, ActionProcessingActivityUpdate, ActionProcessingActivityDelete, @@ -342,6 +422,22 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionRiskAssessmentScenarioList, }, ScopeV1Risk: { + ActionRiskGet, + ActionRiskList, + ActionRiskAssessmentGet, + ActionRiskAssessmentList, + ActionRiskAssessmentScopeGet, + ActionRiskAssessmentScopeList, + ActionRiskAssessmentNodeGet, + ActionRiskAssessmentNodeList, + ActionRiskAssessmentBoundaryGet, + ActionRiskAssessmentBoundaryList, + ActionRiskAssessmentProcessGet, + ActionRiskAssessmentProcessList, + ActionRiskAssessmentThreatGet, + ActionRiskAssessmentThreatList, + ActionRiskAssessmentScenarioGet, + ActionRiskAssessmentScenarioList, ActionRiskCreate, ActionRiskUpdate, ActionRiskDelete, @@ -387,6 +483,9 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionEvidenceList, }, ScopeV1Task: { + ActionTaskGet, + ActionTaskList, + ActionEvidenceList, ActionTaskCreate, ActionTaskUpdate, ActionTaskDelete, @@ -409,6 +508,18 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionThirdPartyRiskAssessmentList, }, ScopeV1ThirdParty: { + ActionThirdPartyList, + ActionThirdPartyGet, + ActionThirdPartyRelationList, + ActionThirdPartyContactGet, + ActionThirdPartyContactList, + ActionThirdPartyServiceGet, + ActionThirdPartyServiceList, + ActionThirdPartyComplianceReportGet, + ActionThirdPartyComplianceReportList, + ActionThirdPartyBusinessAssociateAgreementGet, + ActionThirdPartyDataPrivacyAgreementGet, + ActionThirdPartyRiskAssessmentList, ActionThirdPartyCreate, ActionThirdPartyUpdate, ActionThirdPartyDelete, @@ -436,6 +547,8 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionWebhookSubscriptionGet, }, ScopeV1Webhook: { + ActionWebhookSubscriptionList, + ActionWebhookSubscriptionGet, ActionWebhookSubscriptionCreate, ActionWebhookSubscriptionUpdate, ActionWebhookSubscriptionDelete, diff --git a/pkg/resourcealias/oauth2_scopes.go b/pkg/resourcealias/oauth2_scopes.go index ec33932b4..8b92e84d3 100644 --- a/pkg/resourcealias/oauth2_scopes.go +++ b/pkg/resourcealias/oauth2_scopes.go @@ -27,6 +27,7 @@ var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{ ActionAliasGet, }, ScopeV1ResourceAlias: { + ActionAliasGet, ActionAliasSet, ActionAliasRemove, },