From 30f28919ea4cc31a1eacd78e14c09b064a2d89c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 7 Jul 2020 17:38:50 +0200 Subject: [PATCH] spec: add an example of how to send private updates to specific users --- spec/mercure.md | 105 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 26 deletions(-) diff --git a/spec/mercure.md b/spec/mercure.md index d46eca2c..d165837e 100644 --- a/spec/mercure.md +++ b/spec/mercure.md @@ -2,12 +2,13 @@ title = "The Mercure Protocol" area = "Internet" workgroup = "Network Working Group" +submissiontype = "IETF" [seriesInfo] name = "Internet-Draft" -value = "draft-dunglas-mercure-06" +value = "draft-dunglas-mercure-07" stream = "IETF" -status = "informational" +status = "standard" [[author]] initials="K." @@ -90,11 +91,11 @@ The publisher **MAY** provide the following target attributes in the Link Header * `last-event-id`: the identifier of the last event dispatched by the publisher at the time of the generation of this resource. If provided, it **MUST** be passed to the hub through a query parameter called `Last-Event-ID` and will be used to ensure that possible updates having been - made during between the resource generation time and the connection to the hub are not lost. See - (#reconciliation). + made between the resource generation by the server and the connection to the hub are not lost. + See (#reconciliation). - * `content-type`: the content type of the updates that will pushed by the hub. If omitted, the - subscriber **MUST** assume that the content type will be the same as that of the original + * `content-type`: the content type of the updates that will be pushed by the hub. If omitted, + the subscriber **MUST** assume that the content type will be the same as that of the original resource. Setting the `content-type` attribute is especially useful to hint that partial updates will be pushed, using formats such as JSON Patch [@RFC6902] or JSON Merge Patch [@RFC7386]. @@ -214,8 +215,9 @@ To determine if a string matches a selector, the following steps must be followe The subscriber subscribes to a URL exposed by a hub to receive updates from one or many topics. To subscribe to updates, the client opens an HTTPS connection following the Server-Sent Events specification [@!W3C.REC-eventsource-20150203] to the hub's subscription URL advertised by the -publisher. The `GET` HTTP method must be used. The connection **SHOULD** use HTTP/2 to leverage -mutliplexing and other advanced features of this protocol. +publisher. The `GET` HTTP method must be used. The connection **SHOULD** use HTTP version 2 or +superior to leverage multiplexing and other performance-oriented related features provided by these +versions. The subscriber specifies the list of topics to get updates from by using one or several query parameters named `topic`. The `topic` query parameters **MUST** contain topic selectors. See @@ -300,11 +302,11 @@ The request **MUST** be encoded using the `application/x-www-form-urlencoded` fo * `id` (optional): the topic's revision identifier: it will be used as the SSE's `id` property. The provided id **MUST NOT** start with the `#` character. The provided id **SHOULD** be a valid - IRI. If omitted, the hub **MUST** generate a valid IRI [@!RFC3987]. An UUID [@RFC4122] or a DID - (@W3C.WD-did-core-20200421) **MAY** be used. Alternatively the hub **MAY** generate a relative - URI composed of a fragment (starting with `#`). This is convenient to return an offset or a - sequence that is unique for this hub. Even if provided, the hub **MAY** ignore the id provided - by the client and generate its own id. + IRI. If omitted, the hub **MUST** generate a valid IRI [@!RFC3987]. An UUID [@RFC4122] or a + [DID](https://www.w3.org/TR/did-core/) **MAY** be used. Alternatively the hub **MAY** generate a + relative URI composed of a fragment (starting with `#`). This is convenient to return an offset + or a sequence that is unique for this hub. Even if provided, the hub **MAY** ignore the id + provided by the client and generate its own id. * `type` (optional): the SSE's `event` property (a specific event type). @@ -314,6 +316,22 @@ In the event of success, the HTTP response's body **MUST** be the `id` associate generated by the hub and a success HTTP status code **MUST** be returned. The publisher **MUST** be authorized to publish updates. See (#authorization). +Example: + +~~~ http +POST /.well-known/mercure HTTP/1.1 +Host: example.com +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer [snip] + +topic=https://example.com/foo&data=the%20content + +HTTP/1.1 200 OK +Content-type: text/plain + +urn:uuid:e1ee88e2-532a-4d6f-ba70-f0f8bd584022 +~~~ + # Authorization To ensure that they are authorized, both publishers and subscribers must present a valid JWS @@ -388,8 +406,43 @@ To receive updates marked as `private`, the JWS presented by the subscriber **MU claim named `mercure` with a key named `subscribe` that contains an array of topic selectors. See (#topic-selectors). -The hub **MUST** check that at least one topic of the update to dispatch matches at least one topic -selector provided in `mercure.subscribe`. +The hub **MUST** check that at least one topic of the update to dispatch (*canonical* or +*alternate*) matches at least one topic selector provided in `mercure.subscribe`. + +This behavior makes it possible to subscribe to several topics using URI templates while +guaranteeing that only authorized subscribers will receive updates marked as private (even if their +canonical topics are matched by these templates). + +Let's say that a subscriber wants to receive updates concerning all *book* resources it has access +to. The subscriber can use the topic selector `https://example.com/books/{id}` as value of the +`topic` query parameter. Adding this same URI template to the `mercure.publisher` claim of the JWS +presented by the subscriber to the hub would allow this subscriber to receive all updates for all +book resources. It is not what we want here: this subscriber is only authorized to access **some** +of these resources. + +To solve this problem, the `mercure.subscribe` claim could contain a topic selector such as: +`https://example.com/users/foo/{?topic}`. + +The publisher could then take advantage of the previously described behavior by +publishing a private update having `https://example.com/books/1` as canonical topic and +`https://example.com/users/foo/?topic=https%3A%2F%2Fexample.com%2Fbooks%2F1` as alternate topic: + +~~~ http +POST /.well-known/mercure HTTP/1.1 +Host: example.com +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer [snip] + +topic=https://example.com/books/1&topic=https://example.com/users/foo/?topic=https%3A%2F%2Fexample.com%2Fbooks%2F1&private=on +~~~ + +The subscriber is subscribed to `https://example.com/books/{id}` that is matched by the +canonical topic of the update. This canonical topic isn't matched by the topic selector +provided in its JWS claim `mercure.subscribe`. However, an alternate topic of the update, +`https://example.com/users/foo/?topic=https%3A%2F%2Fexample.com%2Fa-random-topic`, is matched by it. +Consequently, this private update will be received by this subscriber, while other updates having +a canonical topic matched by the selector provided in a `topic` query parameter but not matched by +selectors in the `mercure.subscribe` claim will not. ## Payload @@ -408,7 +461,7 @@ The protocol allows to reconciliate states after a reconnection. It can also be To allow re-establishment in case of connection lost, events dispatched by the hub **MUST** include an `id` property. The value contained in this `id` property **SHOULD** be an IRI [@!RFC3987]. An -UUID [@RFC4122] or a DID (@W3C.WD-did-core-20200421) **MAY** be used. +UUID [@RFC4122] or a [DID](https://www.w3.org/TR/did-core/) **MAY** be used. According to the server-sent events specification, in case of connection lost the subscriber will try to automatically re-connect. During the @@ -417,7 +470,7 @@ re-connection, the subscriber **MUST** send the last received event id in a In order to fetch any update dispatched between the initial resource generation by the publisher and the connection to the hub, the subscriber **MUST** send the event id provided during the discovery -in the `last-event-id` as the last event id. See (#discovery). +as a `Last-Event-ID` header or query parameter. See (#discovery). `EventSource` implementations may not allow to set HTTP headers during the first connection (before a reconnection) and implementations in web browsers don't allow to set it. @@ -447,7 +500,7 @@ partial updates in the JSON Patch [@RFC6902] format, or when using the hub as an updates lost can cause data lost. To detect if a data lost ocurred, the subscriber **CAN** compare the value of the `Last-Event-ID` -response HTTP header with `Last-Event-ID` it requested. In case of data lost, the subscriber +response HTTP header with the `Last-Event-ID` it requested. In case of data lost, the subscriber **SHOULD** re-fetch the original topic. Note: Native `EventSource` implementations don't give access to headers associated with the HTTP @@ -466,9 +519,9 @@ Variables are templated and expanded in accordance with [@!RFC6570]. ## Subscription Events -If the hub supports the active subscription feature, it **MUST** publish an update when a +If the hub supports the active subscriptions feature, it **MUST** publish an update when a subscription is created or terminated. If this feature is implemented by the hub, an update **MUST** -be dispatched every time that a subscription is created or terminated. +be dispatched every time a subscription is created or terminated. The topic of these updates **MUST** be an expansion of `/.well-known/mercure/subscriptions/{topic}/{subscriber}`. `{topic}` is the topic selector used for @@ -479,8 +532,8 @@ variables, values will usually contain the `:`, `/`, `{` and `}` characters. Per characters are reserved. They **MUST** be percent encoded during the expansion process. If a subscriber has several subscriptions, it **SHOULD** be identified by the same -identifier. `{subscriber}` **SHOULD** be an IRI [@!RFC3987]. An UUID [@RFC4122] or a DID -(@W3C.WD-did-core-20200421) **MAY** be used. +identifier. `{subscriber}` **SHOULD** be an IRI [@!RFC3987]. An UUID [@RFC4122] or a +[DID](https://www.w3.org/TR/did-core/) **MAY** be used. The content of the update **MUST** be a JSON-LD [@!W3C.REC-json-ld-20140116] document containing at least the following properties: @@ -533,8 +586,8 @@ The web API **MUST** expose endpoints following these patterns: * `/.well-known/mercure/subscriptions`: the collection of subscriptions - * `/.well-known/mercure/subscriptions/{topic}`: the collection subscriptions for the given topic - selector + * `/.well-known/mercure/subscriptions/{topic}`: the collection of subscriptions for the given + topic selector * `/.well-known/mercure/subscriptions/{topic}/{subscriber}`: a specific subscription @@ -711,8 +764,8 @@ the hub. To make sure that the message content can not be read by the hub, the publisher **MAY** encrypt the message before sending it to the hub. The publisher **SHOULD** use JSON Web Encryption [@!RFC7516] to encrypt the update content. The publisher **MAY** provide the URL pointing to the relevant -encryption key(s) in the `key-set` attribute of the Link HTTP header during the discovery. See -(#discovery). The `key-set` attribute **MUST** contain a key encoded using the JSON Web Key Set +encryption key(s) in the `key-set` attribute of the `Link` HTTP header during the discovery. See +(#discovery). The `key-set` attribute **MUST** link to a key encoded using the JSON Web Key Set [@!RFC7517] format. Any other out-of-band mechanism **MAY** be used instead to share the key between the publisher and the subscriber.