Back to Openviking

ACL API

docs/en/api/12-acl.md

0.4.187.6 KB
Original Source

ACL API

The ACL API manages direct grants on shared viking://resources/... nodes and reports their inherited effective permissions. Private resources do not accept ACLs and must be moved into the shared scope to be shared.

Read Resource Access Control (ACL) for the permission and inheritance model.

Endpoint Summary

MethodPathDescription
GET/api/v1/acl?uri={uri}Get direct, inherited, and effective ACLs
PUT/api/v1/aclReplace the node's direct ACL
DELETE/api/v1/acl?uri={uri}Clear the node's direct ACL
POST/api/v1/acl/grantSet one principal's direct level
POST/api/v1/acl/revokeRemove one principal's direct grant

Every endpoint requires manage on the target node. Account ADMINs implicitly manage shared resources.

viking://resources is a fixed shared scope and cannot carry a direct ACL. The account setting acl.enabled defaults to false. While disabled, shared resources use the original public behavior and ACL authorization is skipped. When enabled, newly created shared files, directories, and add-resource roots grant the creator direct manage and inherit the parent ACL. Existing content without an ACL remains public. Descendants within an add-resource import only inherit the root grant.

Data Structures

ACL entry

json
{
  "principal": "user:bob",
  "level": "read"
}
FieldTypeDescription
principalstringuser:{user_id}, group:{group_id}, or user:*
levelstringread, write, or manage

The caller supplies the account-unique, stable group_id through the Admin API. A group has no separate display name. After a group is deleted, its old principal no longer matches any request unless the same group_id is created again.

ACL report

json
{
  "uri": "viking://resources/project-a",
  "acl_mode": "inherit",
  "direct_entries": [
    {"principal": "user:bob", "level": "read"}
  ],
  "inherited_entries": [
    {"principal": "group:engineering", "level": "write"}
  ],
  "effective_entries": [
    {"principal": "group:engineering", "level": "write"},
    {"principal": "user:bob", "level": "read"}
  ]
}
FieldDescription
direct_entriesEntries set directly on this node
inherited_entriesMerged direct ACLs from all ancestors
effective_entriesThe merged direct and inherited entries
acl_modenone when ACL does not control the node; inherit when direct and inherited ACLs apply; read-only and derived

The account ADMIN implicit manage permission is not included in these lists.

Get an ACL

GET /api/v1/acl?uri={uri}

GET can report an existing target that has no context record: direct_entries is empty and inherited permissions are resolved from existing ancestor contexts. Mutating ACL endpoints require a context record for the target.

bash
curl "http://localhost:1933/api/v1/acl?uri=viking%3A%2F%2Fresources%2Fproject-a" \
  -H "X-API-Key: your-key"

Python SDK

python
report = client.acl_get("viking://resources/project-a")

Go SDK

go
report, err := client.ACL(ctx, "viking://resources/project-a")

Replace a Direct ACL

PUT /api/v1/acl

Request body:

json
{
  "uri": "viking://resources/project-a",
  "entries": [
    {"principal": "user:bob", "level": "read"},
    {"principal": "group:engineering", "level": "write"}
  ]
}

entries completely replaces this node's direct ACL without changing direct ACLs on ancestors or descendants. Duplicate principals keep their highest level. An empty list is equivalent to deleting this node's direct ACL.

bash
curl -X PUT http://localhost:1933/api/v1/acl \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "uri": "viking://resources/project-a",
    "entries": [
      {"principal": "user:bob", "level": "read"},
      {"principal": "group:engineering", "level": "write"}
    ]
  }'

Python SDK

python
report = client.acl_set(
    "viking://resources/project-a",
    [
        {"principal": "user:bob", "level": "read"},
        {"principal": "group:engineering", "level": "write"},
    ],
)

The asynchronous client uses the same method name:

python
report = await client.acl_set(uri, entries)

Go SDK

go
report, err := client.SetACL(ctx, "viking://resources/project-a", []openviking.ACLEntry{
    {Principal: "user:bob", Level: "read"},
    {Principal: "group:engineering", Level: "write"},
})

CLI

bash
ov acl set viking://resources/project-a \
  --entry user:bob=read \
  --entry group:engineering=write

Set One Principal's Level

POST /api/v1/acl/grant
json
{
  "uri": "viking://resources/project-a",
  "principal": "user:bob",
  "level": "write"
}

This sets Bob's direct level on the current node to write. It updates an existing direct entry without changing other principals.

bash
curl -X POST http://localhost:1933/api/v1/acl/grant \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "uri": "viking://resources/project-a",
    "principal": "user:bob",
    "level": "write"
  }'
python
report = client.acl_grant(
    "viking://resources/project-a",
    principal="user:bob",
    level="write",
)
bash
ov acl grant viking://resources/project-a --principal user:bob --level write

Remove One Direct Grant

POST /api/v1/acl/revoke
json
{
  "uri": "viking://resources/project-a",
  "principal": "user:bob"
}

revoke removes only Bob's direct entry on the current node. Any permission inherited by Bob from an ancestor remains effective.

python
report = client.acl_revoke("viking://resources/project-a", principal="user:bob")
bash
ov acl revoke viking://resources/project-a --principal user:bob

Clear the Node's Direct ACL

DELETE /api/v1/acl?uri={uri}

This does not remove direct ACLs on descendants. The current node is recalculated from its ancestors, while each descendant continues to combine its own direct ACL with its ancestors.

bash
curl -X DELETE \
  "http://localhost:1933/api/v1/acl?uri=viking%3A%2F%2Fresources%2Fproject-a" \
  -H "X-API-Key: your-key"
python
report = client.acl_delete("viking://resources/project-a")
bash
ov acl rm viking://resources/project-a

Errors

The API checks manage permission before confirming existence to an authorized caller, preventing resource discovery through error types.

ScenarioError
URI is outside viking://resources/...INVALID_ARGUMENT
Caller lacks managePERMISSION_DENIED
Authorized caller targets a URI that does not existNOT_FOUND
ACL mutation targets a URI without a context recordINVALID_ARGUMENT; index it first
Invalid principal syntax or group:*INVALID_ARGUMENT
Level is not read/write/manageINVALID_ARGUMENT
Request includes read-only fields such as acl_modeINVALID_ARGUMENT

Direct and inherited ACL fields are both stored in context records. An update changes the target direct ACL and recalculates descendant inherited ACLs in one subtree batch; a failed write restores the previous context ACL fields.