Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Permissions and ACLs

ScaiDrive's permission model is Discretionary Access Control with inheritance — the same shape as Windows NTFS. Every file and folder can have its own ACL with allow/deny entries granting specific permissions to users and groups. ACLs inherit from the parent unless explicitly broken.

The six permissions#

Permission Flag value Meaning
READ 1 View contents, list folder entries, read metadata
WRITE 2 Modify contents, rename, update metadata
DELETE 4 Move to trash
CREATE 8 Create new items inside (folders/files)
SHARE 16 Create external share links
MANAGE_PERMISSIONS 32 Modify the ACL, transfer ownership

Permissions are modeled as a bitfield. A principal with READ | WRITE = 3 can read and write but not delete. The API represents permissions as arrays of strings in request bodies (["READ", "WRITE"]) and as integer bitfields in effective-permission responses.

The resolution algorithm#

When a user attempts an action, ScaiDrive computes their effective permissions on the target resource:

flowchart TD Q["User wants permission P<br/>on resource R"] --> A{"Global admin?<br/>super_admin or<br/>tenant_admin"} A -- yes --> ALLOW(["Allow"]) A -- no --> SR{"Share role grants P?<br/>(owner/admin/contributor/reader)"} SR -- yes --> ACL{"Resource has ACL?"} SR -- no --> ACL2{"Resource has ACL?"} ACL2 -- no --> DENY(["Deny"]) ACL2 -- yes --> ALLOWS{"Allow ACE grants P?<br/>(direct, group, or Everyone;<br/>inherited from parents)"} ALLOWS -- yes --> DCHK ALLOWS -- no --> DENY ACL -- no --> ALLOW ACL -- yes --> DCHK{"Any deny ACE matches<br/>this user + P?"} DCHK -- yes --> DENY DCHK -- no --> ALLOW
  1. Check global admin. If the user has super_admin or tenant_admin role (database-stored, not JWT-only), grant all permissions.
  2. Check share membership. Determine the user's role in the share (via direct membership or any group they're in). owner and admin get all permissions; contributor gets READ | WRITE | DELETE | CREATE; reader gets only READ.
  3. Apply ACL. If the resource has an ACL, evaluate ACEs:
    • Deny entries are collected first — any explicit deny overrides allow.
    • Allow entries are OR'd together.
    • An ACE matches if its principal is the user, a group the user is in, or Everyone.
    • If the ACL is set to inherit from the parent, walk up to the parent folder and include its ACEs.
  4. Combine. Effective = (share role permissions ∪ allow ACEs) – deny ACEs.

This mirrors NTFS: deny beats allow, explicit beats inherited, and inheritance walks up the tree.

Checking permissions#

To ask "can this user do X on resource Y?":

bash
1
2
3
4
5
curl -G $SCAIDRIVE_URL/api/v1/permissions/check \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-urlencode "resource_type=file" \
  --data-urlencode "resource_id=fil_01J3K" \
  --data-urlencode "permission=WRITE"
python
1
2
3
4
5
6
resp = httpx.get(
    f"{url}/api/v1/permissions/check",
    headers={"Authorization": f"Bearer {token}"},
    params={"resource_type": "file", "resource_id": "fil_01J3K", "permission": "WRITE"},
)
print(resp.json()["allowed"])
typescript
1
2
3
4
5
6
7
8
9
const params = new URLSearchParams({
  resource_type: "file",
  resource_id: "fil_01J3K",
  permission: "WRITE",
});
const resp = await fetch(`${url}/api/v1/permissions/check?${params}`, {
  headers: { Authorization: `Bearer ${token}` },
});
const { allowed } = await resp.json();

Or get the full set of effective permissions in one call:

bash
1
2
3
4
curl -G $SCAIDRIVE_URL/api/v1/permissions/effective \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-urlencode "resource_type=folder" \
  --data-urlencode "resource_id=fld_01J3M"
json
1
2
3
4
5
6
7
8
{
  "can_read": true,
  "can_write": true,
  "can_delete": false,
  "can_create": true,
  "can_share": false,
  "can_manage_permissions": false
}

For UI rendering (showing/hiding buttons), batch checks reduce round trips:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -X POST $SCAIDRIVE_URL/api/v1/permissions/check/batch \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "checks": [
      {"resource_type": "file", "resource_id": "fil_A", "permission": "WRITE"},
      {"resource_type": "file", "resource_id": "fil_B", "permission": "DELETE"},
      {"resource_type": "folder", "resource_id": "fld_C", "permission": "CREATE"}
    ]
  }'

ACL entries#

An ACL is a list of ACEs (Access Control Entries). Each ACE has:

Field Values
principal_type user, group, everyone
principal_id usr_..., grp_..., or "everyone"
permissions Array of permission names, e.g., ["READ", "WRITE"]
ace_type allow or deny
inherit_to_children true — ACE applies to this resource and descendants. false — applies only here
inherited Server-set. true if this ACE came from an ancestor, false if set directly

Reading the ACL#

bash
1
2
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "resource_type": "folder",
  "resource_id": "fld_01J3M",
  "inherit_from_parent": true,
  "entries": [
    {
      "id": "ace_01J3N",
      "principal_type": "group",
      "principal_id": "grp_01J3L",
      "principal_name": "Engineering",
      "permissions": ["READ", "WRITE", "CREATE", "DELETE"],
      "ace_type": "allow",
      "inherited": false,
      "inherit_to_children": true
    },
    {
      "id": "ace_01J3P",
      "principal_type": "user",
      "principal_id": "usr_01J4A",
      "principal_name": "External Contractor",
      "permissions": ["WRITE", "DELETE"],
      "ace_type": "deny",
      "inherited": true,
      "inherit_to_children": true
    }
  ]
}

Adding an ACE#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -X POST $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "principal_type": "user",
    "principal_id": "usr_01J5B",
    "permissions": ["READ"],
    "ace_type": "allow",
    "inherit_to_children": true
  }'

You need MANAGE_PERMISSIONS on the resource to modify its ACL.

Breaking inheritance#

By default, a resource's ACL is "the parent's ACL plus anything set directly here." To stop inheriting:

bash
1
2
3
4
5
6
7
curl -X PUT $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M/inheritance \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inherit_from_parent": false,
    "copy_inherited": true
  }'

copy_inherited: true copies the inherited ACEs into this resource as direct entries before breaking — the effective permissions don't change immediately, but further ancestor modifications stop propagating. copy_inherited: false drops inherited rights entirely.

To restore inheritance:

bash
1
2
3
4
curl -X PUT $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M/inheritance \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inherit_from_parent": true}'

Deny overrides allow#

Classic NTFS rule: if any ACE denies a permission, the user doesn't have it, regardless of other allows.

Example: user alice is in group engineering which has WRITE allowed on a folder. A direct deny WRITE ACE on alice overrides — she can't write, even though her group can.

This lets you carve exceptions without restructuring group membership.

Ownership is separate from permissions#

A resource has an owner_id (user or group). The owner has MANAGE_PERMISSIONS implicitly, whether or not it's in the ACL — owners can always change who has access.

Ownership can be transferred via POST /api/v1/permissions/ownership/{resource_type}/{resource_id}/transfer.

Common pitfall: inheritance is tree, not DAG#

Each resource has exactly one parent. ACL inheritance walks up the tree and stops at the share. It doesn't follow move operations — if you move a file to a new folder, the file's inherited ACEs change.

If you need a resource that inherits from multiple sources (rare), break inheritance and set the ACEs explicitly.

What's next#

Updated 2026-05-18 15:04:12 View source (.md) rev 2