55
66package auth
77
8- import "strings"
8+ import (
9+ "net/http"
10+ "strings"
11+ )
912
1013// ScopePrefix and the remaining constants define the canonical portal authorization scopes.
1114const (
@@ -31,34 +34,73 @@ var AllPortalScopes = []string{
3134 ScopePurposesRead , ScopePurposesWrite ,
3235}
3336
34- // ScopeForAPIRequest returns the canonical scope for an allowlisted /api request.
37+ type apiScopePolicy struct {
38+ method , pattern , scope string
39+ }
40+
41+ // apiScopePolicies explicitly allowlists operations exposed through the
42+ // catch-all API proxy. New upstream operations require a deliberate policy.
43+ var apiScopePolicies = []apiScopePolicy {
44+ {http .MethodGet , "/api/consents" , ScopeConsentsReadAny },
45+ {http .MethodPost , "/api/consents" , ScopeConsentsWriteAny },
46+ {http .MethodGet , "/api/consents/attributes" , ScopeConsentsReadAny },
47+ {http .MethodPost , "/api/consents/validate" , ScopeConsentsReadAny },
48+ {http .MethodGet , "/api/consents/{consentId}" , ScopeConsentsReadAny },
49+ {http .MethodPut , "/api/consents/{consentId}" , ScopeConsentsWriteAny },
50+ {http .MethodGet , "/api/consents/{consentId}/history" , ScopeConsentsReadAny },
51+ {http .MethodPost , "/api/consents/{consentId}/revoke" , ScopeConsentsWriteAny },
52+ {http .MethodGet , "/api/consents/{consentId}/authorizations" , ScopeConsentsReadAny },
53+ {http .MethodPost , "/api/consents/{consentId}/authorizations" , ScopeConsentsWriteAny },
54+ {http .MethodGet , "/api/consents/{consentId}/authorizations/{authorizationId}" , ScopeConsentsReadAny },
55+ {http .MethodPut , "/api/consents/{consentId}/authorizations/{authorizationId}" , ScopeConsentsWriteAny },
56+ {http .MethodGet , "/api/consent-elements" , ScopeElementsRead },
57+ {http .MethodPost , "/api/consent-elements" , ScopeElementsWrite },
58+ {http .MethodGet , "/api/consent-elements/{elementId}" , ScopeElementsRead },
59+ {http .MethodGet , "/api/consent-elements/{elementId}/versions" , ScopeElementsRead },
60+ {http .MethodPost , "/api/consent-elements/{elementId}/versions" , ScopeElementsWrite },
61+ {http .MethodGet , "/api/consent-elements/{elementId}/versions/{version}" , ScopeElementsRead },
62+ {http .MethodDelete , "/api/consent-elements/{elementId}/versions/{version}" , ScopeElementsWrite },
63+ {http .MethodGet , "/api/consent-purposes" , ScopePurposesRead },
64+ {http .MethodPost , "/api/consent-purposes" , ScopePurposesWrite },
65+ {http .MethodGet , "/api/consent-purposes/{purposeId}" , ScopePurposesRead },
66+ {http .MethodGet , "/api/consent-purposes/{purposeId}/versions" , ScopePurposesRead },
67+ {http .MethodPost , "/api/consent-purposes/{purposeId}/versions" , ScopePurposesWrite },
68+ {http .MethodGet , "/api/consent-purposes/{purposeId}/versions/{version}" , ScopePurposesRead },
69+ {http .MethodDelete , "/api/consent-purposes/{purposeId}/versions/{version}" , ScopePurposesWrite },
70+ }
71+
72+ // ScopeForAPIRequest returns the canonical scope for an explicitly allowlisted
73+ // API operation.
3574func ScopeForAPIRequest (method , path string ) (string , bool ) {
3675 method = strings .ToUpper (method )
37- parts := strings .Split (strings .Trim (strings .TrimPrefix (path , "/api/" ), "/" ), "/" )
38- if len (parts ) == 0 || parts [0 ] == "" {
39- return "" , false
40- }
41- write := method == "POST" || method == "PUT" || method == "DELETE"
42- switch parts [0 ] {
43- case "consents" :
44- if len (parts ) == 2 && parts [1 ] == "validate" && method == "POST" {
45- return ScopeConsentsReadAny , true
46- }
47- if write {
48- return ScopeConsentsWriteAny , true
76+ for _ , policy := range apiScopePolicies {
77+ if method == policy .method && matchAPIPath (policy .pattern , path ) {
78+ return policy .scope , true
4979 }
50- return ScopeConsentsReadAny , method == "GET"
51- case "consent-elements" :
52- if write {
53- return ScopeElementsWrite , true
80+ }
81+ return "" , false
82+ }
83+
84+ func isKnownAPIPath (path string ) bool {
85+ for _ , policy := range apiScopePolicies {
86+ if matchAPIPath (policy .pattern , path ) {
87+ return true
5488 }
55- return ScopeElementsRead , method == "GET"
56- case "consent-purposes" :
57- if write {
58- return ScopePurposesWrite , true
89+ }
90+ return false
91+ }
92+
93+ func matchAPIPath (pattern , path string ) bool {
94+ patternParts := strings .Split (strings .Trim (pattern , "/" ), "/" )
95+ pathParts := strings .Split (strings .Trim (path , "/" ), "/" )
96+ if len (patternParts ) != len (pathParts ) {
97+ return false
98+ }
99+ for i , patternPart := range patternParts {
100+ placeholder := strings .HasPrefix (patternPart , "{" ) && strings .HasSuffix (patternPart , "}" )
101+ if (! placeholder && patternPart != pathParts [i ]) || pathParts [i ] == "" {
102+ return false
59103 }
60- return ScopePurposesRead , method == "GET"
61- default :
62- return "" , false
63104 }
105+ return true
64106}
0 commit comments