Skip to content

Commit

Permalink
spec: add an example of how to send private updates to specific users
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Jul 7, 2020
1 parent 1b405e0 commit b062b80
Showing 1 changed file with 79 additions and 26 deletions.
105 changes: 79 additions & 26 deletions spec/mercure.md
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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].

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).

Expand All @@ -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
Expand Down Expand Up @@ -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.subscribe` 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

Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand Down

0 comments on commit b062b80

Please sign in to comment.