Skip to content

[Feature]: ACL and Custom Role Support #988

@ruanrunxue

Description

@ruanrunxue

Problem Statement

OpenViking has implemented a basic multi-tenant RBAC system, but there are limitations in permission management:

  • Fixed Roles: Only three built-in roles (ROOT, ADMIN, USER) are supported, which cannot meet complex enterprise permission requirements
  • No Custom Permissions: Cannot create business-related roles like developer, viewer, project-manager
  • No ACL: Current permission control is only based on space (user data space), cannot implement cross-user resource sharing

Proposed Solution

Goal: Extend on existing RBAC to support custom roles and ACL permission control.

Constraints:

  • Existing user code works without modification (backward compatible)
  • Admin can manage custom roles
  • ACL supports directory-level sharing (inheritance)
  • Storage uses AGFS JSON files

1. Overall Architecture

Request
  │
  ▼
[Auth Middleware] ── Parse API Key → (account_id, user_id, role)
  │
  ▼
[RBAC Guard] ── Check operation permissions by role (require_role)
  │
  ▼
[RequestContext] ── UserIdentifier + Role injected as FastAPI dependency
  │
  ▼
[Router] ── Pass RequestContext to Service
  │
  ▼
[Service Layer] ── Request-level user context
  │
  ├─► [VikingFS] ── Singleton, _uri_to_path isolates by account_id, _is_accessible filters permissions
  │     │
  │     └─► [ACL Manager] ── Locate authorization list by owner_space, check space/role authorization
  │
  └─► [VectorDB] ── Single collection, query injects account_id + owner_space filter
       │
       └─► [Role Manager] ── Role registry management (create/delete custom roles)

[Admin API] ── Role management + ACL management
  │
  ├─► /accounts/{account_id}/roles ── Role CRUD
  └─► /accounts/{account_id}/acls ── ACL sharing CRUD

Core Principles:

  • Role Extension: Role changed from Enum to str type alias, supports built-in and custom roles
  • ACL Dual Authorization: Supports grantee_space (user space) and grantee_role (role) two authorization dimensions
  • ACL Organized by Owner: acls indexed by owner_space dimension, efficient query
  • Backward Compatible: Original permission check logic takes priority, ACL as extension layer

2. Permission Check Flow

RequestContext
     │
     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    VikingFS._is_accessible(uri, ctx)                    │
├─────────────────────────────────────────────────────────────────────────┤
│  1. Role check: if role in (ROOT, ADMIN) → allow                        │
│  2. Scope check: if scope in (resources, temp) → allow                  │
│  3. Space check: if uri's space == user's space → allow                │
│  4. ACL check: ┌─────────────────────────────────────────────────────┐ │
│                 │ 4.1 Parse owner_space from URI                      │ │
│                 │     - user/agent/session scope → extract space      │ │
│                 │     - resources scope → skip ACL (public resource) │ │
│                 │                                                     │ │
│                 │ 4.2 Lookup ACL: acls[owner_space]                    │ │
│                 │     - not found → no permission                     │ │
│                 │                                                     │ │
│                 │ 4.3 Iterate owner's sharing list:                   │ │
│                 │     for acl in acls[owner_space]:                   │ │
│                 │       if uri.startswith(acl.path):                 │ │
│                 │         for entry in acl.entries:                  │ │
│                 │           if grantee_space in ctx.spaces:           │ │
│                 │             if permission in role_perms: return T   │ │
│                 │           if grantee_role == ctx.role:              │ │
│                 │             if permission in role_perms: return T   │ │
│                 └─────────────────────────────────────────────────────┘ │
│  5. Default deny: return False                                          │
└─────────────────────────────────────────────────────────────────────────┘

3. Role Management

3.1 Role Types

Two-layer role system:

  • Built-in Roles: ROOT, ADMIN, USER (cannot be deleted, as system foundation)
  • Custom Roles: Created and managed by Admin, e.g., developer, viewer, project-manager

