fix(19.7): RA-IN-05 a token whose scopes no longer resolve gets its own error
The built-in backend guard's principal answers errScopeUnavailable, not a nil principal, when an admin personal token's stored scopes all fail to resolve (a scope's plugin was removed). MCPDispatch passes it through and dispatch maps it to the fixed tool error "pier: the token's scopes no longer grant this route" instead of claiming the administrator's account is unknown or deactivated; the listing check still treats it as a refusal, not as a failed check.
This commit is contained in:
@@ -61,6 +61,9 @@ func (g backendGuard) MCPAllows(ctx context.Context, subject pact.MCPSubject, re
|
||||
// personal token's scope limit computed for method.
|
||||
func (g backendGuard) MCPAllowsMethod(ctx context.Context, subject pact.MCPSubject, _ pact.MCPRequirements, permissions []string, method string) (bool, error) {
|
||||
principal, err := g.principal(ctx, subject, method)
|
||||
if errors.Is(err, errScopeUnavailable) {
|
||||
return false, nil // a refusal, not a failed check
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -75,6 +78,13 @@ func (g backendGuard) MCPAllowsMethod(ctx context.Context, subject pact.MCPSubje
|
||||
// MCP client.
|
||||
var errSubjectUnavailable = errors.New("pier: the caller's account is unknown or deactivated")
|
||||
|
||||
// errScopeUnavailable is what a dispatch answers when an admin personal
|
||||
// token's stored scopes no longer resolve to any registered scope (a
|
||||
// scope's plugin was removed, for example); its text is safe to show the
|
||||
// MCP client and, unlike errSubjectUnavailable, does not claim the account
|
||||
// is gone (RA-IN-05).
|
||||
var errScopeUnavailable = errors.New("pier: the token's scopes no longer grant this route")
|
||||
|
||||
// MCPDispatch builds the admin principal for one in-process call.
|
||||
func (g backendGuard) MCPDispatch(ctx context.Context, subject pact.MCPSubject, method string) (pact.MCPDispatch, error) {
|
||||
principal, err := g.principal(ctx, subject, method)
|
||||
@@ -90,7 +100,8 @@ func (g backendGuard) MCPDispatch(ctx context.Context, subject pact.MCPSubject,
|
||||
// principal loads the admin through cabana.BackendPrincipal (nil when the
|
||||
// admin does not exist or is deactivated) and, for a personal-token subject
|
||||
// (no OAuth client), sets the scope limit for method; a token whose stored
|
||||
// scopes are all unknown answers nil, as the HTTP guard refuses it.
|
||||
// scopes are all unknown answers errScopeUnavailable, as the HTTP guard
|
||||
// refuses it.
|
||||
func (g backendGuard) principal(ctx context.Context, subject pact.MCPSubject, method string) (*bouncer.Principal, error) {
|
||||
principal, err := cabana.BackendPrincipal(ctx, g.app, subject.UserID)
|
||||
if err != nil || principal == nil {
|
||||
@@ -99,7 +110,7 @@ func (g backendGuard) principal(ctx context.Context, subject pact.MCPSubject, me
|
||||
if subject.ClientID == "" && subject.TokenID != 0 {
|
||||
limit, known, _ := cabana.ScopeLimitFor(g.app, subject.Scopes, method)
|
||||
if known == 0 {
|
||||
return nil, nil
|
||||
return nil, errScopeUnavailable
|
||||
}
|
||||
if limit == nil {
|
||||
limit = &bouncer.ScopeLimit{}
|
||||
|
||||
Reference in New Issue
Block a user