feat(16.3-01): account token API with OpenAPI and phrase keys
- Add list/create/revoke/scopes handlers, validation, and soft revoke - Regenerate admin OpenAPI and TS types; inventory and conformance cases - Add server-side api_tokens phrase keys (en/pl) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -162,14 +162,13 @@ func validateTokenScopeMessageKeys(reg *Registry, tr *phrasebook.Translator) err
|
||||
return nil
|
||||
}
|
||||
for _, scope := range reg.tokenScopes.ordered {
|
||||
// Framework scopes use backend::lang.api_tokens.* keys added with the
|
||||
// account token API; only plugin scopes are checked at boot here.
|
||||
if scope.PluginID == "" {
|
||||
continue
|
||||
who := scope.PluginID
|
||||
if who == "" {
|
||||
who = "framework"
|
||||
}
|
||||
for _, text := range []string{scope.Label, scope.Description} {
|
||||
if strings.Contains(text, "::") && !tr.Has(text) {
|
||||
return fmt.Errorf("cabana: token scope %s (%s) names missing phrase key %s", scope.Name, scope.PluginID, text)
|
||||
return fmt.Errorf("cabana: token scope %s (%s) names missing phrase key %s", scope.Name, who, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,59 +326,42 @@ func scopeCoversAll(limit *bouncer.ScopeLimit, cover []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// scopeOverlaps is the D-17 picker/issue rule (Task 3 grows callers).
|
||||
func scopeOverlaps(cat *tokenScopeCatalog, owner *bouncer.Principal, name string) bool {
|
||||
if cat == nil || owner == nil {
|
||||
// scopeOverlaps is the D-17 picker/issue rule: a plugin scope is offered when
|
||||
// some registered permission code has a full match in the scope and the owner
|
||||
// holds that code. "*" is always offered. "admin:read" is offered when the
|
||||
// owner is a superuser or holds at least one catalog permission.
|
||||
func scopeOverlaps(reg *Registry, owner *bouncer.Principal, name string) bool {
|
||||
if reg == nil || reg.tokenScopes == nil || owner == nil {
|
||||
return false
|
||||
}
|
||||
if name == scopeAll {
|
||||
return true
|
||||
}
|
||||
scope, ok := cat.byName[name]
|
||||
scope, ok := reg.tokenScopes.byName[name]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if scope.Full {
|
||||
// admin:read: offered when superuser or holds at least one catalog code.
|
||||
if owner.IsSuperuser {
|
||||
return true
|
||||
}
|
||||
for code := range catPermissionCodes(cat) {
|
||||
for code := range reg.permissions {
|
||||
if ownerHas(owner, code) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Fall through: also check against registry permissions via owner grants.
|
||||
for code := range owner.PermissionGrants {
|
||||
if owner.PermissionGrants[code] {
|
||||
return false
|
||||
}
|
||||
for code := range reg.permissions {
|
||||
for _, pattern := range scope.Permissions {
|
||||
if scopeMatch(pattern, code) == matchFull && ownerHas(owner, code) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return owner.IsSuperuser
|
||||
}
|
||||
for _, c := range scope.Permissions {
|
||||
if scopeMatch(c, c) == matchFull && ownerHas(owner, c) {
|
||||
return true
|
||||
}
|
||||
// Some registered catalog code has a full match in the scope and ownerHas(c).
|
||||
_ = c
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func catPermissionCodes(cat *tokenScopeCatalog) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
if cat == nil {
|
||||
return out
|
||||
}
|
||||
for _, scope := range cat.ordered {
|
||||
for _, p := range scope.Permissions {
|
||||
out[p] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func methodFilterScopes(cat *tokenScopeCatalog, stored []string, method string) (limit *bouncer.ScopeLimit, known int, methodOK int) {
|
||||
if cat == nil {
|
||||
return nil, 0, 0
|
||||
|
||||
Reference in New Issue
Block a user