Role permissions defined via permissions field:

  • read: Read permission
  • write: Read-write permission (includes read)
  • delete: Delete permission
  • admin: Admin permission

3.2 Role Storage

  • Root Key: ov.conf server section (static config)
  • Per-account role definitions: /{account_id}/_system/roles.json
  • User role assignments: /{account_id}/_system/users.json

Storage structure example:

// /acme/_system/roles.json —— Role definitions
{
    "roles": {
        "developer": { "description": "Developer", "permissions": ["read", "write"], "created_by": "alice" },
        "viewer": { "description": "Viewer", "permissions": ["read"], "created_by": "alice" }
    }
}

// /acme/_system/users.json —— User registry (extended role field)
{
    "users": {
        "alice": { "role": "admin", "key": "..." },
        "bob": { "role": "developer", "key": "..." },
        "charlie": { "role": "viewer", "key": "..." }
    }
}

3.3 Admin API

POST   /api/v1/admin/accounts/{account_id}/roles      Create custom role (ROOT, ADMIN)
GET    /api/v1/admin/accounts/{account_id}/roles      List all roles (ROOT, ADMIN)
PUT    /api/v1/admin/accounts/{account_id}/roles/{role_id} Update role (ROOT, ADMIN)
DELETE /api/v1/admin/accounts/{account_id}/roles/{role_id} Delete custom role (ROOT, ADMIN)

4. ACL Management

4.1 Authorization Dimensions

ACL supports two authorization dimensions:

  • grantee_space: Authorize to specific user space (user_space_name or agent_space_name)
  • grantee_role: Authorize to specific role (any valid role, including custom roles)

Permission levels:

  • read: Read-only access
  • write: Read-write access (includes read)

4.2 ACL Storage

Design Consideration: ACL organized by authorizer (owner_space) dimension, not by path.

Advantages:

  • Efficient Query: Given URI, directly locate the user's authorization list from acls[owner_space], avoid traversing entire ACL table
  • Intuitive Management: Viewing "what resources a user shared" is more natural than "who accessed a resource"

File: /{account_id}/_system/acls.json

{
    "acls": {
        "alice_space": [
            {
                "path": "viking://resources/project-alpha/",
                "entries": [
                    { "grantee_space": "bob_space", "permission": "read" },
                    { "grantee_role": "developer", "permission": "write" }
                ]
            },
            {
                "path": "viking://agent/coding-agent/",
                "entries": [
                    { "grantee_space": "charlie_agent_space", "permission": "read" }
                ]
            }
        ],
        "bob_space": [
            {
                "path": "viking://resources/bob-private/",
                "entries": [
                    { "grantee_role": "viewer", "permission": "read" }
                ]
            }
        ]
    }
}

Permission Check Flow:

User accesses URI: viking://resources/project-alpha/README.md
  │
  ▼
1. Parse owner_space from URI:
   - resources scope → no space, belongs to account public resource
   - Need to infer owner from URI path (see below)
  │
  ▼
2. Lookup ACL: acls[owner_space]
  │
  ▼
3. Iterate owner's sharing list:
   - Check uri.startswith(acl.path)
   - Check grantee_space or grantee_role in entries
   - Match → return corresponding permission
  │
  ▼
4. No match → return no permission

URI to owner_space Mapping:

URI Pattern owner_space Resolution
viking://resources/... Account-level public resource, no single owner, skip ACL check
viking://user/{space}/... {space} is owner_space
viking://agent/{space}/... {space} is owner_space
viking://session/{space}/... {space} is owner_space

4.3 Admin API

POST   /api/v1/admin/accounts/{account_id}/acls      Create sharing (ROOT, ADMIN)
GET    /api/v1/admin/accounts/{account_id}/acls      List all ACLs (ROOT, ADMIN)
DELETE /api/v1/admin/accounts/{account_id}/acls      Delete sharing (ROOT, ADMIN)

5. Existing Code Changes

5.1 identity.py

Change: Role from Enum to str type alias

