# DocWright RBAC, Organisations & Collaboration API

This document is the contract for **identity, the organisation hierarchy, fine‑grained
access control, and collaboration** in DocWright. It is the foundation the BIM module (and
any multi‑team workflow) builds on.

All endpoints are versioned under `/api/v1`, speak JSON, and return
[RFC 7807](https://www.rfc-editor.org/rfc/rfc7807) `application/problem+json` on error. Every
response carries a correlation id so a failure is always traceable.

---

## 1. The model

```
Organisation                         (root; kind = "organization")
└── Org Unit                         (kind = "unit"; MAY nest — OU under OU, to depth 12)
    └── Org Unit … (nested)
        └── Team                     (kind = "team"; the collaborating leaf)
            ├── members              (users)
            └── placed on Projects   (every member inherits the team's project role)
```

Every node is a row in `groups` with a `kind` and a `parent_id`, so the whole tree reuses the
existing group/membership/quota machinery. Three ideas:

- **Nesting.** An organisation contains units; a unit may contain **further units** and/or
  teams. Teams are always leaves — the unit you actually collaborate as.
- **Membership.** A user belongs to any node via `user_groups`. Team membership is what turns
  into project access.
- **Scoped roles (fine‑grained RBAC).** A role is granted to a user **at a node** and is
  **inherited by that node's whole subtree**. This is how "admin" is confined to an OU.

### Roles

| Role | Typical use |
|---|---|
| `admin` | Full authority **within the scope it is granted at** (manage the subtree, its members, and grants). |
| `manager` | Manage collaboration/workflow, not identity. |
| `editor` | Create and edit documents. |
| `reviewer` | Comment and review. |
| `viewer` | Read‑only. |

Project collaboration uses a parallel, document‑control vocabulary: `owner`, `editor`,
`reviewer`, `viewer`.

---

## 2. The authorization model (contextual, deny‑by‑default)

Authority is **never global except for the one platform super‑admin.** Every hierarchy
operation is checked **against the target node**:

```
canAt(user, node, permission) =
      user holds `permission` GLOBALLY            (platform super‑admin — user_roles)
   OR a role granting `permission` is scoped to    (org_role_grants on the node's
      the node or ANY of its ancestors              ancestor chain)
```

Consequences, by design:

- **Admin of the "Engineering" unit** can manage Engineering and everything beneath it —
  create sub‑units and teams, add members, grant roles, rename/delete within the subtree —
  and **nothing** above it (its parent org) or beside it (a sibling unit).
- **The platform super‑admin** (the single global `admin` role, e.g. `jsp@jsp.net.in`) has
  authority everywhere and is the only principal who may create top‑level organisations or
  platform‑wide users.
- **Deny‑by‑default.** With no grant on the ancestor chain, a caller has no access. To avoid
  leaking the shape of the tree, an unauthorised request to a node returns **404**, not 403.
- **No escalation.** Because a grant is always made *at a node the caller already administers*,
  a scoped admin can delegate within their subtree but can never mint authority above it, and
  can never create a *global* grant (that path is platform‑admin only).

Authentication is by **scoped API key** (`X-API-Key: dw_…` or `Authorization: Bearer dw_…`)
or by **browser session**. Hierarchy/RBAC routes require only authentication at the edge;
the fine‑grained check happens inside each handler against the node.

---

## 3. Endpoints

### 3.1 Who am I / what may I do

```
GET /api/v1/me/access
```
Returns the caller's effective access — use it to drive what an admin UI renders.
```json
{ "data": {
  "user_id": 1,
  "platform_admin": true,
  "global_roles": ["admin"],
  "scoped_grants": [ { "group_id": 42, "node": "Engineering", "kind": "unit", "role": "admin", "granted_at": "…" } ]
} }
```

### 3.2 The organisation tree

| Method & path | Authority | Purpose |
|---|---|---|
| `GET /org/tree` | any authenticated | The tree **pruned to what the caller may see** (platform admin: everything; others: the subtrees they hold a grant on, plus the path to the root for context). |
| `GET /org/nodes/{id}` | node‑admin or member | Node detail: `{ node, members, grants }`. |
| `POST /org/organizations` | **platform admin** | Create a top‑level organisation. Body `{ name, description? }`. |
| `POST /org/nodes/{id}/units` | node‑admin(`id`) | Create a **nested** unit under an org or unit. Body `{ name, description? }`. |
| `POST /org/nodes/{id}/teams` | node‑admin(`id`) | Create a team under an org or unit. Body `{ name, description? }`. |
| `PATCH /org/nodes/{id}` | node‑admin(`id`) | Rename. Body `{ name }`. |
| `DELETE /org/nodes/{id}` | node‑admin(`id`) | Delete the node **and its subtree** (cascades members, grants, team placements). |

### 3.3 Membership

| Method & path | Authority | Purpose |
|---|---|---|
| `POST /org/nodes/{id}/members` | node‑admin(`id`) | Add a user. Body `{ user_id }`. |
| `DELETE /org/nodes/{id}/members/{user}` | node‑admin(`id`) | Remove a user. |

### 3.4 Scoped role grants (fine‑grained RBAC)

| Method & path | Authority | Purpose |
|---|---|---|
| `POST /org/nodes/{id}/grants` | node‑admin(`id`) | Grant a role to a user, **scoped to `id` and its subtree.** Body `{ user_id, role }`. |
| `DELETE /org/nodes/{id}/grants` | node‑admin(`id`) | Revoke. Body `{ user_id, role }`. |
| `GET /users/{id}/grants` | self or platform admin | Every scoped grant a user holds. |

> **Example — make Alice admin of Engineering only:**
> `POST /org/nodes/42/grants  { "user_id": 7, "role": "admin" }`
> Alice can now administer node 42 and everything under it — and nothing else.

### 3.5 Users

| Method & path | Authority | Purpose |
|---|---|---|
| `GET /admin/users` | **platform admin** | List users. |
| `POST /users` | platform admin, **or** node‑admin(`org_id`) | Create a user. |
| `GET /users/{id}/grants` | self or platform admin | See §3.4. |

`POST /api/v1/users` body:

| Field | Req. | Notes |
|---|---|---|
| `username` | ✓ | Unique; `409` if taken. |
| `email` | – | |
| `name` | – | Display name (defaults to `username`). |
| `password` | – | If omitted, a strong one is generated and returned **once** as `generated_password`. |
| `external` | – | `true` marks a reduced‑trust external collaborator (`is_external`). |
| `org_id` | – | **The pivot.** If present, the caller need only be **node‑admin of `org_id`**: the new user is created, granted `role` (below) scoped to `org_id`, and added as a member. If **absent**, this is a platform‑wide account and requires the **platform admin**. |
| `role` | – | Scoped role when `org_id` is given (default `editor`, or `viewer` for external). |

This is how an OU‑admin onboards their own people without any platform‑wide privilege.

### 3.6 Project collaborators

Collaboration is managed by the **project owner** (or the platform admin). A collaborator is
either an individual user **or a whole team** — placing a team grants every current and future
member the chosen role.

| Method & path | Purpose |
|---|---|
| `GET /projects/{slug}/collaborators` | `{ members: […], teams: […] }`. |
| `POST /projects/{slug}/collaborators` | Add. Body `{ user_id \| team_id, role }` (`role` ∈ owner/editor/reviewer/viewer). |
| `DELETE /projects/{slug}/collaborators/team/{team}` | Remove a team. |
| `DELETE /projects/{slug}/collaborators/user/{user}` | Remove an individual. |

Effective project role = the **strongest** role across the caller's direct membership and all
teams they belong to that are placed on the project. External collaborators and anonymous
link‑sharing (`share_links`) compose with this — an external user only ever sees the specific
projects they are added to.

---

## 4. CLI (same operations, before/without the UI)

```
bin/console org tree
bin/console org new-org "Acme"
bin/console org new-unit  <parentNodeId> "Engineering"      # parent may be an org OR a unit (nesting)
bin/console org new-team  <parentNodeId> "BIM"
bin/console org add-member <nodeId> <userId|username>
bin/console org grant     <userId|username> <nodeId> <role> # scoped grant (role + subtree)
bin/console org revoke    <userId|username> <nodeId> <role>
bin/console org whoami    <userId|username> [nodeId]        # effective access, incl. "admin at node?"
bin/console org place-team <projectSlug> <teamId> [role]
bin/console org collaborators <projectSlug>
bin/console user:create   <username> [--email=] [--role=editor] [--password=]
```

---

## 5. Worked example

```bash
# Platform admin builds the org and delegates.
bin/console org new-org "Acme"                     # → org #10
bin/console org new-unit 10 "Engineering"          # → unit #11
bin/console org new-unit 11 "Platform"             # nested OU → unit #12
bin/console org new-team 12 "BIM"                  # → team #13
bin/console org grant alice 11 admin               # Alice = admin of Engineering (11) + subtree

# Alice (only an Engineering admin) now onboards + collaborates, via the API with her key:
POST /api/v1/users                 { "username":"bob", "org_id":12, "role":"editor" }   # 201
POST /api/v1/org/nodes/13/members  { "user_id": <bob> }                                 # 201 (BIM team)
POST /api/v1/projects/bridge-model/collaborators { "team_id":13, "role":"editor" }      # 201

# Alice cannot reach outside Engineering:
POST /api/v1/org/organizations     { "name":"Rogue" }          # 403 (platform‑admin only)
POST /api/v1/org/nodes/10/units    { "name":"Nope" }           # 404 (10 is her ancestor org)
```

---

## 6. Data model (reference)

- `groups(id, name, description, kind, parent_id, …)` — the hierarchy (`kind` ∈
  `organization|unit|team|group`).
- `user_groups(user_id, group_id)` — membership.
- `org_role_grants(user_id, group_id, role_id, granted_by, granted_at)` — **scoped** grants;
  a grant at `group_id` applies to its whole subtree.
- `project_members(project_id, user_id, role)` — individual collaborators.
- `project_teams(project_id, group_id, role, added_by)` — team collaborators.
- `users.is_external` — reduced‑trust external accounts.
- `share_links(token, project_id, scope, allow_anonymous, expires_at, max_uses, …)` — link sharing.

Roles/permissions themselves live in `roles`, `permissions`, `role_permissions`; a role's
permissions are what a scoped grant confers within its subtree.
