{"id":21384667,"url":"https://github.com/p2panda/access-control","last_synced_at":"2026-01-03T12:04:49.531Z","repository":{"id":232835695,"uuid":"783605123","full_name":"p2panda/access-control","owner":"p2panda","description":"Capability based Access Control for p2panda","archived":false,"fork":false,"pushed_at":"2024-05-04T05:41:47.000Z","size":58,"stargazers_count":4,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-22T23:41:17.768Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://p2panda.org/blog/2024/04/08/capabilities","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/p2panda.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-08T08:11:42.000Z","updated_at":"2024-05-04T05:41:50.000Z","dependencies_parsed_at":"2024-04-11T19:11:56.902Z","dependency_job_id":"a42034ad-9e8a-4cd4-816a-0d8a9b9d710e","html_url":"https://github.com/p2panda/access-control","commit_stats":null,"previous_names":["p2panda/access-control"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p2panda%2Faccess-control","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p2panda%2Faccess-control/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p2panda%2Faccess-control/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p2panda%2Faccess-control/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/p2panda","download_url":"https://codeload.github.com/p2panda/access-control/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243864626,"owners_count":20360355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-22T11:42:30.142Z","updated_at":"2026-01-03T12:04:44.509Z","avatar_url":"https://github.com/p2panda.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Capability based Access Control in `p2panda` [under review]\n\n# Introduction\n\nThis document describes an access control system for the [p2panda protocol](https://p2panda.org) which gives peers fine grained control over who can read their data, and what actions they are authorized to perform on it. The system must take account of the particular challenges faced as a result of supporting offline-first functionality and delay tolerant networking.\n\n# Background\n\nPeer-to-peer protocols such as p2panda use public-key cryptography and digital signatures to establish the identity of peers and the authenticity of messages replicated on a network. Any peer can verify that messages it receives were created by the claimed peer (public key), and that they have not been tampered with by any third parties. This allows peers to replicate messages freely, safe in the knowledge that if tampering occurs it can be detected.\n\nWhat if we don't want to share data with anyone though? Even if we know it won't be tampered with, we may want a stricter system where an author has additional control over where their data can travel, and how peers are allowed to interact with it. If we understand \"authorship\" as equal to \"ownership\" then we can say that only an owner has \"read\" and \"write\" authority over the data they created (\"write\" authority being relevant to long-lived mutable data).\n\nThis results in a system with strict restrictions on who has access to data on the network, in fact in the scenario described above no data would travel on the network at all, which in healthy and collaborative communities is of course not the desired behavior. In this document we will describe mechanisms for opening up such a system, with a protocol for delegating fine-grained and bounded access control to other peers.\n\n# Overview\n\nFollowing design patterns found in the field of capability-based security we describe a token based capability system with which peers can delegate the authority to access and perform actions on their owned resources. The features we require are very close to those provided by [UCAN](https://ucan.xyz/)s, although our implementation will not be inter-operable, we have incorporated their specification into our designs where possible.\n\n## Notable challenges\n\np2panda is a peer-to-peer protocol with first class support for offline-first functionality. We make very few assumptions about the nature of the network messages are carried on, delays and temporary partitions are expected (a property often referred to as delay tolerance) and messages may arrive out-of-order. Accounting for these properties presents some challenges when designing a suitable access control system.\n\nIn centralized, and many decentralized, access control systems it is assumed that a connection between the \"authorizer\" and \"requester\" is present at the moment when an action request is being made. In a peer-to-peer system where peers are often offline and messages are gossiped through the network this assumption cannot be made. Equally, access to resources is typically limited to certain time ranges, however in a delay tolerant setting authorized messages may be received many days, weeks, or even months after they were created. Our system offers solutions for these challenges while retaining the security guarantees expected from systems following least-authority principals.\n\n## Design goals\n\n- authors have fine-grained control over who can access and perform actions on their data\n- delay tolerant networking and offline-first operation supported\n- authority can be delegated and revoked\n- role based access control patterns can be modeled\n- hierarchical and \"flat\" ownership structures can be modeled\n- modular system with features which can be incrementally supported\n\n## Supported access control structures\n\nA requirement of our system is that it allows developers to build apps which model both \"flat\" and hierarchical ownership structures with suitable access control boundaries.\n\nThe below diagram sketches the access control boundaries for a festival and it's schedule of events. The application data has hierarchical ownership structure. There is an admin group which owns the festival and collection of events, organisers are delegated `collection/add` authority so they can add events to the collection, they own any events they create. Visitors are given `document/read` authority for the festival info and all it's events.\n\n```\nowner=admins────────────────────────┐\nvisitor:read                        │\n│                                   │\n│          FESTIVAL INFO            │\n│                                   │\n│    owner=admins──────────────┐    │\n│    organiser:add             │    │\n│    │                         │    │\n│    │         EVENTS          │    │\n│    │                         │    │\n│    │   owner=organiser───┐   │    │\n│    │   │                 │   │    │\n│    │   │      EVENT      │   │    │\n│    │   │                 │   │    │\n│    │   └─────────────────┘   │    │\n│    │                         │    │\n│    │   owner=organiser───┐   │    │\n│    │   │                 │   │    │\n│    │   │      EVENT      │   │    │\n│    │   │                 │   │    │\n│    │   └─────────────────┘   │    │\n│    │                         │    │\n│    │   owner=organiser───┐   │    │\n│    │   │                 │   │    │\n│    │   │      EVENT      │   │    │\n│    │   │                 │   │    │\n│    │   └─────────────────┘   │    │\n│    │                         │    │\n│    │                         │    │\n│    └─────────────────────────┘    │\n│                                   │\n└───────────────────────────────────┘\n```\n\nIn the following diagram we can see how non-hierarchical ownership can be modeled with a photo sharing app. The app displays to the user a collection of photos, this isn't an \"owned\" collection as no peer has overall authority over it, each user will have their own photo collection. All they need is `read` authority for any published photos.\n\n```\nowner=anna─────────┐   owner=billie───────┐\nme:read            │   me:read            │\n│                  │   │                  │\n│      PHOTO       │   │      PHOTO       │\n│                  │   │                  │\n│                  │   │                  │\n└──────────────────┘   └──────────────────┘\n\nowner=claire───────┐   owner=diana────────┐\nme:read            │   me:read            │\n│                  │   │                  │\n│      PHOTO       │   │      PHOTO       │\n│                  │   │                  │\n│                  │   │                  │\n└──────────────────┘   └──────────────────┘\n```\n\n# System context assumptions\n\nWe assume a system where resources can be referenced by unique identifiers (for example, via content hashes) and messages between peers are signed using public key cryptography. Optionally, messages can form append-only logs (by including sequence number and backlinks) so as to provide verifiable causal ordering and stricter capability condition restrictions.\n\n# Definitions\n\n**peer:** a user possessing knowledge of the private key for an ed25519 signing keypair; publishes operations and syncs document state with other peers  \n**owner:** the peer who created a document  \n**issuer:** the peer issuing authority via a capability  \n**receiver:** the peer receiving authority via a capability  \n**authorizer:** the peer authorizing a request or operation against a capability  \n**invoker** the peer attempting to use the authority a capability delegates (by publishing operations or syncing documents)  \n**document:** a uniquely identifiable resource which capabilities are associated with  \n**operation:** messages peers publish to the network containing changes to documents, require that suitable authority is demonstrated before being accepted by another peer  \n**collection:** a collection of documents  \n**group:** a group of authors (public keys) which can be the owner of a document, and receiver of a capability  \n**schema:** description of a documents' format, is addressable by a unique schema id  \n\n# Capability\n\n| Field        | Type                          | Required | Description                                             |\n| ------------ | ----------------------------- | -------- | ------------------------------------------------------- |\n| `issuer`     | `PublicKey`                   | Yes      | Public key of the issuing author                        |\n| `receiver`   | `PublicKey` / `GroupId` / `*` | Yes      | The receiver of this capability                         |\n| `subject`    | `PublicKey`                   | Yes      | The initial (root) issuer in any capability chain       |\n| `action`     | `String`                      | Yes      | The action a capability gives authority to perform      |\n| `conditions` | `Conditions`                  | Yes      | Conditions which restrict this capability               |\n| `not_before` | `Integer`                     | No       | \"Not before\" UTC Unix Timestamp in seconds (valid from) |\n| `expires`    | `Integer`                     | No       | Expiration UTC Unix Timestamp in seconds (valid until)  |\n\n## `issuer`\n\nPeer issuing a capability identified by their public key. May be the original owner of a resource (in which case the public key must match the `subject`) or a peer issuing a capability delegation.\n\nEven if a peer receives a capability due to being the member of a group (receive contains a group id), then further delegations are issued using their public key as issuer. When validating delegation chains including delegations to a group, current membership of the group must be confirmed for the `issuer` public key.\n\n## `receiver`\n\nThe receiver of a capability can be an individual peer identified by their public key, a group of peers identified by a group id (all members of the group receive authority) or the wildcard symbol `*` which signifies _any_ peer is authorized to perform the action described by this capability.\n\nIf a `receiver` is defined by a group id then an `authorizer` must be able to resolve the group to a list of public keys containing only the current group members. The public key of any `invoker` must be a member of this group.\n\nIf the `receiver` contains wildcard symbol `*` then this capability can be invoked by _any_ peer.\n\n## `subject`\n\nPeer who is the owner of the resource this capability targets and therefore the root of any delegation chain.\n\n## `action`\n\nAction a capability gives authority to perform. Can be any string value using `/` as separator between arguments, initially supported values are:\n\n### Read actions\n\n- `document/read`: read and sync a document\n\n### Write actions\n\n- `document/write`: write to a document\n- `document/delete`: delete a document\n- `collection/add`: add documents to a collection\n- `collection/remove`: remove documents from a collection\n- `group/add`: add members to a group\n- `group/remove`: remove members from a group\n- `capability/issue`: issue capabilities\n- `capability/revoke`: revoke capabilities\n\nActions can be application specific with desired level of granularity. For example, if a document contains a Yjs CRDT Map, you may want to authorize peers to only be able to write to one key. This could be achieved by introducing the action `ycrdt/write/\u003cKEY\u003e`, where `KEY` is the only field allowed to write to. This could be coupled with custom document methods and validation logic in order to implement capabilities providing a granularity of access control which is finer than the document boundary itself.\n\n## `conditions`\n\nConditions are how the authority a capability delegates is bounded, adding conditions can restrict a capability along several parameters. Supported conditions are:\n\n| Field            | Type         | Action     | Required | Description                                                       |\n| ---------------- | ------------ | ---------- | -------- | ----------------------------------------------------------------- |\n| `document_ids`   | `[Hash]`     | read/write | No       | List document ids this capability applies to                      |\n| `schema_ids`     | `[SchemaId]` | read/write | No       | List of schema ids this capability applies to                     |\n| `to_timestamp`   | `Integer`    | read/write | No       | The timestamp until which read/write of operations is authorized  |\n| `from_timestamp` | `Integer`    | read/write | No       | The timestamp from which read/write of operations is authorized   |\n| `to_seq`         | `Integer`    | write      | No       | The seq number until which read/write of operations is authorized |\n| `from_seq`       | `Integer`    | write      | No       | The seq number from which read/write of operations is authorized  |\n\n### Empty conditions\n\nIf the conditions are empty then the action contained in the capability is authorized for any document owned by the `subject` \u0026/ `issuer`.\n\n```json\n{\n  //...\n  \"action\": \"document/read\",\n  \"conditions\": {}\n}\n```\n\n### `document_ids`\n\nIncluding `document_ids` in a capabilities conditions restricts the authority scope to only the listed documents. The type of document is not specified (it can be a single document, a collection or a group) but the action a capability contains must be compatible with the type of documents targeted.\n\n```json\n{\n  //...\n  \"action\": \"document/update\",\n  \"conditions\": { \"document_ids\": [\"0A01..\"] }\n}\n```\n\nThe above capability gives authority to update only document `\"0A01..\"`.\n\n### `schema_ids`\n\nIncluding `schema_ids` in a capabilities conditions restricts the authority scope to only documents following the specified schemas.\n\n```json\n{\n  //...\n  \"action\": \"document/update\",\n  \"conditions\": { \"schema_ids\": [\"events_0A01..\", \"resources_0B02..\"] }\n}\n```\n\nThe above capability gives authority to update any documents which are `\"events\"` or `\"resources\"`.\n\n### `to_timestamp`\n\nIncluding `to_timestamp` restricts a capabilities authority to only operations which contain a lower `timestamp` in their header.\n\n```json\n{\n  //...\n  \"action\": \"document/update\",\n  \"conditions\": {\n    \"document_ids\": [\"0A01..\"],\n    \"to_timestamp\": 1712226632\n  }\n}\n```\n\nThe above capability restricts authority to document `\"0A01\"` and only operations containing a timestamp `\u003c=` `1712226632`.\n\nAt first glance it may seem that this (and the following `from_timestamp`) condition is duplicating behaviour achievable via a capabilities `expires` and `not_before` field. However on closer consideration we can see how these two fields can be configured to support network specific degrees of delay tolerance while still retaining recommended \"least authority\" practices. To demonstrate this we can use an example where a network delay of one day is tolerated. In this situation we can expect that after one day has passed all peers should have received all published operations for all documents they replicate (and have `document/read` capabilities for). For this network we might issue capabilities with the following values:\n\n```json\n{\n  //...\n  \"expires\": 1712310016, // one day later than to_timestamp\n  \"action\": \"document/update\",\n  \"conditions\": {\n    \"document_ids\": [\"0A01..\"],\n    \"to_timestamp\": 1712226632 // one day before expires\n  }\n}\n```\n\nBy including an `\"expires\"` field which contains a timestamp one day ahead of the `\"to_timestamp\"` condition we are allowing operations within the capabilities scope to arrive up to one day late, but no longer. After `\"expires\"` has passed we can reject updates from authors depending on this capability, even if they would have fallen within the authorised scope. Correct configuration of this behaviour requires an understanding of the networks you will be using and the degree of delay you wish to tolerate. Misconfiguration has the potential to artificially introduce network partitions, where one peer accepted operations which another never received, however the capability used has now expired. This \"deadlock\" is expected to be resolved when new capabilities are issued, or if the operations are due for garbage collection in any case.\n\n### `from_timestamp`\n\nIncluding `from_timestamp` restricts a capabilities authority to only operations' which contain a greater `timestamp` in their header.\n\n```json\n{\n  //...\n  \"action\": \"document/update\",\n  \"conditions\": {\n    \"document_ids\": [\"0A01..\"],\n    \"from_timestamp\": 1712226632\n  }\n}\n```\n\nThe above capability restricts authority to document `\"0A01\"` and only operations containing a timestamp `\u003e` `1712226632`.\n\n### `to_seq`\n\nIncluding `to_seq` restricts a capabilities authority to only operations' which contain a lower `timestamp` in their header.\n\nAn authors contributions to a document contain a monotonically increasing sequence number. By leveraging this property we can introduce a more strictly bounded authority than is possible when using timestamps.\n\n```json\n{\n  //...\n  \"action\": \"document/update\",\n  \"conditions\": {\n    \"document_ids\": [\"0A01..\"],\n    \"to_seq\": 100\n  }\n}\n```\n\nThe above capability restricts authority to document `\"0A01\"` and only operations containing a sequence number `\u003c` 100. Effectively this capability allows a maximum of 100 operations to be published to a document.\n\n### `from_seq`\n\nIncluding `from_seq` restricts a capabilities authority to only operations' which contain a greater `timestamp` in their header.\n\n### `not_before`\n\nThe UTC Unix Timestamp in milliseconds specifying at which point this capability becomes valid.\n\n### `expires`\n\nThe UTC Unix Timestamp in milliseconds specifying at which point this capability expires.\n\nAlthough this field is optional, it is highly recommended that one includes an expiry for most capabilities. The exception for this is when issuing capabilities containing a restriction based on an operations sequence number. As this is a securely bounded condition, with measures in place at a protocol level to detect potentially malicious behavior, then expiry can be safely omitted.\n\n# Issuing\n\nThe owner of a resource can issue a root capability which grants authority to the receiver based on the action and conditions.\n\n```mermaid\nclassDiagram\n    Capability --\u003e Anna : issuer\n    Capability --\u003e Billie : receiver\n    Conditions --\u003e Capability\n    Capability --\u003e Document: described by conditions\n    class Document {\n      id = \"0A01\"\n      author = Anna\n    }\n    class Conditions {\n      document_ids = [\"0A01\"]\n    }\n    class Capability {\n      issuer = Anna\n      receiver = Billie\n      subject = Anna\n      expires = 1712226632\n      action = \"document/read\"\n    }\n    class Anna\n    class Billie\n```\n\nIn the above example Anna issues a capability to Billie which gives the authority to read document `\"0A01\"`. It is valid from the moment it is issued as no `not_before` timestamp is included, it expires after timestamp `1712226632` has passed.\n\n# Delegation\n\nOnce a peer has received a capability they can then delegate new capabilities based on the authority they received.\n\n```mermaid\nclassDiagram\n    Conditions01 --\u003e Capability01\n    Capability01 --\u003e Anna : issuer\n    Capability01 --\u003e Billie : receiver\n    Capability01 --\u003e Document01\n    Capability01 --\u003e Document02\n    Capability02 --\u003e Billie : issuer\n    Conditions02 --\u003e Capability02\n    Capability02 --\u003e Claire : receiver\n    Capability02 --\u003e Document01\n    class Document01 {\n      id = \"0A01\"\n      author = Anna\n    }\n    class Document02 {\n      id = \"0B02\"\n      author = Anna\n    }\n    class Conditions01 {\n      document_ids = [\"0A01\", \"0B02\"]\n      to_timestamp = 1712226632\n    }\n    class Capability01 {\n      issuer = Anna\n      receiver = Billie\n      subject = Anna\n      expires = 1712226632\n      action = \"document/read\"\n    }\n    class Conditions02 {\n      document_ids = [\"0A01\"]\n      to_timestamp = 1712216632\n    }\n    class Capability02 {\n      issuer = Billie\n      receiver = Claire\n      subject = Anna\n      expires = 1712226632\n      action = \"document/read\"\n    }\n    class Anna\n    class Billie\n    class Claire\n```\n\nIn the above example we see a read capability being issued from Anna to Billie for documents `\"0A01\"` and `\"0B02\"`, Billie then delegates read a capability to Claire only covering document `\"0A01\"`.\n\nPeers can only delegate authority \"less than\" that which they have been given. The bounds of a peers authority is defined by the `not_before`, `expires` and `conditions` fields of capabilities they have been given. `not_before` must only increase, `expires` must only decrease and `conditions` must only attenuate the scope of authority given. Once a condition is included in a capability, it can be attenuated, but cannot be omitted in further delegations.\n\n# Revocation\n\n| Field    | Type   | Required | Description                                      |\n| -------- | ------ | -------- | ------------------------------------------------ |\n| `revoke` | `Hash` | Yes      | The operation id of the capability being revoked |\n\nDue to the nature of distributed systems, it is not possible to offer a guaranteed solution for global data removal. The best effort we can offer is that if all peers correctly follow protocol and messages effectively replicate across the network, then data removal can eventually be achieved. This is also true when considering the revocation of previously issued capabilities, and is why employing sensible capability expiry and restricted access (following principle of least authority) should be the preferred method for securing access control in distributed systems.\n\nRevocation offers a manual approach to invalidating previously issued capabilities. Our design is based on the [UCAN revocation specification](https://github.com/ucan-wg/revocation/blob/first-draft/README.md) which should be referred to for detailed descriptions of expected behavior.\n\n```mermaid\nclassDiagram\n    Revocation --\u003e Capability : revoked capability\n    Revocation --\u003e Author : signed by\n    Capability --\u003e Author : issuer\n    class Revocation {\n        revoke: CapabilityID\n    }\n    class Capability {\n        id: CapabilityID\n        issuer: Author\n        receiver: Author\n        subject: Author\n        action: String\n        conditions: Conditions\n        ?not_before: u64\n        ?expires: u64\n    }\n    class Author {\n      public_key: PublicKey\n    }\n```\n\nIf the desired behavior is that actions already authorized under the to-be-revoked capability be retained (only new ones are rejected) then before revocation occurs a new capability covering the new, more restrictive, range should first be issued.\n\n# Format\n\nCapabilities and revocations are published as the body of a p2panda operation, specification [here](https://p2panda.org/specifications/namakemono).\n\nA capability operation which has the schema id `cap_v1`.\n\n```mermaid\nclassDiagram\n    Header --\u003e `Body (Capability)`\n    class Header {\n        version: u64,\n        schema_id: \"cap_v1\"\n        signature: Signature,\n        public_key: PublicKey,\n        payload_hash: Option~Hash~,\n        payload_size: Option~u64~,\n        timestamp: u64,\n        seq_num: u64,\n    }\n    class `Body (Capability)` {\n        issuer: PublicKey\n        receiver: PublicKey | GroupId | \"*\"\n        subject: PublicKey\n        action: String\n        conditions: Conditions\n        ?not_before: u64\n        ?expires: u64\n    }\n```\n\nA revoke operation which has the schema id `revoke_v1`.\n\n```mermaid\nclassDiagram\n    Header --\u003e `Body (Revocation)`\n    class Header {\n        version: u64,\n        schema_id: \"revoke_v1\"\n        signature: Signature,\n        public_key: PublicKey,\n        payload_hash: Option~Hash~,\n        payload_size: Option~u64~,\n        timestamp: u64,\n        seq_num: u64,\n    }\n    class `Body (Revocation)` {\n        revoke: Hash\n    }\n```\n\n# Validation\n\nValidation of capabilities must be performed by an authorizer before any new operation arriving at a peer, or any sync request, is authorized.\n\nIf any of the following criteria are not met, the capability must be considered invalid:\n\n## Time Bounds\n\nThe system time must not be earlier than `not_before` or have passed the `expires` timestamp. Any new sync requests attempting to use this capability to read documents must be rejected and no operations relying on this capability for authority to perform an action must be rejected.\n\nIn the case of any `write` capabilities expiring, a peer can choose to keep already accepted operations which rely on this capabilities authority after expiry has passed, or where possible, roll back these changes and remove the operations. Which approach is taken will depend on the desired behavior for the document.\n\n## Condition Attenuation\n\nDelegated conditions must not expand the scope of the capability it delegates from, it must only attenuate.\n\nThe following table gives some examples of valid and invalid delegated conditions.\n\n| Received Conditions                                | Delegated Conditions                               | Valid | Reason                                        |\n| -------------------------------------------------- | -------------------------------------------------- | ----- | --------------------------------------------- |\n| `{document_ids: [\"0X01\", \"0X02\"]}`                 | `{document_ids: [\"0X01\"]}`                         | ✅    | Condition is attenuated                       |\n| `{schema_ids: [\"events\"]}`                         | `{schema_ids: [\"events\"], document_ids: [\"0X01\"]}` | ✅    | New condition added which restricts authority |\n| `{from_timestamp: 10, to_timestamp: 100}`          | `{from_timestamp: 50, to_timestamp: 80}`           | ✅    | Condition is attenuated                       |\n| `{schema_ids: [\"events\"], document_ids: [\"0X01\"]}` | `{schema_ids: [\"events\"]}`                         | ❌    | Condition removed                             |\n| `{document_ids: [\"0X01\"]}`                         | `{document_ids: [\"0X01\", \"0X02\"]}`                 | ❌    | Condition expanded                            |\n| `{from_timestamp: 50, to_timestamp: 80}`           | `{from_timestamp: 0, to_timestamp: 100}`           | ❌    | Condition expanded                            |\n\n## Principal Alignment\n\nIn delegation, the `receiver` field of every proof must match the `issuer` field of the capability being delegated to. This alignment must form a chain back to the `subject` for each resource.\n\n## Issuer Validation\n\nThe issuer field must match against the public key from the operation header.\n\n# Authorization\n\nValid capabilities are used to authorize two main classes of action: read and write. Read actions result in operations being sent to other peers, while write actions involve a peer accepting and applying operations. This occurs during a sync session between two peers or when a client is making requests to a shared node.\n\n## Read authority\n\nRead requests are considered authorized if a valid read capability exists where the requesting peer is the `receiver` of the capability and the document being syncronised is included in it's authority scope. Only operations within the `from_timestamp` to `to_timestamp` range must be included.\n\nIt is worth noting that operations by _all_ authors who have contributed to this document will be syncronised, not only those of the capability `issuer`.\n\nThese check can be performed against a capability store or alternatively as a performance optimisation an access-control list can be maintained based on all known (not expired or revoked) capabilities.\n\n## Write authority\n\nWrite requests are considered authorized if a valid read capability exists where the author of the operation is the `receiver` of the capability and the document being syncronised is included in it's authority scope. Only operations within the `from_timestamp` to `to_timestamp` and `from_seq` to `to_seq` ranges must be included.\n\nThese check can be performed against a capability store or alternatively as a performance optimisation an access-control list can be maintained based on all known (not expired or revoked) capabilities.\n\n# Implementation\n\n## Handling expired capabilities\n\nWhen a capability a peer holds expires it signals that the authority previously delegated by the token should now be considered invalidated. This has several consequences: a peer may hold in it's store operations which have lost authorization (due to the expiry date passing) and other peers would now reject these operations if sent during a sync session.\n\nOnce a capability expires it may or may not be possible/desireable to \"roll-back\" any operations which were previously accepted under this authority. This depends on the nature of the target document. If no history is persisted then roll-back is likely not possible or expected, if however the document contained operations of a history-retaining CRDT tracking group membership, it may be desireable to drop any changes made by the now \"not authorized\" author. If the intention is that their historic changes should be retained then a new capability should have been issued already.\n\nHow likely this situation is to occur also depends on the desired network delay tolerance. For example, on a highly connected network where a documents' payload is a Yjs CRDT and only the most recent operation (depth 1, no history is kept) is retained then regularly issued capabilities will likely already be rendered redundant by ambient garbage collection before they expire. A contrasting example is that of a highly delay tolerant network, where entire document histories are retained, if a full network partition occurs then intended capability re-issues may not be received, in this situation peers may well choose to not yet roll-back the now invalidated operations even though they can no longer be replicated (until re-issued capabilities arrive).\n\n## Handling revoked capabilities\n\nWhen receiving a revocation message the capability it revokes must immediately be invalidated. Where possible previously authorized operations can be rolled back.\n\nIn order to ensure a deterministic revocation point the revocation can be appended to the document(s) which are the target of the revoked capability.\n\n## Supporting protocol features\n\nThe features of the capability system described so far in this document can be implemented and supported in incremental stages. The initial layer would be\nthe mechanisms for issuing, storing and validating requests against issued capabilities. On top of this, revocation can be optionally supported.\n\n## Distribution\n\nCapabilities can be distributed ahead of synchronizing any other application data or sent at the start of a synchronization session. Which approach should be taken may depend on your use case and how sensitive metadata contained in the capabilities is considered to be. Revocation messages must be distributed to all relevant peers regardless of the approach taken for capabilities.\n\n## Handling dependencies\n\nDependencies exist between messages in our capability system (revocations refer to a capability, capabilities may require a delegation chain), as messages may travel between peers on the network out-of-order, mechanisms for processing messages in the correct order are required. This is a common requirement of p2p systems and p2panda has existing solutions for ordering collections of operations correctly. In our implementation we can rely on existing patterns around handling relations between documents and deterministically ordering operation graphs within one document.\n\n## Examples\n\n### authorize `read` request against sent capability\n\nHere a peer makes a `read` request and sends the authorizing capability in the request. The authorizer can validate the capability and the send delegation chain to ensure the authority it gives includes the requested document.\n\n```mermaid\nsequenceDiagram\n    PeerA-\u003e\u003ePeerB (Authorizer): make read request `read(DocumentID, Capability, ?DelegationChain)`\n    opt if is delegation\n        PeerB (Authorizer)-\u003e\u003ePeerB (Authorizer): validate delegation chain\n    end\n    opt if revocation supported\n        PeerB (Authorizer)-\u003e\u003ePeerB (Authorizer): check if revocation exists\n    end\n    PeerB (Authorizer)-\u003e\u003ePeerB (Authorizer): process request against any conditions\n    PeerB (Authorizer)-\u003e\u003ePeerA: return operations PeerA has authority to `read`\n```\n\n### authorize `read` request against cached capabilities (ACL)\n\nIf capabilities are cached (likely in the form of an Access Control List) then relevant capabilities do not need to be sent and the authorizer can simply query the ACL to check the requesting peer has the necessary authority.\n\nIn this example validation of capabilities, their delegation chains, and any existing revocations has already been performed and we can consider the ACL to be our current view of known peer authority mappings.\n\n```mermaid\nsequenceDiagram\n    PeerA-\u003e\u003ePeerB (Authorizer): make read request `read(DocumentID)`\n    PeerB (Authorizer)-\u003e\u003ePeerB (Authorizer): query ACL to check PeerA\u003cbr\u003ehas the required authority\n    PeerB (Authorizer)-\u003e\u003ePeerA: return operations PeerA has authority to `read`\n```\n\n### authorize `write` / `delete` operations\n\nIn the above `read` examples the \"invoker\" of the capability was also the peer who received authority through it (the authority to read/sync operations of a document). In the case of capabilities which give authority to mutate documents (`write`, `delete`, etc..), this may not be the case. It will be common that the \"invoker\" of a capability (ie. the peer sending operations as part of a sync session) is not actually the receiver of the capability.\n\nThis example uses an local ACL to establish authority, but peers could also include all required capabilities in the sync request.\n\n```mermaid\nsequenceDiagram\n    note over PeerA, PeerB (Authorizer): sync session has established the set of\u003cbr\u003e operations PeerA needs to send PeerB\n    PeerA-\u003e\u003ePeerB (Authorizer): send operations `sync([Operation])`\n    loop for each operation\n       PeerB (Authorizer)-\u003e\u003ePeerB (Authorizer): query ACL to check operation author\u003cbr\u003e has authority to perform action\n       PeerB (Authorizer)-\u003e\u003ePeerB (Authorizer): apply operation to local document\n    end\n```\n\n# Use Cases\n\n## A personal travel blog\n\nAnna is starting a travel blog which she wants to share with only her friends and family. Billie is a close friend that Anna trusts and so she issues a `document/read` capability with no expiry date (`1`). Billie can now read all entries in the travel blog document (`2`). Claire is a mutual friend of Anna and Billie, she lives in the town Anna is visiting but can't contact her and so asks Billie if they can give her access to the travel blog, Billie delegates the capability they hold for a temporary period (`3`). Now Claire can read the blog entries Billie holds (`4`), and also any new ones Claire publishes (`5`). Further attempts to read new blog entries after the `read` capability has expired will fail (`6`).\n\n```mermaid\nsequenceDiagram\n    autonumber\n    Anna-\u003e\u003eBillie: Issue `document/read` capability with no expiry date\n    Billie-\u003e\u003eAnna: Use `read` capability to sync the blog document\n    Billie-\u003e\u003eClaire: Delegate `document/read` capability with expiry date\n    Claire-\u003e\u003eBillie: Use `read` capability to sync the blog document\n    Claire-\u003e\u003eAnna: Use `read` capability to sync the blog document\n    Note over Anna, Claire: Claire's `read` capability expires\n    Claire-\u003e\u003eAnna: Attempt to use `read` capability to sync the blog document fails\n```\n\n## Collaborative document\n\nAnna is having a meeting with Billie and Claire, she starts a collaborative document where they can all help to record the minutes. She shares `read` and `write` authority with them both at the start of the meeting (`1` \u0026 `2`). During the meeting they can all edit the document (`3`). The `write` capability expires once the meeting ends, but read capabilities last longer so that the document can still be viewed and discussed later.\n\n```mermaid\nsequenceDiagram\n    autonumber\n    Anna-\u003e\u003eBillie\u0026Claire: Issue `read` capability with long expiry\n    Anna-\u003e\u003eBillie\u0026Claire: Issue `write` capability which expires when the meeting finishes\n    Billie\u0026Claire-\u003e\u003eAnna: Use `read` and `write` capabilities to edit the document\n    Note over Anna, Billie\u0026Claire: The meeting finishes and `write` capability expires\n```\n\n## Collaborative offline-first maps\n\nHere we have a collaborative offline-first application where users can drop pins at points-of-interest on a map. Each map is maintained by an admin group, who can invite new collaborators and moderate content.\n\n`AdminGroup` invites Daisy to join a map and issues them an `collection/add` capability (`1`) on the maps associated collection of `pins`. Daisy can now add her pins to the map. To use the app Daisy is expected to give the admin group authority to edit her pins. She does this by issuing a `document/write` capability restricted to documents of type `PIN_SCHEMA_ID` to the admin group (`2`).\n\nDaisy could revoke the authority given to the AdminGroup at a later point, the admin group would then potentially revoke her right to add pins to the map.\n\n```mermaid\nsequenceDiagram\n    autonumber\n    AdminGroup-\u003e\u003eDaisy: Issue `collection/add` capability for a map document\n    note over AdminGroup, Daisy: Daisy can now add pins to the map\n    Daisy-\u003e\u003eAdminGroup: Issue `document/write` capability restricting to documents of `PIN_SCHEMA_ID`\n    note over AdminGroup, Daisy: AdminGroup can now edit any pins Daisy makes\n```\n\n# Related projects\n\nAs previously stated, the capability system described in this document is heavily based of the UCAN specification (in-progress `v1.0.0` spec). That said, there were also many other projects which inspired these designs: [CapTP](https://spritelyproject.org/news/what-is-captp.html), [Spritely](https://spritely.institute/), [Biscuit](https://www.biscuitsec.org/), [Willow](https://willowprotocol.org/), [Cap'N'Proto](https://capnproto.org/) and [Cable](https://github.com/cabal-club/cable) to name a few.\n\n## UCAN format comparison\n\nIf you're familiar with UCAN tokens, then it may be useful to see a side-by-side capability definition.\n\n### `p2panda` capability\n\n```json\n{\n  \"issuer\": \"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\n  \"receiver\": \"z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z\",\n  \"subject\": \"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\n  \"action\": \"document/write\",\n  \"conditions\": {\n    \"document_ids\": [\n      \"c2500c3088b01a98f4a7cfdab6037371ac64d4b929d4677daf39a3aa0c257612\"\n    ],\n    \"to_timestamp\": 1712226632\n  },\n  \"expires\": 1712226632\n}\n```\n\n### `UCAN` capability\n\n```json\n{\n  \"iss\": \"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\n  \"aud\": \"did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z\",\n  \"sub\": \"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\n  \"can\": \"document/write\",\n  \"cond\": [\n    {\n      \"field\": \"document_id\",\n      \"is_in\": [\n        \"c2500c3088b01a98f4a7cfdab6037371ac64d4b929d4677daf39a3aa0c257612\"\n      ]\n    },\n    {\n      \"field\": \"timestamp\",\n      \"lt\": 1712226632\n    }\n  ],\n  \"exp\": 1712226632\n}\n```\n\n## Supported by\n\nThis work would not have been possible without the generous support of the [NLNet Foundation](https://nlnet.nl/) under the [NGI Zero Entrust](https://nlnet.nl/entrust/) funding scheme.\n\n\u003cimg src=\"https://raw.githubusercontent.com/p2panda/.github/main/assets/nlnet-logo.svg\" width=\"auto\" height=\"80px\"\u003e\u003cbr /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp2panda%2Faccess-control","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fp2panda%2Faccess-control","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp2panda%2Faccess-control/lists"}