# Before
class Role(str, Enum):
    ROOT = "root"
    ADMIN = "admin"
    USER = "user"

# After
Role = str  # Type alias

ROOT = "root"
ADMIN = "admin"
USER = "user"

BUILTIN_ROLES = {ROOT, ADMIN, USER}

5.2 api_keys.py

New:

  • Integrate RoleManager
  • is_valid_role(account_id, role) method
  • get_role_permissions(account_id, role) method

Change:

  • register_user() supports any valid role

5.3 viking_fs.py

New:

  • _acl_manager attribute
  • set_acl_manager() method
  • _get_role_permissions() method

Change:

  • _is_accessible() adds ACL check logic

5.4 app.py

New:

  • Initialize RoleManager and ACLManager
  • Load all accounts' roles and ACLs

5.5 admin.py

New endpoints:

  • /accounts/{account_id}/roles - GET, POST, PUT, DELETE
  • /accounts/{account_id}/acls - GET, POST, DELETE

6. Key Files

File Change Type Description
openviking/server/identity.py Modify Role to str type, add built-in role constants
openviking/server/role_manager.py New Role registry management
openviking/server/acl_manager.py New ACL permission management
openviking/server/api_keys.py Modify Integrate RoleManager
openviking/server/auth.py Modify Permission check extension
openviking/storage/viking_fs.py Modify _is_accessible integrate ACL
openviking/server/app.py Modify Initialize RoleManager and ACLManager
openviking/server/routers/admin.py Modify Add role/ACL endpoints

7. Implementation Order

Phase 1: Role System

T1: Identity Type Definition Update

Modify openviking/server/identity.py

  • Role from Enum to str type alias
  • Add built-in role constants ROOT, ADMIN, USER
  • Add BUILTIN_ROLES set

T2: RoleManager

New openviking/server/role_manager.py

  • Load/save roles.json
  • create_role, delete_role, get_roles, is_valid_role, get_permissions

T3: APIKeyManager Integration

Modify openviking/server/api_keys.py

  • Integrate RoleManager
  • register_user supports custom roles

T4: Admin Router Role Endpoints

Modify openviking/server/routers/admin.py

  • GET/POST/PUT/DELETE /accounts/{account_id}/roles

Phase 2: ACL System

T5: ACLManager

New openviking/server/acl_manager.py

  • Load/save acls.json
  • grant, revoke, check_access, delete_by_path

T6: VikingFS Integration

Modify openviking/storage/viking_fs.py

  • Add _acl_manager
  • _is_accessible adds ACL check

T7: App Initialization Integration

Modify openviking/server/app.py

  • Initialize RoleManager and ACLManager
  • Inject into VikingFS

T8: Admin Router ACL Endpoints

Modify openviking/server/routers/admin.py

  • GET/POST/DELETE /accounts/{account_id}/acls

8. Verification Plan

T1: Role System Tests

  • Create custom role "developer"
  • Verify users can be assigned custom roles
  • Verify custom role login has correct permissions
  • Verify cannot delete built-in roles
  • Verify ADMIN can manage custom roles
  • Verify ADMIN can only operate on their own account

T2: ACL Tests

  • alice shares viking://resources/project-alpha/ to bob (space authorization)
  • alice shares directory to "developer" role (role authorization)
  • bob uses space authorization to access shared directory → success
  • User with "developer" role accesses role-authorized directory → success
  • Unauthorized user access → 403
  • After revoking authorization, access denied

T3: Regression Tests

  • Existing tests adapt to new Role type
  • Dev mode (no root_api_key) works normally
  • Existing users (role=admin/user) login normally

9. Backward Compatibility

  • roles.json and acls.json are optional files, created automatically if not exist
  • Existing role field in users.json continues to work
  • VikingFS original permission check logic takes priority, ACL as extension layer
  • Dev mode (no root_api_key) not affected

Alternatives Considered

use VectorDB to storage acl entity

Feature Area

Storage/VectorDB

Use Case

Scenario 1: Project Team Sharing

Background

  • account: acme
  • alice is project lead (admin)
  • bob, charlie are developers (developer)
  • david is tester (tester)

Steps

1. alice creates roles
   POST /api/v1/admin/accounts/acme/roles
   { "role_id": "developer", "description": "Developer", "permissions": ["read", "write"] }

   POST /api/v1/admin/accounts/acme/roles
   { "role_id": "tester", "description": "Tester", "permissions": ["read"] }

2. alice registers users and assigns roles
   POST /api/v1/admin/accounts/acme/users { "user_id": "bob", "role": "developer" }
   POST /api/v1/admin/accounts/acme/users { "user_id": "charlie", "role": "developer" }
   POST /api/v1/admin/accounts/acme/users { "user_id": "david", "role": "tester" }

3. alice creates shared directories
   POST /api/v1/admin/accounts/acme/acls
   { "path": "viking://resources/project-alpha/", "grantee_role": "developer", "permission": "write" }

   POST /api/v1/admin/accounts/acme/acls
   { "path": "viking://resources/project-alpha/", "grantee_role": "tester", "permission": "read" }

4. bob (developer) accesses project directory
   - has write permission → can read/write

5. david (tester) accesses project directory
   - has read permission → read-only

Scenario 2: Cross-User Resource Sharing

Background

  • alice has private materials to share with bob
  • Not through role, but direct authorization to user

Steps

1. alice shares private directory with bob
   POST /api/v1/admin/accounts/acme/acls
   { "path": "viking://user/alice_space/docs/", "grantee_space": "bob_space", "permission": "read" }

2. bob accesses alice's private directory
   GET /api/v1/fs/ls?uri=viking://user/alice_space/docs/
   → 200 OK (gains permission via ACL)

3. charlie attempts to access alice's private directory
   GET /api/v1/fs/ls?uri=viking://user/alice_space/docs/
   → 403 Forbidden (no ACL authorization)

Scenario 3: Agent Knowledge Sharing

Background

  • alice trained a coding-agent, wants to share with team members
  • bob and charlie can both use alice's agent

Steps

1. alice shares agent directory
   POST /api/v1/admin/accounts/acme/acls
   { "path": "viking://agent/coding-agent/", "grantee_space": "bob_agent_space", "permission": "read" }
   
   POST /api/v1/admin/accounts/acme/acls
   { "path": "viking://agent/coding-agent/", "grantee_space": "charlie_agent_space", "permission": "read" }

2. bob uses alice's agent
   GET /api/v1/fs/ls?uri=viking://agent/coding-agent/
   → 200 OK (accesses alice's agent space via ACL)

Scenario 4: Permission Revocation

Background

  • david transfers from developer to viewer
  • Need to revoke developer role permissions

Steps

Option 1: Modify user role
   PUT /api/v1/admin/accounts/acme/users/david/role
   { "role": "viewer" }

Option 2: Delete ACL authorization
   DELETE /api/v1/admin/accounts/acme/acls
   { "path": "viking://resources/project-alpha/", "grantee_space": "david_space" }

Scenario 5: Temporary Authorization

Background

  • External auditor eve needs temporary access to certain resources
  • Create temporary role or short-term ACL

Steps

1. alice creates temporary role
   POST /api/v1/admin/accounts/acme/roles
   { "role_id": "auditor", "description": "Temporary Auditor", "permissions": ["read"] }

2. alice registers temporary user
   POST /api/v1/admin/accounts/acme/users { "user_id": "eve", "role": "auditor" }

3. alice authorizes audit scope
   POST /api/v1/admin/accounts/acme/acls
   { "path": "viking://resources/audit-2026Q1/", "grantee_role": "auditor", "permission": "read" }

4. After audit completes, delete user and role
   DELETE /api/v1/admin/accounts/acme/users/eve
   DELETE /api/v1/admin/accounts/acme/roles/auditor


### Example API (Optional)

```python

Additional Context

No response

Contribution

  • I am willing to contribute to implementing this feature

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions