- Added detailed logging for ACL enforcement failures in the OAuth guard, providing clearer insights into permission issues. - Updated error messages in the ACL enforcement logic to include contextual information such as client and user IDs, enhancing debugging capabilities. - Introduced new constraints (CreatorOnly, EditorOnly) in the data access model, allowing for more granular control over data visibility based on user roles. - Enhanced scope definitions to support additional constraints, improving flexibility in access control configurations. - Updated documentation to reflect changes in data constraints and their usage in API handlers.
21 KiB
ACL Scopes Configuration Guide
Overview
This guide explains how to configure and manage ACL (Access Control List) scopes for your OAuth-protected APIs. Scopes define what resources and actions are accessible to different users, teams, and clients.
Directory Structure
All scope configurations should be placed in the openapi/scopes/ directory with the following structure:
openapi/scopes/
├── scopes.yml # Global configuration and default policies
├── alias.yml # Scope aliases for simplified permission management
└── <resource>/ # Resource-specific scope definitions
├── collections.yml # Collections resource scopes
├── documents.yml # Documents resource scopes
└── ...
Organization Guidelines:
- Group related scopes by resource (e.g.,
kb/,user/,job/,file/) - Use descriptive filenames matching the resource name
- Keep each file focused on a single resource or logical grouping
Configuration Files
1. Global Configuration (scopes.yml)
The scopes.yml file defines global ACL behavior, public endpoints, and default rules.
Structure
# Default action for unmatched API endpoints
default: deny # Options: "deny" or "allow"
# Public endpoints (accessible without authentication)
public:
- GET /user/entry
- GET /user/entry/captcha
- POST /user/entry/verify
- GET /user/teams/invitations/:invitation_id
# Default endpoint rules (can be overridden by specific scopes)
endpoints:
# Read operations allowed for authenticated users
- GET /kb/* allow
- GET /kb/collections allow
# Write operations require specific scopes
- POST /kb/* deny
- PUT /kb/* deny
- DELETE /kb/* deny
Fields
| Field | Type | Required | Description |
|---|---|---|---|
default |
string | Yes | Default policy for unmatched endpoints: "allow" or "deny" |
public |
array | No | List of public endpoints (no authentication required) |
endpoints |
array | No | Default endpoint rules (see Endpoint Rules below) |
Endpoint Rules Format
Each endpoint rule can be specified as:
Simple String Format (recommended):
- GET /api/users allow
- POST /api/users deny
- DELETE /api/users/* deny
Struct Format:
- method: GET
path: /api/users
action: allow
Path Patterns:
- Exact path:
/kb/collections- matches exactly - Parameter path:
/kb/collections/:collectionID- matches with parameters - Wildcard path:
/kb/*- matches all paths under/kb/
Best Practices:
- Set
default: denyfor security (deny by default, allow explicitly) - List public endpoints explicitly (login, registration, health checks)
- Use wildcards for broad policies, then override with specific scopes
- Order matters: more specific rules should come after general ones
2. Scope Definitions (Resource Files)
Scope definition files define specific permissions for resources. Each file contains multiple scope definitions.
Structure
# Scope naming convention: resource:action:level
collections:read:all:
description: "Read knowledge base for all users"
endpoints:
- GET /kb/collections
- GET /kb/collections/:collectionID
- GET /kb/collections/:collectionID/exists
collections:read:own:
owner: true # Only show collections owned by current user
creator: true # Only show collections created by current user
description: "Read knowledge base for own collections"
endpoints:
- GET /kb/collections/own
- GET /kb/collections/own/:collectionID
- GET /kb/collections/own/:collectionID/exists
collections:write:own:
owner: true
editor: true # Only allow editing by last editor
description: "Write knowledge base for own collections"
endpoints:
- POST /kb/collections/own
- PUT /kb/collections/own/:collectionID
- DELETE /kb/collections/own/:collectionID
collections:read:team:
team: true # Only show team collections
description: "Read knowledge base for team collections"
endpoints:
- GET /kb/collections/team
- GET /kb/collections/team/:collectionID
collections:read:department:
extra: # Custom constraints
department_only: true
region: "us-west"
description: "Read collections for department in specific region"
endpoints:
- GET /kb/collections/department
- GET /kb/collections/department/:collectionID
Scope Definition Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
description |
string | No | "" | Human-readable description of the scope |
owner |
bool | No | false | If true, data access is restricted to owner only (sets OwnerOnly constraint) |
creator |
bool | No | false | If true, data access is restricted to creator only (sets CreatorOnly constraint) |
editor |
bool | No | false | If true, data access is restricted to editor only (sets EditorOnly constraint) |
team |
bool | No | false | If true, data access is restricted to team only (sets TeamOnly constraint) |
extra |
map | No | {} | User-defined custom constraints (key-value pairs) |
endpoints |
array | Yes | - | List of API endpoints this scope grants access to |
Endpoint Format
Each endpoint in the endpoints array should be formatted as:
METHOD /path
Examples:
endpoints:
- GET /kb/collections
- GET /kb/collections/:collectionID
- POST /kb/collections/own
- PUT /kb/collections/:collectionID
- DELETE /kb/collections/own/:collectionID
Supported HTTP Methods:
GET- Read operationsPOST- Create operationsPUT- Update operationsDELETE- Delete operationsPATCH- Partial update operations
Path Parameters:
- Use
:paramNamesyntax for path parameters (e.g.,:collectionID,:userID) - Parameter names should be descriptive and consistent
3. Scope Aliases (alias.yml)
Aliases allow you to group multiple scopes under a single name for simplified permission management.
Structure
# Alias naming: category:level
user:auth:
- entry:access:public
- entry:register:authenticated
- entry:logout:own
kb:read:
- collections:read:all
- documents:read:all
- search:read:all
- hits:read:all
kb:own:
- collections:read:own
- collections:write:own
- collections:delete:own
- documents:read:own
- documents:write:own
- documents:delete:own
kb:admin:
- collections:read:all
- collections:write:all
- collections:delete:all
- documents:read:all
- documents:write:all
- documents:delete:all
- search:read:all
- graphs:read:all
# System root permission - absolute highest privilege
system:root:
- "*:*:*"
Alias Usage
In Role Configuration:
// Assign aliases to roles instead of individual scopes
role := &Role{
ID: "kb-viewer",
AllowedScopes: []string{
"kb:read", // Expands to all KB read scopes
"user:auth", // Expands to all auth scopes
},
}
Benefits:
- Simplified Management: Change multiple scopes by updating one alias
- Consistency: Ensure users get consistent permission sets
- Readability: Clear, semantic permission names
- Maintenance: Easier to add/remove scopes from permission groups
Best Practices:
- Use hierarchical naming:
resource:level(e.g.,kb:read,kb:own,kb:admin) - Create aliases for common permission patterns
- Document what each alias includes
- Use wildcards (
*:*:*) sparingly and only for system-level access
Scope Naming Convention
Follow a consistent three-part naming convention for scopes:
resource:action:level
Components
-
Resource (noun): The resource being accessed
- Examples:
collections,documents,profile,jobs,files - Should be plural for collections, singular for single resources
- Examples:
-
Action (verb): The operation being performed
read- View/retrieve data (GET)write- Create/update data (POST, PUT, PATCH)delete- Remove data (DELETE)control- Special operations (start, stop, pause)access- Generic access without CRUD semantics
-
Level (scope): The access level or data visibility
all- Full access to all resourcesown- Access only to user's own resourcesteam- Access to team resourcespublic- Public/unauthenticated accessauthenticated- Basic authenticated access
Examples
| Scope | Description |
|---|---|
collections:read:all |
Read all collections |
collections:read:own |
Read only own collections |
collections:read:team |
Read team collections |
collections:write:own |
Create/update own collections |
collections:delete:own |
Delete own collections |
documents:write:all |
Create/update any document |
documents:delete:team |
Delete team documents |
profile:read:own |
Read own profile |
jobs:control:own |
Control (start/stop) own jobs |
search:read:all |
Search across all resources |
Data Access Constraints
Data access constraints control how API handlers should filter data based on ownership.
Owner-Only Access (owner: true)
When owner: true is set, the scope grants access only to resources owned by the current user.
collections:read:own:
owner: true
description: "Read knowledge base for own collections"
endpoints:
- GET /kb/collections/own
- GET /kb/collections/own/:collectionID
API Implementation:
func GetCollections(c *gin.Context) {
authInfo := authorized.GetInfo(c)
query := db.Query("SELECT * FROM collections")
// Apply owner constraint
if authInfo.Constraints.OwnerOnly {
query = query.Where("user_id = ?", authInfo.UserID)
}
collections, _ := query.Get()
c.JSON(200, collections)
}
Team-Only Access (team: true)
When team: true is set, the scope grants access only to resources owned by the current team.
collections:read:team:
team: true
description: "Read knowledge base for team collections"
endpoints:
- GET /kb/collections/team
- GET /kb/collections/team/:collectionID
API Implementation:
func GetCollections(c *gin.Context) {
authInfo := authorized.GetInfo(c)
query := db.Query("SELECT * FROM collections")
// Apply team constraint
if authInfo.Constraints.TeamOnly {
query = query.Where("team_id = ?", authInfo.TeamID)
}
collections, _ := query.Get()
c.JSON(200, collections)
}
Combined Constraints
Both constraints can be applied:
documents:read:own:
owner: true
team: true # Can be used together
description: "Read own documents within team context"
endpoints:
- GET /kb/documents/own
API Implementation:
func GetDocuments(c *gin.Context) {
authInfo := authorized.GetInfo(c)
query := db.Query("SELECT * FROM documents")
// Apply constraints (OwnerOnly is more restrictive)
if authInfo.Constraints.OwnerOnly {
query = query.Where("user_id = ?", authInfo.UserID)
} else if authInfo.Constraints.TeamOnly {
query = query.Where("team_id = ?", authInfo.TeamID)
}
documents, _ := query.Get()
c.JSON(200, documents)
}
Complete Example
Let's create a complete scope configuration for a blog system.
Directory Structure
openapi/scopes/
├── scopes.yml
├── alias.yml
└── blog/
├── posts.yml
├── comments.yml
└── categories.yml
scopes.yml
default: deny
public:
- GET /blog/posts
- GET /blog/posts/:postID
- GET /blog/categories
endpoints:
# Read operations allowed for authenticated users
- GET /blog/* allow
# Write operations require specific scopes
- POST /blog/* deny
- PUT /blog/* deny
- DELETE /blog/* deny
blog/posts.yml
posts:read:all:
description: "Read all blog posts"
endpoints:
- GET /blog/posts
- GET /blog/posts/:postID
posts:read:own:
owner: true
description: "Read own blog posts"
endpoints:
- GET /blog/posts/own
- GET /blog/posts/own/:postID
posts:write:own:
owner: true
description: "Create and update own blog posts"
endpoints:
- POST /blog/posts
- PUT /blog/posts/:postID
- PATCH /blog/posts/:postID
posts:delete:own:
owner: true
description: "Delete own blog posts"
endpoints:
- DELETE /blog/posts/:postID
posts:write:all:
description: "Create and update any blog post (admin)"
endpoints:
- POST /blog/posts/admin
- PUT /blog/posts/admin/:postID
posts:delete:all:
description: "Delete any blog post (admin)"
endpoints:
- DELETE /blog/posts/admin/:postID
blog/comments.yml
comments:read:all:
description: "Read all comments"
endpoints:
- GET /blog/posts/:postID/comments
- GET /blog/comments/:commentID
comments:write:own:
owner: true
description: "Write own comments"
endpoints:
- POST /blog/posts/:postID/comments
- PUT /blog/comments/:commentID
comments:delete:own:
owner: true
description: "Delete own comments"
endpoints:
- DELETE /blog/comments/:commentID
comments:delete:all:
description: "Delete any comment (moderator)"
endpoints:
- DELETE /blog/comments/admin/:commentID
alias.yml
# Blog reader - can read all posts and comments
blog:reader:
- posts:read:all
- comments:read:all
# Blog author - can manage own posts and comments
blog:author:
- posts:read:all
- posts:write:own
- posts:delete:own
- comments:read:all
- comments:write:own
- comments:delete:own
# Blog moderator - can manage all comments
blog:moderator:
- posts:read:all
- comments:read:all
- comments:delete:all
# Blog admin - full access to all blog features
blog:admin:
- posts:read:all
- posts:write:all
- posts:delete:all
- comments:read:all
- comments:write:own
- comments:delete:all
Wildcard Scopes
Wildcard scopes allow flexible permission matching using * as a placeholder.
Syntax
system:root:
- "*:*:*" # Matches everything
blog:admin:
- "posts:*:*" # Matches all post operations at all levels
- "comments:*:*" # Matches all comment operations at all levels
kb:read:
- "collections:read:*" # Matches collections:read:all, collections:read:own, etc.
- "documents:read:*" # Matches documents:read:all, documents:read:own, etc.
Matching Rules
- Full wildcard (
*:*:*): Matches any scope - Resource wildcard (
posts:*:*): Matches any action and level for the resource - Action wildcard (
posts:read:*): Matches any level for the resource and action - No partial wildcards:
post*:read:allis NOT supported
Use Cases
- System root access:
*:*:*for system administrators - Resource administrators:
resource:*:*for resource-level admins - Grouped permissions:
resource:action:*for action-level permissions
Security Considerations
- Use wildcards sparingly
- Prefer explicit scope lists for most roles
- Reserve
*:*:*for system-level accounts only - Document wildcard usage clearly
- Consider restricted scopes to block specific actions even with wildcards
Best Practices
1. Scope Design
✅ DO:
- Use consistent naming conventions
- Group related scopes in the same file
- Provide clear descriptions for each scope
- Design scopes around resources and actions, not UI features
- Keep scopes granular but not too fine-grained
❌ DON'T:
- Mix different resources in one scope file
- Create scopes for every single endpoint
- Use vague or inconsistent naming
- Duplicate endpoint definitions across scopes
2. Permission Levels
Create a clear hierarchy of permission levels:
- Public (
public): No authentication required - Authenticated (
authenticated): Basic logged-in access - Owner (
own): User's own resources - Team (
team): Team's resources - All (
all): All resources (admin level)
3. Aliases
✅ DO:
- Create aliases for common user roles (viewer, editor, admin)
- Use aliases to group related scopes
- Document what each alias grants
- Keep alias names intuitive
❌ DON'T:
- Create single-scope aliases (use the scope directly)
- Nest aliases (aliases should reference scopes, not other aliases)
- Use ambiguous alias names
4. Data Constraints
✅ DO:
- Set
owner: truefor personal resource scopes - Set
team: truefor team resource scopes - Implement constraint checks in ALL relevant API handlers
- Return appropriate errors when constraints are violated
❌ DON'T:
- Rely solely on URL paths (
/own,/team) for access control - Skip constraint validation in database queries
- Assume constraints are enforced automatically
5. Endpoint Definitions
✅ DO:
- List all related endpoints for a scope
- Use consistent parameter naming (
:id,:userID,:collectionID) - Include all HTTP methods the scope covers
- Group similar endpoints together
❌ DON'T:
- Define the same endpoint in multiple scopes (unless intentional)
- Use inconsistent path formats
- Forget to include related endpoints
6. Testing
- Test each scope definition with real requests
- Verify data constraints are enforced correctly
- Test wildcard matching behavior
- Ensure public endpoints are accessible without auth
- Validate that denied endpoints return proper errors
7. Documentation
- Comment complex scope definitions
- Document the purpose of each alias
- Maintain a scope reference for developers
- Update documentation when scopes change
- Provide examples of scope usage in roles
Troubleshooting
Common Issues
Issue: Endpoint not accessible even with correct scope
Solution:
- Check if endpoint is in
scopes.ymldefault deny list - Verify scope name matches exactly (case-sensitive)
- Ensure endpoint path matches (check for typos, extra slashes)
- Verify HTTP method matches
Issue: Data constraint not working
Solution:
- Confirm
owner: trueorteam: trueis set in scope definition - Check if API handler reads
authInfo.Constraints - Verify database query applies constraint filters
- Ensure
authInfo.UserIDorauthInfo.TeamIDis populated
Issue: Wildcard scope not matching
Solution:
- Verify wildcard syntax (
*in correct position) - Check scope name format (must be
part1:part2:part3) - Ensure no typos in scope name parts
- Remember: wildcards only work with colon-separated scopes
Issue: Changes not taking effect
Solution:
- Restart the application to reload scope configurations
- Clear role cache:
role.RoleManager.ClearCache() - Verify YAML syntax is correct (use YAML validator)
- Check file is in correct directory
Reference
Related Files
- types.go: Scope configuration structures
- scope.go: Scope matching and validation logic
- README.md: ACL enforcement logic
- DESIGN.md: Overall ACL system design
Related Concepts
- OAuth 2.1 Scopes: Standard OAuth scope mechanism
- RBAC: Role-Based Access Control
- Data Constraints: Fine-grained data access control
- Endpoint Matching: Path pattern matching algorithm
Migration Guide
From Legacy Permissions
If migrating from a legacy permission system:
-
Map old permissions to scopes:
can_read_posts → posts:read:all can_edit_own_posts → posts:write:own can_delete_any_post → posts:delete:all -
Create scope definitions for each permission
-
Define aliases for existing roles:
role:editor: - posts:read:all - posts:write:own - posts:delete:own -
Update API handlers to check constraints
-
Migrate role assignments to use new scopes/aliases
-
Test thoroughly before deploying
Version Compatibility
- v1.0: Basic scope checking
- v1.1: Data constraints (
owner,team) - v1.2: Wildcard scopes, restricted scopes
Summary
Key points to remember:
- Three main files:
scopes.yml(global),alias.yml(aliases), resource files (scopes) - Naming convention:
resource:action:level - Data constraints: Use
owner: trueandteam: truefor data filtering - Aliases: Group scopes for easier role management
- Wildcards: Use
*for flexible matching, but sparingly - Testing: Always test scope configurations thoroughly
For more details, refer to: