Summary
Implement scope-based authorization decorator and endpoint protection for the Campus Audit Service API.
Background
Parent issue: #538
Issue #542 previously covered both API key model methods and scope authorization. The model methods (is_active, is_expired, is_revoked, has_scope) have been moved to #566.
This issue focuses solely on the authorization layer: the @require_scopes decorator and endpoint protection.
Requirements
1. Create Scope Authorization Decorator
File: campus/audit/decorators.py
Implement @require_scopes(*scopes) decorator that:
- Reads scopes from
flask.g.api_key_scopes (populated during authentication)
- Supports wildcard expansion:
traces:* matches traces:read, traces:write, etc.
- Supports global wildcard:
* matches all scopes
- Returns 403 Forbidden if required scopes not satisfied
2. Populate Scopes During Authentication
File: campus/audit/resources/apikeys.py
Update _authenticate_audit_api_key() to:
- Call
api_key.has_scope() or similar to get key's scopes
- Store scopes in
flask.g.api_key_scopes as a list
- Handle revoked/expired keys (should already work)
3. Protect Trace Endpoints
File: campus/audit/routes/traces.py
Apply @require_scopes to endpoints:
- GET
/traces → @require_scopes("traces:read")
- GET
/traces/<trace_id> → @require_scopes("traces:read")
- POST
/traces → @require_scopes("traces:write")
Scope Definitions
traces:read - Read trace data and summaries
traces:write - Ingest new trace spans
traces:* - Both read and write
* - All scopes
Acceptance Criteria
Related
Summary
Implement scope-based authorization decorator and endpoint protection for the Campus Audit Service API.
Background
Parent issue: #538
Issue #542 previously covered both API key model methods and scope authorization. The model methods (is_active, is_expired, is_revoked, has_scope) have been moved to #566.
This issue focuses solely on the authorization layer: the
@require_scopesdecorator and endpoint protection.Requirements
1. Create Scope Authorization Decorator
File:
campus/audit/decorators.pyImplement
@require_scopes(*scopes)decorator that:flask.g.api_key_scopes(populated during authentication)traces:*matchestraces:read,traces:write, etc.*matches all scopes2. Populate Scopes During Authentication
File:
campus/audit/resources/apikeys.pyUpdate
_authenticate_audit_api_key()to:api_key.has_scope()or similar to get key's scopesflask.g.api_key_scopesas a list3. Protect Trace Endpoints
File:
campus/audit/routes/traces.pyApply
@require_scopesto endpoints:/traces→@require_scopes("traces:read")/traces/<trace_id>→@require_scopes("traces:read")/traces→@require_scopes("traces:write")Scope Definitions
traces:read- Read trace data and summariestraces:write- Ingest new trace spanstraces:*- Both read and write*- All scopesAcceptance Criteria
@require_scopesdecorator implemented incampus/audit/decorators.pytraces:*→traces:read,traces:write)flask.g.api_key_scopespopulated during authenticationRelated