diff --git a/IETF-RFC.md b/IETF-RFC.md index a8d1dea..eb054f8 100644 --- a/IETF-RFC.md +++ b/IETF-RFC.md @@ -720,10 +720,11 @@ contain the following information about its OCM API: include one or more of the following items: - `"enforce-mfa"` - to indicate that this OCM Server can apply a Sending Server's MFA requirements for a Share on their behalf. - - `"exchange-token"` - to indicate that this OCM Server exposes a - [RFC6749]-compliant endpoint, which allows to exchange a secret - received in the protocol properties of a Share Creation Notification - for a short-lived bearer token. + - `"exchange-token"` - to indicate that this OCM Server supports the + OCM code flow via an [RFC6749]-compliant token endpoint. When this + OCM Server acts as Sending Server, it hosts `tokenEndPoint`. When it + acts as Receiving Server, it can honor inbound shares that require + token exchange. - `"http-sig"` - to indicate that this OCM Server supports [RFC9421] HTTP Message Signatures and advertises public keys in the format specified by [RFC7517] at the `/.well-known/jwks.json` @@ -751,9 +752,13 @@ contain the following information about its OCM API: for instance: - `"http-request-signatures"` - to indicate that API requests without http signatures will be rejected. - - `"token-exchange"` - to indicate that API requests without - token exchange will be rejected (see the [Code Flow](#code-flow) - section). + - `"token-exchange"` - to indicate that when this OCM Server acts + as Receiving Server, it requires the code flow for all inbound + shares. Shares that do not include `must-exchange-token` in + their `protocol.webdav.requirements` will be rejected. An + OCM Server advertising this criterium MUST also expose the + `exchange-token` capability. See the [Code Flow](#code-flow) + section. - `"denylist"` - some servers MAY be blocked based on their IP address - `"allowlist"` - unknown servers MAY be blocked based on their IP @@ -771,8 +776,10 @@ contain the following information about its OCM API: `"/index.php/apps/sciencemesh/accept"` is specified here then a WAYF Page SHOULD redirect the end-user to `/index.php/apps/sciencemesh/ accept?token=zi5kooKu3ivohr9a&providerDomain=cloud.example.org`. -* OPTIONAL: tokenEndPoint (string) - URL of the token endpoint where the - Sending Server can exchange a secret for a short-lived bearer token. +* OPTIONAL: tokenEndPoint (string) - URL of the token endpoint hosted by + this OCM Server. When this OCM Server acts as Sending Server, the + Receiving Server POSTs here to exchange a `sharedSecret` for a + short-lived bearer token. Implementations that offer the `"exchange-token"` capability MUST provide this URL as well. Example: `"https://cloud.example.org/ocm/token"`. @@ -788,6 +795,21 @@ To create a Share, the Sending Server SHOULD make a HTTP POST request * using TLS * using httpsig [RFC9421] +Before constructing the notification, the Sending Server MUST query +the Receiving Server's OCM API Discovery endpoint. If the Receiving +Server advertises `token-exchange` in its `criteria` and the Sending +Server exposes the `exchange-token` capability with a `tokenEndPoint`, +the Sending Server MUST include `must-exchange-token` in +`protocol.webdav.requirements` and MUST NOT fall back to legacy +shared-secret access. If the Receiving Server advertises +`token-exchange` but the Sending Server does not expose the +`exchange-token` capability or does not have a `tokenEndPoint`, the +Sending Server MUST NOT create the share, as the Receiving Server +would reject any notification that lacks the code-flow requirement. +If the Receiving Server does not advertise `token-exchange` in its +`criteria`, the Sending Server MAY still include `must-exchange-token` +voluntarily. + ## Fields * REQUIRED shareWith (string) @@ -916,8 +938,10 @@ To create a Share, the Sending Server SHOULD make a HTTP POST request - `must-exchange-token` requires the recipient to exchange the given `sharedSecret` via a signed HTTPS request to the Sending Server's {tokenEndPoint} [RFC6749]. - This MAY be used if the recipient provider exposes the - `exchange-token` capability. + This MAY be used if the Sending Server exposes the + `exchange-token` capability and `tokenEndPoint`, and MUST be + included when the Receiving Server advertises `token-exchange` + in criteria. - OPTIONAL size (integer) The size of the resource to be transferred, useful especially in case of `datatx` access type. @@ -1085,9 +1109,9 @@ protocol required for access. The procedure is as follows: token for a short-lived bearer token, and only use that bearer token to access the Resource (See the [Code Flow](#code-flow) section). If the `must-exchange-token` requirement is not present - and the Discovery endpoint inspected at step 1. exposes the - `token-exchange` capability, the receiver MAY attempt to perform - the token exchange as above, but it MUST fall back to the following + and the discovery inspected at step 1 exposes the `exchange-token` + capability with a `tokenEndPoint`, the receiver MAY attempt the + token exchange as above, but it MUST fall back to the following steps should the process fail. 3.2. If it includes `must-use-mfa`, the Receiving Server MUST ensure that the Receiving Party has been authenticated with MFA, or prompt @@ -1201,6 +1225,62 @@ response with a JSON object containing an OAuth 2.0 error code Permitted error codes are `invalid_request`, `invalid_client`, `invalid_grant`, `unauthorized_client` and `unsupported_grant_type`. +## Decision Table + +The directional contract depends first on whether the share is strict. +For strict shares, the Receiving Server's advertised behavior determines +whether the Sending Server can require code flow. For non-strict +shares, the Sending Server's advertised behavior determines whether +token exchange is available in addition to legacy access. + +1. If the Sending Server includes `must-exchange-token` in + `protocol.webdav.requirements` and the Receiving Server exposes the + `exchange-token` capability, strict token exchange is required + before the Resource is accessed. +2. If the Sending Server includes `must-exchange-token` and the + Receiving Server does not expose the `exchange-token` capability, + the Sending Server SHOULD NOT include that requirement, because the + Receiving Server may be unable to complete the exchange. +3. If the Sending Server omits `must-exchange-token` and exposes the + `exchange-token` capability with a `tokenEndPoint`, the Receiving + Server MAY attempt token exchange first and MUST fall back to legacy + shared-secret access if that exchange fails. +4. If the Sending Server omits `must-exchange-token` and does not + expose the `exchange-token` capability, only legacy shared-secret + access is available. + +The following examples illustrate typical end-to-end outcomes: + +1. Strict required code flow: Provider A acts as Sending Server and + exposes the `exchange-token` capability with a `tokenEndPoint`. + Provider B acts as Receiving Server and advertises both + `exchange-token` and `token-exchange`. After discovering B's + `token-exchange` criteria, A MUST include `must-exchange-token` in + `protocol.webdav.requirements`. B MUST exchange the + `sharedSecret` at A's `tokenEndPoint` and then use only the bearer + token to access the Resource. +2. Optional exchange with fallback: Provider A acts as Sending Server + and exposes the `exchange-token` capability with a `tokenEndPoint`. + Provider B does not advertise `token-exchange`, so A sends a share + without `must-exchange-token`. When B later accesses the Resource, + it MAY attempt the token exchange at A's `tokenEndPoint`, but if + that exchange fails it MUST fall back to the legacy + `sharedSecret`. +3. Legacy share to a code-flow-capable peer: Provider A does not + expose the `exchange-token` capability. Provider B does expose + `exchange-token`, so B is capable of honoring strict inbound shares + from other peers. Because A does not advertise a `tokenEndPoint`, + A can only send a legacy share and B can only use legacy + shared-secret access for that share. +4. Asymmetric role behavior: Provider A exposes `exchange-token` and + `token-exchange`, so it can require code flow for inbound shares + when it acts as Receiving Server. When A later acts as Sending + Server toward Provider B, and B does not advertise + `token-exchange`, A MAY omit `must-exchange-token`. B may then + attempt token exchange against A's `tokenEndPoint` or fall back to + legacy access. A therefore accepts strict inbound shares while + still choosing a legacy-compatible outbound share. + # Share Deletion A `"SHARE_ACCEPTED"` notification followed by a `"SHARE_UNSHARED"` diff --git a/diagrams/code-flow-role-binding.md b/diagrams/code-flow-role-binding.md new file mode 100644 index 0000000..87b2cf9 --- /dev/null +++ b/diagrams/code-flow-role-binding.md @@ -0,0 +1,23 @@ +```mermaid +flowchart TD + ET["exchange-token in discovery"] + TX["token-exchange in criteria"] + EP["tokenEndPoint in discovery"] + MS["must-exchange-token on the share"] + + ET --> P["One provider-level code-flow capability"] + P --> S["Sending Server role"] + P --> R["Receiving Server role"] + + S --> S1["Hosts tokenEndPoint"] + EP --> S1 + + R --> R1["Can honor inbound strict shares"] + R --> R2["Receiver policy for inbound shares"] + TX --> R2 + R2 --> R3["If advertised, inbound shares must include must-exchange-token"] + R3 --> MS + + MS --> M1["Strict share contract"] + M1 --> M2["Receiver must exchange sharedSecret before access"] +``` diff --git a/diagrams/receiver-discovery-policy.md b/diagrams/receiver-discovery-policy.md new file mode 100644 index 0000000..5a5b91c --- /dev/null +++ b/diagrams/receiver-discovery-policy.md @@ -0,0 +1,15 @@ +```mermaid +flowchart TD + A["Provider acts as Receiving Server"] + A --> B{"Does it support the code flow"} + + B -- "Yes" --> C["Advertise exchange-token"] + C --> D{"Does it require code flow for all inbound shares"} + D -- "Yes" --> E["Advertise token-exchange in criteria"] + E --> F["Reject inbound shares that omit must-exchange-token"] + D -- "No" --> G["Do not advertise token-exchange"] + G --> H["Strict inbound shares may still be accepted share by share"] + + B -- "No" --> I["Do not advertise exchange-token"] + I --> J["Cannot honor strict inbound shares"] +``` diff --git a/diagrams/resource-access-paths.md b/diagrams/resource-access-paths.md new file mode 100644 index 0000000..a323d53 --- /dev/null +++ b/diagrams/resource-access-paths.md @@ -0,0 +1,16 @@ +```mermaid +flowchart TD + A["Receiving Server accesses the shared resource"] + A --> B["Inspect protocol.webdav.requirements"] + B --> C{"must-exchange-token present"} + + C -- "Yes" --> D["POST sharedSecret to the Sending Server tokenEndPoint"] + D --> E["Use only the bearer token for WebDAV access"] + + C -- "No" --> F{"Sender discovery exposes exchange-token and tokenEndPoint"} + F -- "Yes" --> G["Receiver may attempt token exchange"] + G --> H{"Exchange succeeds"} + H -- "Yes" --> E + H -- "No" --> I["Fall back to sharedSecret access"] + F -- "No" --> I +``` diff --git a/diagrams/share-creation-preflight.md b/diagrams/share-creation-preflight.md new file mode 100644 index 0000000..1e060dc --- /dev/null +++ b/diagrams/share-creation-preflight.md @@ -0,0 +1,17 @@ +```mermaid +flowchart TD + A["Sending Server wants to create a share"] + A --> B["Query Receiving Server discovery"] + B --> C{"Receiver advertises token-exchange"} + + C -- "Yes" --> D{"Sender exposes exchange-token and tokenEndPoint"} + D -- "Yes" --> E["Include must-exchange-token"] + E --> F["Create strict share"] + F --> G["Do not fall back to legacy access for that share"] + D -- "No" --> H["Do not create the share"] + + C -- "No" --> I{"What is the sender policy for this share"} + I -- "Strict" --> J["Include must-exchange-token voluntarily"] + J --> F + I -- "Legacy" --> K["Send legacy share without must-exchange-token"] +``` diff --git a/schemas/ocm-discovery.json b/schemas/ocm-discovery.json index 4aeaf83..54a1578 100644 --- a/schemas/ocm-discovery.json +++ b/schemas/ocm-discovery.json @@ -22,14 +22,14 @@ }, "capabilities": { "type": "array", - "description": "Capabilities values of 'exchange-token', 'webdav-uri', 'protocol-object', 'invites', 'invite-wayf' defined in draft", + "description": "Capability values of 'enforce-mfa', 'exchange-token', 'http-sig', 'invites', 'invite-wayf', 'notifications', 'protocol-object', and 'webdav-uri' are defined in the draft", "items": { "type": "string" } }, "criteria": { "type": "array", - "description": "Criteria values of 'http-request-signatures', 'token-exchange', 'denlyist' and 'allowlist' are defined in draft", + "description": "Criteria values of 'http-request-signatures', 'token-exchange', 'denylist', 'allowlist', and 'invite' are defined in the draft", "items": { "type": "string" } @@ -43,7 +43,8 @@ }, "tokenEndPoint": { "type": "string", - "format": "uri" + "format": "uri", + "description": "URL of the token endpoint hosted by the Sending Server." } }, "required": [ diff --git a/spec.yaml b/spec.yaml index f1fa552..69b878d 100644 --- a/spec.yaml +++ b/spec.yaml @@ -440,15 +440,22 @@ components: type: string format: uri description: > - Optional URL of the Token Exchange endpoint to obtain bearer tokens in exchange for codes. - If the `exchange-token` capability is exposed, the tokenEndPoint MUST be advertised in the discovery response. + Optional URL of the Token Exchange endpoint hosted by the + provider when acting as Sending Server. The Receiving Server + POSTs here to obtain bearer tokens in exchange for + authorization codes (shared secrets). If the `exchange-token` + capability is exposed, the tokenEndPoint MUST be advertised + in the discovery response. **Token Exchange API:** - This optional endpoint allows obtaining a (potentially short-lived) bearer token in exchange for a secret. + This optional endpoint allows the Receiving Server to obtain + a (potentially short-lived) bearer token in exchange for an + authorization code (shared secret). **HTTP Request:** - Method: POST - - URL: The URL discovered in this field + - URL: The URL hosted by the Sending Server and discovered in + this field - Content-Type: application/x-www-form-urlencoded - Body: TokenRequest schema (form-encoded, required) @@ -644,7 +651,9 @@ components: `sharedSecret` via a signed HTTPS request to tokenEndPoint at the Sending Server, in order to get a short-lived token to be used for subsequent access [RFC6749]. This requirement MAY be used if - the recipient provider exposes the `exchange-token` capability. + the Sending Server exposes the `exchange-token` capability and + `tokenEndPoint`, and MUST be included when the Receiving Server + advertises `token-exchange` in criteria. enum: - must-use-mfa - must-exchange-token