This document describes an access control system for the p2panda protocol 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.
Peer-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.
What 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).
This 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.
Following 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 UCANs, although our implementation will not be inter-operable, we have incorporated their specification into our designs where possible.
p2panda 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.
In 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.
- authors have fine-grained control over who can access and perform actions on their data
- delay tolerant networking and offline-first operation supported
- authority can be delegated and revoked
- role based access control patterns can be modeled
- hierarchical and "flat" ownership structures can be modeled
- modular system with features which can be incrementally supported
A 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.
The 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.
owner=admins────────────────────────┐
visitor:read │
│ │
│ FESTIVAL INFO │
│ │
│ owner=admins──────────────┐ │
│ organiser:add │ │
│ │ │ │
│ │ EVENTS │ │
│ │ │ │
│ │ owner=organiser───┐ │ │
│ │ │ │ │ │
│ │ │ EVENT │ │ │
│ │ │ │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ owner=organiser───┐ │ │
│ │ │ │ │ │
│ │ │ EVENT │ │ │
│ │ │ │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ owner=organiser───┐ │ │
│ │ │ │ │ │
│ │ │ EVENT │ │ │
│ │ │ │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ │ │
│ └─────────────────────────┘ │
│ │
└───────────────────────────────────┘
In 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.
owner=anna─────────┐ owner=billie───────┐
me:read │ me:read │
│ │ │ │
│ PHOTO │ │ PHOTO │
│ │ │ │
│ │ │ │
└──────────────────┘ └──────────────────┘
owner=claire───────┐ owner=diana────────┐
me:read │ me:read │
│ │ │ │
│ PHOTO │ │ PHOTO │
│ │ │ │
│ │ │ │
└──────────────────┘ └──────────────────┘
We 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.
peer: a user possessing knowledge of the private key for an ed25519 signing keypair; publishes operations and syncs document state with other peers
owner: the peer who created a document
issuer: the peer issuing authority via a capability
receiver: the peer receiving authority via a capability
authorizer: the peer authorizing a request or operation against a capability
invoker the peer attempting to use the authority a capability delegates (by publishing operations or syncing documents)
document: a uniquely identifiable resource which capabilities are associated with
operation: messages peers publish to the network containing changes to documents, require that suitable authority is demonstrated before being accepted by another peer
collection: a collection of documents
group: a group of authors (public keys) which can be the owner of a document, and receiver of a capability
schema: description of a documents' format, is addressable by a unique schema id
Field | Type | Required | Description |
---|---|---|---|
issuer |
PublicKey |
Yes | Public key of the issuing author |
receiver |
PublicKey / GroupId / * |
Yes | The receiver of this capability |
subject |
PublicKey |
Yes | The initial (root) issuer in any capability chain |
action |
String |
Yes | The action a capability gives authority to perform |
conditions |
Conditions |
Yes | Conditions which restrict this capability |
not_before |
Integer |
No | "Not before" UTC Unix Timestamp in seconds (valid from) |
expires |
Integer |
No | Expiration UTC Unix Timestamp in seconds (valid until) |
Peer 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.
Even 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.
The 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.
If 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.
If the receiver
contains wildcard symbol *
then this capability can be invoked by any peer.
Peer who is the owner of the resource this capability targets and therefore the root of any delegation chain.
Action a capability gives authority to perform. Can be any string value using /
as separator between arguments, initially supported values are:
document/read
: read and sync a document
document/write
: write to a documentdocument/delete
: delete a documentcollection/add
: add documents to a collectioncollection/remove
: remove documents from a collectiongroup/add
: add members to a groupgroup/remove
: remove members from a groupcapability/issue
: issue capabilitiescapability/revoke
: revoke capabilities
Actions 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/<KEY>
, 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.
Conditions are how the authority a capability delegates is bounded, adding conditions can restrict a capability along several parameters. Supported conditions are:
Field | Type | Action | Required | Description |
---|---|---|---|---|
document_ids |
[Hash] |
read/write | No | List document ids this capability applies to |
schema_ids |
[SchemaId] |
read/write | No | List of schema ids this capability applies to |
to_timestamp |
Integer |
read/write | No | The timestamp until which read/write of operations is authorized |
from_timestamp |
Integer |
read/write | No | The timestamp from which read/write of operations is authorized |
to_seq |
Integer |
write | No | The seq number until which read/write of operations is authorized |
from_seq |
Integer |
write | No | The seq number from which read/write of operations is authorized |
If the conditions are empty then the action contained in the capability is authorized for any document owned by the subject
&/ issuer
.
{
//...
"action": "document/read",
"conditions": {}
}
Including 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.
{
//...
"action": "document/update",
"conditions": { "document_ids": ["0A01.."] }
}
The above capability gives authority to update only document "0A01.."
.
Including schema_ids
in a capabilities conditions restricts the authority scope to only documents following the specified schemas.
{
//...
"action": "document/update",
"conditions": { "schema_ids": ["events_0A01..", "resources_0B02.."] }
}
The above capability gives authority to update any documents which are "events"
or "resources"
.
Including to_timestamp
restricts a capabilities authority to only operations which contain a lower timestamp
in their header.
{
//...
"action": "document/update",
"conditions": {
"document_ids": ["0A01.."],
"to_timestamp": 1712226632
}
}
The above capability restricts authority to document "0A01"
and only operations containing a timestamp <=
1712226632
.
At 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:
{
//...
"expires": 1712310016, // one day later than to_timestamp
"action": "document/update",
"conditions": {
"document_ids": ["0A01.."],
"to_timestamp": 1712226632 // one day before expires
}
}
By 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.
Including from_timestamp
restricts a capabilities authority to only operations' which contain a greater timestamp
in their header.
{
//...
"action": "document/update",
"conditions": {
"document_ids": ["0A01.."],
"from_timestamp": 1712226632
}
}
The above capability restricts authority to document "0A01"
and only operations containing a timestamp >
1712226632
.
Including to_seq
restricts a capabilities authority to only operations' which contain a lower timestamp
in their header.
An 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.
{
//...
"action": "document/update",
"conditions": {
"document_ids": ["0A01.."],
"to_seq": 100
}
}
The above capability restricts authority to document "0A01"
and only operations containing a sequence number <
100. Effectively this capability allows a maximum of 100 operations to be published to a document.
Including from_seq
restricts a capabilities authority to only operations' which contain a greater timestamp
in their header.
The UTC Unix Timestamp in milliseconds specifying at which point this capability becomes valid.
The UTC Unix Timestamp in milliseconds specifying at which point this capability expires.
Although 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.
The owner of a resource can issue a root capability which grants authority to the receiver based on the action and conditions.
classDiagram
Capability --> Anna : issuer
Capability --> Billie : receiver
Conditions --> Capability
Capability --> Document: described by conditions
class Document {
id = "0A01"
author = Anna
}
class Conditions {
document_ids = ["0A01"]
}
class Capability {
issuer = Anna
receiver = Billie
subject = Anna
expires = 1712226632
action = "document/read"
}
class Anna
class Billie
In 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.
Once a peer has received a capability they can then delegate new capabilities based on the authority they received.
classDiagram
Conditions01 --> Capability01
Capability01 --> Anna : issuer
Capability01 --> Billie : receiver
Capability01 --> Document01
Capability01 --> Document02
Capability02 --> Billie : issuer
Conditions02 --> Capability02
Capability02 --> Claire : receiver
Capability02 --> Document01
class Document01 {
id = "0A01"
author = Anna
}
class Document02 {
id = "0B02"
author = Anna
}
class Conditions01 {
document_ids = ["0A01", "0B02"]
to_timestamp = 1712226632
}
class Capability01 {
issuer = Anna
receiver = Billie
subject = Anna
expires = 1712226632
action = "document/read"
}
class Conditions02 {
document_ids = ["0A01"]
to_timestamp = 1712216632
}
class Capability02 {
issuer = Billie
receiver = Claire
subject = Anna
expires = 1712226632
action = "document/read"
}
class Anna
class Billie
class Claire
In 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"
.
Peers 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.
Field | Type | Required | Description |
---|---|---|---|
revoke |
Hash |
Yes | The operation id of the capability being revoked |
Due 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.
Revocation offers a manual approach to invalidating previously issued capabilities. Our design is based on the UCAN revocation specification which should be referred to for detailed descriptions of expected behavior.
classDiagram
Revocation --> Capability : revoked capability
Revocation --> Author : signed by
Capability --> Author : issuer
class Revocation {
revoke: CapabilityID
}
class Capability {
id: CapabilityID
issuer: Author
receiver: Author
subject: Author
action: String
conditions: Conditions
?not_before: u64
?expires: u64
}
class Author {
public_key: PublicKey
}
If 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.
Capabilities and revocations are published as the body of a p2panda operation, specification here.
A capability operation which has the schema id cap_v1
.
classDiagram
Header --> `Body (Capability)`
class Header {
version: u64,
schema_id: "cap_v1"
signature: Signature,
public_key: PublicKey,
payload_hash: Option~Hash~,
payload_size: Option~u64~,
timestamp: u64,
seq_num: u64,
}
class `Body (Capability)` {
issuer: PublicKey
receiver: PublicKey | GroupId | "*"
subject: PublicKey
action: String
conditions: Conditions
?not_before: u64
?expires: u64
}
A revoke operation which has the schema id revoke_v1
.
classDiagram
Header --> `Body (Revocation)`
class Header {
version: u64,
schema_id: "revoke_v1"
signature: Signature,
public_key: PublicKey,
payload_hash: Option~Hash~,
payload_size: Option~u64~,
timestamp: u64,
seq_num: u64,
}
class `Body (Revocation)` {
revoke: Hash
}
Validation of capabilities must be performed by an authorizer before any new operation arriving at a peer, or any sync request, is authorized.
If any of the following criteria are not met, the capability must be considered invalid:
The 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.
In 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.
Delegated conditions must not expand the scope of the capability it delegates from, it must only attenuate.
The following table gives some examples of valid and invalid delegated conditions.
Received Conditions | Delegated Conditions | Valid | Reason |
---|---|---|---|
{document_ids: ["0X01", "0X02"]} |
{document_ids: ["0X01"]} |
✅ | Condition is attenuated |
{schema_ids: ["events"]} |
{schema_ids: ["events"], document_ids: ["0X01"]} |
✅ | New condition added which restricts authority |
{from_timestamp: 10, to_timestamp: 100} |
{from_timestamp: 50, to_timestamp: 80} |
✅ | Condition is attenuated |
{schema_ids: ["events"], document_ids: ["0X01"]} |
{schema_ids: ["events"]} |
❌ | Condition removed |
{document_ids: ["0X01"]} |
{document_ids: ["0X01", "0X02"]} |
❌ | Condition expanded |
{from_timestamp: 50, to_timestamp: 80} |
{from_timestamp: 0, to_timestamp: 100} |
❌ | Condition expanded |
In 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.
The issuer field must match against the public key from the operation header.
Valid 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.
Read 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.
It is worth noting that operations by all authors who have contributed to this document will be syncronised, not only those of the capability issuer
.
These 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.
Write 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.
These 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.
When 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.
Once 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.
How 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).
When receiving a revocation message the capability it revokes must immediately be invalidated. Where possible previously authorized operations can be rolled back.
In order to ensure a deterministic revocation point the revocation can be appended to the document(s) which are the target of the revoked capability.
The features of the capability system described so far in this document can be implemented and supported in incremental stages. The initial layer would be the mechanisms for issuing, storing and validating requests against issued capabilities. On top of this, revocation can be optionally supported.
Capabilities 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.
Dependencies 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.
Here 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.
sequenceDiagram
PeerA->>PeerB (Authorizer): make read request `read(DocumentID, Capability, ?DelegationChain)`
opt if is delegation
PeerB (Authorizer)->>PeerB (Authorizer): validate delegation chain
end
opt if revocation supported
PeerB (Authorizer)->>PeerB (Authorizer): check if revocation exists
end
PeerB (Authorizer)->>PeerB (Authorizer): process request against any conditions
PeerB (Authorizer)->>PeerA: return operations PeerA has authority to `read`
If 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.
In 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.
sequenceDiagram
PeerA->>PeerB (Authorizer): make read request `read(DocumentID)`
PeerB (Authorizer)->>PeerB (Authorizer): query ACL to check PeerA<br>has the required authority
PeerB (Authorizer)->>PeerA: return operations PeerA has authority to `read`
In 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.
This example uses an local ACL to establish authority, but peers could also include all required capabilities in the sync request.
sequenceDiagram
note over PeerA, PeerB (Authorizer): sync session has established the set of<br> operations PeerA needs to send PeerB
PeerA->>PeerB (Authorizer): send operations `sync([Operation])`
loop for each operation
PeerB (Authorizer)->>PeerB (Authorizer): query ACL to check operation author<br> has authority to perform action
PeerB (Authorizer)->>PeerB (Authorizer): apply operation to local document
end
Anna 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
).
sequenceDiagram
autonumber
Anna->>Billie: Issue `document/read` capability with no expiry date
Billie->>Anna: Use `read` capability to sync the blog document
Billie->>Claire: Delegate `document/read` capability with expiry date
Claire->>Billie: Use `read` capability to sync the blog document
Claire->>Anna: Use `read` capability to sync the blog document
Note over Anna, Claire: Claire's `read` capability expires
Claire->>Anna: Attempt to use `read` capability to sync the blog document fails
Anna 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
& 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.
sequenceDiagram
autonumber
Anna->>Billie&Claire: Issue `read` capability with long expiry
Anna->>Billie&Claire: Issue `write` capability which expires when the meeting finishes
Billie&Claire->>Anna: Use `read` and `write` capabilities to edit the document
Note over Anna, Billie&Claire: The meeting finishes and `write` capability expires
Here 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.
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
).
Daisy 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.
sequenceDiagram
autonumber
AdminGroup->>Daisy: Issue `collection/add` capability for a map document
note over AdminGroup, Daisy: Daisy can now add pins to the map
Daisy->>AdminGroup: Issue `document/write` capability restricting to documents of `PIN_SCHEMA_ID`
note over AdminGroup, Daisy: AdminGroup can now edit any pins Daisy makes
As 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, Spritely, Biscuit, Willow, Cap'N'Proto and Cable to name a few.
If you're familiar with UCAN tokens, then it may be useful to see a side-by-side capability definition.
{
"issuer": "zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"receiver": "z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"subject": "zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"action": "document/write",
"conditions": {
"document_ids": [
"c2500c3088b01a98f4a7cfdab6037371ac64d4b929d4677daf39a3aa0c257612"
],
"to_timestamp": 1712226632
},
"expires": 1712226632
}
{
"iss": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"aud": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z",
"sub": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"can": "document/write",
"cond": [
{
"field": "document_id",
"is_in": [
"c2500c3088b01a98f4a7cfdab6037371ac64d4b929d4677daf39a3aa0c257612"
]
},
{
"field": "timestamp",
"lt": 1712226632
}
],
"exp": 1712226632
}
This work would not have been possible without the generous support of the NLNet Foundation under the NGI Zero Entrust funding scheme.