diff --git a/drafts/application_services.rst b/drafts/application_services.rst new file mode 100644 index 00000000..9c8b6b69 --- /dev/null +++ b/drafts/application_services.rst @@ -0,0 +1,244 @@ +.. TODO + Sometimes application services need to create rooms (e.g. when lazy loading + from room aliases). Created rooms need to have a user that created them, so + federation works (as it relies on an entry existing in m.room.member). We should + be able to add metadata to m.room.member to state that this user is an application + service, a virtual user, etc. + +Application Services +==================== + +Overview +======== + +Application services provide a way of implementing custom serverside functionality +on top of Matrix without the complexity of implementing the full federation API. +By acting as a trusted service logically located behind an existing homeserver, +Application services are decoupled from: + +* Signing or validating federated traffic or conversation history +* Validating authorisation constraints on federated traffic +* Managing routing or retry schemes to the rest of the Matrix federation + +As such, developers can focus entirely on implementing application logic rather +than being concerned with the details of managing Matrix federation. + +Features available to application services include: + +* Privileged subscription to any events available to the homeserver +* Synthesising virtual users +* Synthesising virtual rooms +* Injecting message history for virtual rooms + +Features not provided by application services include: + +* Intercepting and filtering/modifying message or behaviour within a room + (this is a job for a Policy Server, as it requires a single logical focal + point for messages in order to consistently apply the custom business logic) + +Example use cases for application services include: + +* Exposing existing communication services in Matrix + + * Gateways to/from standards-based protocols (SIP, XMPP, IRC, RCS (MSRP), SIMPLE, Lync, etc) + * Gateways to/from closed services (e.g. WhatsApp) + * Gateways could be architected as: + + * Act as a virtual client on the non-Matrix network + (e.g. connect as multiple virtual clients to an IRC or XMPP server) + * Act as a server on the non-Matrix network + (e.g. speak s2s XMPP federation, or IRC link protocol) + * Act as an application service on the non-Matrix network + (e.g. link up as IRC services, or an XMPP component) + * Exposing a non-Matrix client interface listener from the AS + (e.g. listen on port 6667 for IRC clients, or port 5222 for XMPP clients) + + +* Bridging existing APIs into Matrix + * e.g. SMS/MMS aggregator APIs + * Domain-specific APIs such as SABRE + +* Integrating more exotic content into Matrix + * e.g. MIDI<->Matrix gateway/bridge + * 3D world <-> Matrix bridge + +* Application services: + * Search engines (e.g. elasticsearch search indices) + * Notification systems (e.g. send custom pushes for various hooks) + * VoIP Conference services + * Text-to-speech and Speech-to-text services + * Signal processing + * IVR + * Server-machine translation + * Censorship service + * Multi-User Gaming (Dungeons etc) + * Other "constrained worlds" (e.g. 3D geometry representations) + + * applying physics to a 3D world on the serverside + + * (applying gravity and friction and air resistance... collision detection) + * domain-specific merge conflict resolution of events + + * Payment style transactional usecases with transactional guarantees + +Architecture Outline +==================== + +The application service registers with its host homeserver to offer its services. + +In the registration process, the AS provides: + +* Credentials to identify itself as an approved application service for that HS +* Details of the namespaces of users and rooms the AS is acting on behalf of and + "subscribing to" +* Namespaces are defined as a list of regexps against which to match room aliases, + room IDs, and user IDs. Regexps give the flexibility to say, sub-domain MSISDN + ranges per AS, whereas a blunt prefix string does not. These namespaces are further + configured by setting whether they are ``exclusive`` or not. An exclusive namespace + prevents entities other than the aforementioned AS from creating/editing/deleting + entries within that namespace. This does not affect the visibility/readability of + entries within that namespace (e.g. it doesn't prevent users joining exclusive + aliases, or ASes from listening to exclusive aliases, but does prevent both users + and ASes from creating/editing/deleting aliases within that namespace). +* There is overlap between selecting events via the csv2 Filter API and subscribing + to events here - perhaps subscription involves passing a filter token into the + registration API. +* A URL base for receiving requests from the HS (as the AS is a server, + implementers expect to receive data via inbound requests rather than + long-poll outbound requests) + +On HS handling events to unknown users: + +* If the HS receives an event for an unknown user who is in the namespace delegated to + the AS, then the HS queries the AS for the profile of that user. If the AS + confirms the existence of that user (from its perspective), then the HS + creates an account to represent the virtual user. +* The namespace of virtual user accounts should conform to a structure like + ``@.irc.freenode.Arathorn:matrix.org``. This lets Matrix users communicate with + foreign users who are not yet mapped into Matrix via 3PID mappings or through + an existing non-virtual Matrix user by trying to talk to them via a gateway. +* The AS can alternatively preprovision virtual users using the existing CS API + rather than lazy-loading them in this manner. +* The AS may want to link the matrix ID of the sender through to their 3PID in + the remote ecosystem. E.g. a message sent from ``@matthew:matrix.org`` may wish + to originate from Arathorn on irc.freenode.net in the case of an IRC bridge. + It's left as an AS implementation detail as to how the user should authorise + the AS to act on its behalf. + +On HS handling events to unknown rooms: + +* If the HS receives an invite to an unknown room which is in the namespace + delegated to the AS, then the HS queries the AS for the existence of that room. + If the AS confirms its existence (from its perspective), then the HS creates + the room. +* The initial state of the room may be populated by the AS by querying an + initialSync API (probably a subset of the CS initialSync API, to reuse the + same pattern for the equivalent function). As messages have to be signed + from the point of ``m.room.create``, we will not be able to back-populate + arbitrary history for rooms which are lazy-created in this manner, and instead + have to chose the amount of history to be synchronised into the AS as a one-off. +* If exposing arbitrary history is required, then: + + * either: the room history must be preemptively provisioned in the HS by the AS via + the CS API (TODO: meaning the CS API needs to support massaged + timestamps), resulting in conversation history being replicated between + the HS and the source store. + * or: the HS must delegate conversation storage entirely to the + AS using a Storage API (not defined here) which allows the existing + conversation store to back the HS, complete with all necessary Matrix + metadata (e.g. hashes, signatures, federation DAG, etc). This obviously + increases the burden of implementing an AS considerably, but is the only + option if the implementer wants to avoid duplicating conversation history + between the external data source and the HS. + +On HS handling events to existing users and rooms: + +* If the HS receives an event for a user or room that already exists (either + provisioned by the AS or by normal client interactions), then the message + is handled as normal. +* Events in the namespaces of rooms and users that the AS has subscribed to + are pushed to the AS using the same pattern as the federation API (without + any of the encryption or federation metadata). This serves precisely the + same purpose as the CS event stream and has the same data flow semantics + (and indeed an AS implementer could chose to use the CS event stream instead) + + * Events are linearised to avoid the AS having to handle the complexity of + linearisation, and because if linearisation is good enough for CS, it + should be good enough for AS. Should the AS require non-linearised events + from Matrix, it should implement the federation API rather than the AS API + instead. + * HS->AS event pushes are retried for reliability with sequence numbers + (or logical timestamping?) to preserve the linearisation order and ensure + a reliable event stream. + * Clustered HSes must linearise just as they do for the CS API. Clustered + ASes must loadbalance the inbound stream across the cluster as required. + +On AS relaying events from unknown-to-HS users: + +* AS injects the event to the HS using the CS API, irrespective of whether the + target user or room is known to the HS or not. If the HS doesn't recognise + the target it goes through the same lazy-load provisioning as per above. +* The reason for not using a subset of the federation API here is because it + allows AS developers to reuse existing CS SDKs and benefit from the more + meaningful error handling of the CS API. The sending user ID must be + explicitly specified, as it cannot be inferred from the access_token, which + will be the same for all AS requests. + + * TODO: or do we maintain a separate ``access_token`` mapping? It seems like + unnecessary overhead for the AS developer; easier to just use a single + privileged ``access_token`` and just track which ``user_id`` is emitting events? + * If the AS is spoofing the identity of a real (not virtual) matrix user, + we should actually let them log themselves in via OAuth2 to give permission + to the AS to act on their behalf. + * We can't auth gatewayed virtual users from 3rd party systems who are being + relayed into Matrix, as the relaying is happening whether the user likes it + or not. Therefore we do need to be able to spoof sender ID for virtual users. + +On AS relaying events in unknown-to-HS rooms: + +* See above. + +On AS publishing aliases for virtual rooms: + +* AS uses the normal alias management API to preemptively create/delete public + directory entries for aliases for virtual rooms provided by the AS. +* In order to create these aliases, the underlying room ID must also exist, so + at least the ``m.room.create`` of that room must also be prepopulated. It seems + sensible to prepopulate the required initial state and history of the room to + avoid a two-phase prepopulation process. + +On unregistering the AS from the HS: + +* An AS must tell the HS when it is going offline in order to stop receiving + requests from the HS. It does this by hitting an API on the HS. + +AS Visibility: + +* If an AS needs to sniff events in a room in order to operate on them (e.g. + to act as a search engine) but not inject traffic into the room, it should + do so by subscribing to the relevant events without actually joining the room. +* If the AS needs to participate in the room as a virtual user (e.g. an IVR + service, or a bot, or a gatewayed virtual user), it should join the room + normally. +* There are rare instances where an AS may wish to participate in a room + (including inserting messages), but be hidden from the room list - e.g. a + conferencing server focus bot may wish to join many rooms as the focus and + both listen to VoIP setups and inject its own VoIP answers, without ever + being physically seen in the room. In this scenario, the user should set + its presence to 'invisible', a state that HSes should only allow AS-authed + users to set. + +E2E Encryption + +* The AS obviously has no visibility to E2E encrypted messages, unless it is + explicitly added to an encrypted room and participates in the group chat + itself. + +Extensions to CS API +==================== + +* Ability to assert the identity of the virtual user for all methods. +* Ability to massage timestamps when prepopulating historical state and + messages of virtual rooms (either by overriding ``origin_server_ts`` (preferred) or + adding an ``as_ts`` which we expect clients to honour) +* Ability to delete aliases (including from the directory) as well as create them. diff --git a/drafts/as-http-api.rst b/drafts/as-http-api.rst new file mode 100644 index 00000000..3cc8efbb --- /dev/null +++ b/drafts/as-http-api.rst @@ -0,0 +1,572 @@ +.. TODO + Sometimes application services need to create rooms (e.g. when lazy loading + from room aliases). Created rooms need to have a user that created them, so + federation works (as it relies on an entry existing in m.room.member). We + should be able to add metadata to m.room.member to state that this user is an + application service, a virtual user, etc. + +Application Services HTTP API +============================= + +.. contents:: Table of Contents +.. sectnum:: + +Application Service -> Home Server +---------------------------------- +This contains home server APIs which are used by the application service. + +Registration API ``[Draft]`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This API registers the application service with its host homeserver to offer its +services. + +Inputs: + - Credentials (e.g. some kind of string token) + - Namespace[users] + - Namespace[room aliases] + - URL base to receive inbound comms +Output: + - The credentials the HS will use to query the AS with in return. (e.g. some + kind of string token) +Side effects: + - The HS will start delivering events to the URL base specified if this 200s. +API called when: + - The application service wants to register with a brand new home server. +Notes: + - An application service can state whether they should be the only ones who + can manage a specified namespace. This is referred to as an "exclusive" + namespace. An exclusive namespace prevents humans and other application + services from creating/deleting entities in that namespace. Typically, + exclusive namespaces are used when the rooms represent real rooms on + another service (e.g. IRC). Non-exclusive namespaces are used when the + application service is merely augmenting the room itself (e.g. providing + logging or searching facilities). + - Namespaces are represented by POSIX extended regular expressions in JSON. + They look like:: + + users: [ + { + "exclusive": true, + "regex": "@irc\.freenode\.net/.*" + } + ] + +:: + + POST /register + + Request format + { + url: "https://my.application.service.com/matrix/", + as_token: "some_AS_token", + namespaces: { + users: [ + { + "exclusive": true, + "regex": "@irc\.freenode\.net/.*" + } + ], + aliases: [ + { + "exclusive": true, + "regex": "#irc\.freenode\.net/.*" + } + ], + rooms: [ + { + "exclusive": true, + "regex": "!irc\.freenode\.net/.*" + } + ] + } + } + + + Returns: + 200 : Registration accepted. + 400 : Namespaces do not conform to regex + 401 : Credentials need to be supplied. + 403 : AS credentials rejected. + + + 200 OK response format + + { + hs_token: "string" + } + +Unregister API ``[Draft]`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ +This API unregisters a previously registered AS from the home server. + +Inputs: + - AS token +Output: + - None. +Side effects: + - The HS will stop delivering events to the URL base specified for this AS if + this 200s. +API called when: + - The application service wants to stop receiving all events from the HS. + +:: + + POST /unregister + + Request format + { + as_token: "string" + } + + +Home Server -> Application Service +---------------------------------- +This contains application service APIs which are used by the home server. + +User Query ``[Draft]`` +~~~~~~~~~~~~~~~~~~~~~~ + +This API is called by the HS to query the existence of a user on the Application +Service's namespace. + +Inputs: + - User ID + - HS Credentials +Output: + - Whether the user exists. +Side effects: + - User is created on the HS by the AS via CS APIs during the processing of this request. +API called when: + - HS receives an event for an unknown user ID in the AS's namespace, e.g. an + invite event to a room. +Notes: + - When the AS receives this request, if the user exists, it must create the user via + the CS API. + - It can also set arbitrary information about the user (e.g. display name, join rooms, etc) + using the CS API. + - When this setup is complete, the AS should respond to the HS request. This means the AS + blocks the HS until the user is created. + - This is deemed more flexible than alternative methods (e.g. returning a JSON blob with the + user's display name and get the HS to provision the user). +Retry notes: + - The home server cannot respond to the client's request until the response to + this API is obtained from the AS. + - Recommended that home servers try a few times then time out, returning a + 408 Request Timeout to the client. + +:: + + GET /users/$user_id?access_token=$hs_token + + Returns: + 200 : User is recognised. + 404 : User not found. + 401 : Credentials need to be supplied. + 403 : HS credentials rejected. + + + 200 OK response format + + {} + +Room Alias Query ``[Draft]`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This API is called by the HS to query the existence of a room alias on the +Application Service's namespace. + +Inputs: + - Room alias + - HS Credentials +Output: + - Whether the room exists. +Side effects: + - Room is created on the HS by the AS via CS APIs during the processing of + this request. +API called when: + - HS receives an event to join a room alias in the AS's namespace. +Notes: + - When the AS receives this request, if the room exists, it must create the room via + the CS API. + - It can also set arbitrary information about the room (e.g. name, topic, etc) + using the CS API. + - It can send messages as other users in order to populate scrollback. + - When this setup is complete, the AS should respond to the HS request. This means the AS + blocks the HS until the room is created and configured. + - This is deemed more flexible than alternative methods (e.g. returning an initial sync + style JSON blob and get the HS to provision the room). It also means that the AS knows + the room ID -> alias mapping. +Retry notes: + - The home server cannot respond to the client's request until the response to + this API is obtained from the AS. + - Recommended that home servers try a few times then time out, returning a + 408 Request Timeout to the client. + +:: + + GET /rooms/$room_alias?access_token=$hs_token + + Returns: + 200 : Room is recognised. + 404 : Room not found. + 401 : Credentials need to be supplied. + 403 : HS credentials rejected. + + + 200 OK response format + + {} + +Pushing ``[Draft]`` +~~~~~~~~~~~~~~~~~~~ +This API is called by the HS when the HS wants to push an event (or batch of +events) to the AS. + +Inputs: + - HS Credentials + - Event(s) to give to the AS + - HS-generated transaction ID +Output: + - None. + +Data flows: + +:: + + Typical + HS ---> AS : Home server sends events with transaction ID T. + <--- : AS sends back 200 OK. + + AS ACK Lost + HS ---> AS : Home server sends events with transaction ID T. + <-/- : AS 200 OK is lost. + HS ---> AS : Home server retries with the same transaction ID of T. + <--- : AS sends back 200 OK. If the AS had processed these events + already, it can NO-OP this request (and it knows if it is the same + events based on the transacton ID). + + +Retry notes: + - If the HS fails to pass on the events to the AS, it must retry the request. + - Since ASes by definition cannot alter the traffic being passed to it (unlike + say, a Policy Server), these requests can be done in parallel to general HS + processing; the HS doesn't need to block whilst doing this. + - Home servers should use exponential backoff as their retry algorithm. + - Home servers MUST NOT alter (e.g. add more) events they were going to + send within that transaction ID on retries, as the AS may have already + processed the events. + +Ordering notes: + - The events sent to the AS should be linearised, as they are from the event + stream. + - The home server will need to maintain a queue of transactions to send to + the AS. + +:: + + PUT /transactions/$transaction_id?access_token=$hs_token + + Request format + { + events: [ + ... + ] + } + +Client-Server v2 API Extensions +------------------------------- + +Identity assertion +~~~~~~~~~~~~~~~~~~ +The client-server API infers the user ID from the ``access_token`` provided in +every request. It would be an annoying amount of book-keeping to maintain tokens +for every virtual user. It would be preferable if the application service could +use the CS API with its own ``as_token`` instead, and specify the virtual user +they wish to be acting on behalf of. For real users, this would require +additional permissions granting the AS permission to masquerade as a matrix user. + +Inputs: + - Application service token (``access_token``) + + Either: + - User ID in the AS namespace to act as. + Or: + - OAuth2 token of real user (which may end up being an access token) +Notes: + - This will apply on all aspects of the CS API, except for Account Management. + - The ``as_token`` is inserted into ``access_token`` which is usually where the + client token is. This is done on purpose to allow application services to + reuse client SDKs. + +:: + + /path?access_token=$token&user_id=$userid + + Query Parameters: + access_token: The application service token + user_id: The desired user ID to act as. + + /path?access_token=$token&user_token=$token + + Query Parameters: + access_token: The application service token + user_token: The token granted to the AS by the real user + +Timestamp massaging +~~~~~~~~~~~~~~~~~~~ +The application service may want to inject events at a certain time (reflecting +the time on the network they are tracking e.g. irc, xmpp). Application services +need to be able to adjust the ``origin_server_ts`` value to do this. + +Inputs: + - Application service token (``as_token``) + - Desired timestamp +Notes: + - This will only apply when sending events. + +:: + + /path?access_token=$token&ts=$timestamp + + Query Parameters added to the send event APIs only: + access_token: The application service token + ts: The desired timestamp + +Server admin style permissions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The home server needs to give the application service *full control* over its +namespace, both for users and for room aliases. This means that the AS should +be able to create/edit/delete any room alias in its namespace, as well as +create/delete any user in its namespace. No additional API changes need to be +made in order for control of room aliases to be granted to the AS. Creation of +users needs API changes in order to: + +- Work around captchas. +- Have a 'passwordless' user. + +This involves bypassing the registration flows entirely. This is achieved by +including the AS token on a ``/register`` request, along with a login type of +``m.login.application_service`` to set the desired user ID without a password. + +:: + + /register?access_token=$as_token + + Content: + { + type: "m.login.application_service", + user: "" + } + +Application services which attempt to create users or aliases *outside* of +their defined namespaces will receive an error code ``M_EXCLUSIVE``. Similarly, +normal users who attempt to create users or alises *inside* an application +service-defined namespace will receive the same ``M_EXCLUSIVE`` error code. + +ID conventions ``[Draft]`` +-------------------------- +.. NOTE:: + - Giving HSes the freedom to namespace still feels like the Right Thing here. + - Exposing a public API provides the consistency which was the main complaint + against namespacing. + - This may have knock-on effects for the AS registration API. E.g. why don't + we let ASes specify the *URI* regex they want? + +This concerns the well-defined conventions for mapping 3P network IDs to matrix +IDs, which we expect clients to be able to do by themselves. + +User IDs +~~~~~~~~ +Matrix users may wish to directly contact a virtual user, e.g. to send an email. +The URI format is a well-structured way to represent a number of different ID +types, including: + +- MSISDNs (``tel``) +- Email addresses (``mailto``) +- IRC nicks (``irc`` - https://tools.ietf.org/html/draft-butcher-irc-url-04) +- XMPP (xep-0032) +- SIP URIs (RFC 3261) + +As a result, virtual user IDs SHOULD relate to their URI counterpart. This +mapping from URI to user ID can be expressed in a number of ways: + +- Expose a C-S API on the HS which takes URIs and responds with user IDs. +- Munge the URI with the user ID. + +Exposing an API would allow HSes to internally map user IDs however they like, +at the cost of an extra round trip (of which the response can be cached). +Munging the URI would allow clients to apply the mapping locally, but would force +user X on service Y to always map to the same munged user ID. Considering the +exposed API could just be applying this munging, there is more flexibility if +an API is exposed. + +:: + + GET /_matrix/app/v1/user?uri=$url_encoded_uri + + Returns 200 OK: + { + user_id: + } + +Room Aliases +~~~~~~~~~~~~ +We may want to expose some 3P network rooms so Matrix users can join them directly, +e.g. IRC rooms. We don't want to expose every 3P network room though, e.g. mailto, +tel. Rooms which are publicly accessible (e.g. IRC rooms) can be exposed as an alias by +the application service. Private rooms (e.g. sending an email to someone) should not +be exposed in this way, but should instead operate using normal invite/join semantics. +Therefore, the ID conventions discussed below are only valid for public rooms which +expose room aliases. + +Matrix users may wish to join XMPP rooms (e.g. using XEP-0045) or IRC rooms. In both +cases, these rooms can be expressed as URIs. For consistency, these "room" URIs +SHOULD be mapped in the same way as "user" URIs. + +:: + + GET /_matrix/app/v1/alias?uri=$url_encoded_uri + + Returns 200 OK: + { + alias: + } + +Event fields +~~~~~~~~~~~~ +We recommend that any gatewayed events should include an `external_url` field in +their content to provide a way for Matrix clients to link into the 'native' +client from which the event originated. For instance, this could contain the +message-ID for emails/nntp posts, or a link to a blog comment when gatewaying +blog comment traffic in & out of matrix + + +Examples +-------- +.. NOTE:: + - User/Alias namespaces are subject to change depending on ID conventions. + +IRC +~~~ +Pre-conditions: + - Server admin stores the AS token "T_a" on the home server. + - Home server has a token "T_h". + - Home server has the domain "hsdomain.com" + +1. Application service registration + +:: + + AS -> HS: Registers itself with the home server + POST /register + { + url: "https://someapp.com/matrix", + as_token: "T_a", + namespaces: { + users: [ + { + "exclusive": true, + "regex": "@irc\.freenode\.net/.*" + } + ], + aliases: [ + { + "exclusive": true, + "regex": "#irc\.freenode\.net/.*" + } + ] + } + } + + Returns 200 OK: + { + hs_token: "T_h" + } + +2. IRC user "Bob" says "hello?" on "#matrix" at timestamp 1421416883133: + +:: + + - AS stores message as potential scrollback. + - Nothing happens as no Matrix users are in the room. + +3. Matrix user "@alice:hsdomain.com" wants to join "#matrix": + +:: + + User -> HS: Request to join "#irc.freenode.net/#matrix:hsdomain.com" + + HS -> AS: Room Query "#irc.freenode.net/#matrix:hsdomain.com" + GET /rooms/%23irc.freenode.net%2F%23matrix%3Ahsdomain.com?access_token=T_h + [Starts blocking] + AS -> HS: Creates room. Gets room ID "!aasaasasa:hsdomain.com". + AS -> HS: Sets room name to "#matrix". + AS -> HS: Sends message as ""@irc.freenode.net/Bob:hsdomain.com" + PUT /rooms/%21aasaasasa%3Ahsdomain.com/send/m.room.message + ?access_token=T_a + &user_id=%40irc.freenode.net%2FBob%3Ahsdomain.com + &ts=1421416883133 + { + body: "hello?" + msgtype: "m.text" + } + HS -> AS: User Query "@irc.freenode.net/Bob:hsdomain.com" + GET /users/%40irc.freenode.net%2FBob%3Ahsdomain.com?access_token=T_h + [Starts blocking] + AS -> HS: Creates user using CS API extension. + POST /register?access_token=T_a + { + type: "m.login.application_service", + user: "irc.freenode.net/Bob" + } + AS -> HS: Set user display name to "Bob". + [Finishes blocking] + [Finished blocking] + + - HS sends room information back to client. + +4. @alice:hsdomain.com says "hi!" in this room: + +:: + + User -> HS: Send message "hi!" in room !aasaasasa:hsdomain.com + + - HS sends message. + - HS sees the room ID is in the AS namespace and pushes it to the AS. + + HS -> AS: Push event + PUT /transactions/1?access_token=T_h + { + events: [ + { + content: { + body: "hi!", + msgtype: "m.text" + }, + origin_server_ts: , + user_id: "@alice:hsdomain.com", + room_id: "!aasaasasa:hsdomain.com", + type: "m.room.message" + } + ] + } + + - AS passes this through to IRC. + + +5. IRC user "Bob" says "what's up?" on "#matrix" at timestamp 1421418084816: + +:: + + IRC -> AS: "what's up?" + AS -> HS: Send message via CS API extension + PUT /rooms/%21aasaasasa%3Ahsdomain.com/send/m.room.message + ?access_token=T_a + &user_id=%40irc.freenode.net%2FBob%3Ahsdomain.com + &ts=1421418084816 + { + body: "what's up?" + msgtype: "m.text" + } + + - HS modifies the user_id and origin_server_ts on the event and sends it. diff --git a/drafts/media_repository.rst b/drafts/media_repository.rst index 14a7c967..879e1b85 100644 --- a/drafts/media_repository.rst +++ b/drafts/media_repository.rst @@ -52,7 +52,8 @@ within a given rectangle. Homeservers may generate thumbnails for content uploaded to remote homeservers themselves or may rely on the remote homeserver to thumbnail the content. Homeservers may return thumbnails of a different size to that -requested. However homeservers should provide extact matches where reasonable. +requested. However homeservers should provide exact matches where reasonable. +Homeservers must never upscale images. Security -------- @@ -73,4 +74,4 @@ Clients may try to access a large number of remote files through a homeserver. Homeservers should restrict the number and size of remote files that it caches. Clients or remote homeservers may try to upload malicious files targeting -vunerabilities in either the homeserver thumbnailing or the client decoders. +vulnerabilities in either the homeserver thumbnailing or the client decoders. diff --git a/drafts/pstn_gatewaying.txt b/drafts/pstn_gatewaying.txt new file mode 100644 index 00000000..2cb8c649 --- /dev/null +++ b/drafts/pstn_gatewaying.txt @@ -0,0 +1,48 @@ +Gatewaying to the PSTN via Matrix Application Services +====================================================== + +Matrix Application Services (AS) provides a way for PSTN users to interact +with Matrix via an AS acting as a gateway. Each PSTN user is represented as a +virtual user on a specific homeserver maintained by the AS. Typically the AS +is provisioned on a well-known AS-supplier HS (e.g. matrix.openmarket.com) or +is a service provisioned on the user's local HS. + +In either scenario, the AS maintains virtual users of form +@.tel.e164:homeserver. These are lazily created (as per the AS spec) when +matrix users try to contact a user id of form @.tel.*:homeserver, or when the +AS needs to inject traffic into the HS on behalf of the PSTN user. The reason +for these being a visible virtual user rather than an invisible user or an +invisible sniffing AS is because they do represent real physical 3rd party +endpoints in the PSTN, and need to be able to send return messages. + +Communication with an actual PSTN user happens in a normal Matrix room, which +for 1:1 matrix<->pstn contact will typically store all conversation history +with that user. On first contact, the matrix user invites the virtual user +into the room (or vice versa). In the event of switching to another AS-enabled +HS, the matrix user would kick the old AS and invite the new one. In the event +of needing loadbalancing between two SMS gateways (for instance), the user +would set visibility flags (TODO: specify per-message ACLs, or use crypto to +only sign messages so they're visible to certain other users?) to adjust which +virtual AS users could see which messages in the room. + +For group chat, one or more AS virtual users may be invited to a group chat, +where-upon they will relay all the traffic in that group chat through to their +PSTN counterpart (and vice versa). This behaviour requires no additional +functionality beyond that required to support 1:1 chat. + +When contacting a user, Matrix clients should check whether a given E.164 +number is already mapped to a real Matrix user by querying the identity +servers (or subscribing to identity updates for a given E.164 number. TODO: ID +server subscriptions). If the E.164 number has a validated mapping in the ID +server to a Matrix ID, then this target ID should be used instead of +contacting the virtual user. + +It's likely that PSTN gateway ASes will need to charge the end-user for use of +the gateway. The AS must therefore track credit per matrix ID it interacts +with, and stop gatewaying as desired once credit is exhausted. The task of +extracting credit from the end-user and adding it to the AS is not covered by +the Matrix specification. + +For SMS routing, options are: + * Terminate traffic only (from a shared shortcode originator) + * Two-way traffic via a VMN. To save allocating huge numbers of VMNs to Matrix users, the VMN can be allocated from a pool such that each {caller,callee} tuple is unique (but the caller number will only work from that specific callee). \ No newline at end of file diff --git a/drafts/push_csapi.rst b/drafts/push_csapi.rst new file mode 100644 index 00000000..9cef9004 --- /dev/null +++ b/drafts/push_csapi.rst @@ -0,0 +1,291 @@ +Push Notifications +================== + +Pushers +------- +To receive any notification pokes at all, it is necessary to configure a +'pusher' on the Home Server that you wish to receive notifications from. There +is a single API endpoint for this:: + + POST $PREFIX/pushers/set + +This takes a JSON object with the following keys: + +pushkey + This is a unique identifier for this pusher. The value you should use for this + is the routing or destination address information for the notification, for + example, the APNS token for APNS or the Registration ID for GCM. If your + notification client has no such concept, use any unique identifier. +kind + The kind of pusher to configure. 'http' makes a pusher that sends HTTP pokes. + null deletes the pusher. +profile_tag + This is a string that determines what set of device rules will be matched when + evaluating push rules for this pusher. It is an arbitrary string. Multiple + devices maybe use the same profile_tag. It is advised that when an app's + data is copied or restored to a different device, this value remain the same. + Client apps should offer ways to change the profile_tag, optionally copying + rules from the old profile tag. +app_id + appId is a reverse-DNS style identifier for the application. It is recommended + that this end with the platform, such that different platform versions get + different app identifiers. Max length, 64 chars. +app_display_name + A string that will allow the user to identify what application owns this + pusher. +device_display_name + A string that will allow the user to identify what device owns this pusher. +lang + The preferred language for receiving notifications (eg, 'en' or 'en-US') +data + A dictionary of information for the pusher implementation itself. For HTTP + pushers, this must contain a 'url' key which is a string of the URL that + should be used to send notifications. + +If the pusher was created successfully, an empty JSON dictionary is returned. + + +Push Rules +---------- +Home Servers have an interface to configure what events trigger notifications. +This behaviour is configured through 'Push Rules'. Push Rules come in a variety +of different kinds and each kind of rule has an associated priority. The +different kinds of rule, in descending order of priority, are: + +Override Rules + The highest priority rules are user-configured overrides. +Content Rules + These configure behaviour for (unencrypted) messages that match certain + patterns. Content rules take one parameter, 'pattern', that gives the pattern + to match against. This is treated in the same way as pattern for event_match + conditions, below. +Room Rules + These change the behaviour of all messages to a given room. The rule_id of a + room rule is always the ID of the room that it affects. +Sender + These rules configure notification behaviour for messages from a specific, + named Matrix user ID. The rule_id of Sender rules is always the Matrix user + ID of the user whose messages theyt apply to. +Underride + These are identical to override rules, but have a lower priority than content, + room and sender rules. + +In addition, each kind of rule may be either global or device-specific. Device +specific rules only affect delivery of notifications via pushers with a matching +profile_tag. All device-specific rules are higher priority than all global +rules. Thusly, the full list of rule kinds, in descending priority order, is as +follows: + + * Device-specific Override + * Device-specific Content + * Device-specific Room + * Device-specific Sender + * Device-specific Underride + * Global Override + * Global Content + * Global Room + * Global Sender + * Global Underride + +For some kinds of rule, rules of the same kind also have an ordering with +respect to one another. The kinds that do not are room and sender rules where +the rules are mutually exclusive by definition and therefore an ordering would +be redundant. Actions for the highest priority rule and only that rule apply +(for example, a set_tweak action in a lower priority rule will not apply if a +higher priority rule matches, even if that rule does not specify any tweaks). + +Rules also have an identifier, rule_id, which is a string. The rule_id is +unique within the kind of rule and scope: rule_ids need not be unique between +rules of the same kind on different devices. + +A home server may also have server default rules of each kind and in each scope. +Server default rules are lower priority than user-defined rules in each scope. +Server defined rules do not have a rule_id except when it is necessary to derive +the function of the rule (ie. in room and sender rules). Server default rules +have an attribute, "default" set to true. + +Push Rules: Actions: +-------------------- +All rules have an associated list of 'actions'. An action affects if and how a +notification is delievered for a matching event. This standard defines the +following actions, although if Home servers wish to support more, they are free +to do so: + +notify + This causes each matching event to generate a notification. +dont_notify + Prevents this event from generating a notification +coalesce + This enables notifications for matching events but activates Home Server + specific behaviour to intelligently coalesce multiple events into a single + notification. Not all Home Servers may support this. Those that do not should + treat it as the 'notify' action. +set_tweak + Sets an entry in the 'tweaks' dictionary key that is sent in the notification + poke. This takes the form of a dictionary with a 'set_tweak' key whose value + is the name of the tweak to set. It must also have a 'value' key which is + the value to which it should be set. + +Actions that have no parameters are represented as a string. Otherwise, they are +represented as a dictionary with a key equal to their name and other keys as +their parameters, eg. { "set_tweak": "sound", "value": "default" } + +Push Rule Actions: Tweaks +------------------------- +The 'set_tweak' key action is used to add an entry to the 'tweaks' dictionary +that is sent in the notification poke. The following tweaks are defined: + +sound + A sound to be played when this notification arrives. 'default' means to + play a default sound. + +Tweaks are passed transparently through the Home Server so client applications +and push gateways may agree on additional tweaks, for example, how to flash the +notification light on a mobile device. + +Push Rules: Conditions: +----------------------- +Override, Underride and Default rules have a list of 'conditions'. All +conditions must hold true for an event in order for a rule to be applied to an +event. A rule with no conditions always matches. Matrix specifies the following +conditions, although if Home Servers wish to support others, they are free to +do so: + +event_match + This is a glob pattern match on a field of the event. Parameters: + * 'key': The dot-separated field of the event to match, eg. content.body + * 'pattern': The glob-style pattern to match against. Patterns with no + special glob characters should be treated as having asterisks + prepended and appended when testing the condition. +profile_tag + Matches the profile_tag of the device that the notification would be + delivered to. Parameters: + + * 'profile_tag': The profile_tag to match with. +contains_display_name + This matches unencrypted messages where content.body contains the owner's + display name in that room. This is a separate rule because display names may + change and as such it would be hard to maintain a rule that matched the user's + display name. This condition has no parameters. +room_member_count + This matches the current number of members in the room. + * 'is': A decimal integer optionally prefixed by one of, '==', '<', '>', + '>=' or '<='. A prefix of '<' matches rooms where the member count is + strictly less than the given number and so forth. If no prefix is present, + this matches rooms where the member count is exactly equal to the given + number (ie. the same as '=='). + +Room, Sender, User and Content rules do not have conditions in the same way, +but instead have predefined conditions, the behaviour of which can be configured +using parameters named as described above. In the cases of room and sender +rules, the rule_id of the rule determines its behaviour. + +Push Rules: API +--------------- +Rules live under a hierarchy in the REST API that resembles:: + + $PREFIX/pushrules/// + +The component parts are as follows: + +scope + Either 'global' or 'device/' to specify global rules or + device rules for the given profile_tag. +kind + The kind of rule, ie. 'override', 'underride', 'sender', 'room', 'content'. +rule_id + The identifier for the rule. + +To add or change a rule, a client performs a PUT request to the appropriate URL. +When adding rules of a type that has an ordering, the client can add parameters +that define the priority of the rule: + +before + Use 'before' with a rule_id as its value to make the new rule the next-more + important rule with respect to the given rule. +after + This makes the new rule the next-less important rule relative to the given + rule. + +All requests to the push rules API also require an access_token as a query +paraemter. + +The content of the PUT request is a JSON object with a list of actions under the +'actions' key and either conditions (under the 'conditions' key) or the +appropriate parameters for the rule (under the appropriate key name). + +Examples: + +To create a rule that suppresses notifications for the room with ID '!dj234r78wl45Gh4D:matrix.org':: + + curl -X PUT -H "Content-Type: application/json" -d '{ "actions" : ["dont_notify"] }' "http://localhost:8008/_matrix/client/api/v1/pushrules/global/room/%21dj234r78wl45Gh4D%3Amatrix.org?access_token=123456" + +To suppress notifications for the user '@spambot:matrix.org':: + + curl -X PUT -H "Content-Type: application/json" -d '{ "actions" : ["dont_notify"] }' "http://localhost:8008/_matrix/client/api/v1/pushrules/global/sender/%40spambot%3Amatrix.org?access_token=123456" + +To always notify for messages that contain the work 'cake' and set a specific sound (with a rule_id of 'SSByZWFsbHkgbGlrZSBjYWtl'):: + + curl -X PUT -H "Content-Type: application/json" -d '{ "pattern": "cake", "actions" : ["notify", {"set_sound":"cakealarm.wav"}] }' "http://localhost:8008/_matrix/client/api/v1/pushrules/global/content/SSByZWFsbHkgbGlrZSBjYWtl?access_token=123456" + +To add a rule suppressing notifications for messages starting with 'cake' but ending with 'lie', superseeding the previous rule:: + + curl -X PUT -H "Content-Type: application/json" -d '{ "pattern": "cake*lie", "actions" : ["notify"] }' "http://localhost:8008/_matrix/client/api/v1/pushrules/global/content/U3BvbmdlIGNha2UgaXMgYmVzdA?access_token=123456&before=SSByZWFsbHkgbGlrZSBjYWtl" + +To add a custom sound for notifications messages containing the word 'beer' in any rooms with 10 members or fewer (with greater importance than the room, sender and content rules):: + + curl -X PUT -H "Content-Type: application/json" -d '{ "conditions": [{"kind": "event_match", "key": "content.body", "pattern": "beer" }, {"kind": "room_member_count", "is": "<=10"}], "actions" : ["notify", {"set_sound":"beeroclock.wav"}] }' "http://localhost:8008/_matrix/client/api/v1/pushrules/global/override/U2VlIHlvdSBpbiBUaGUgRHVrZQ?access_token=123456 + + +To delete rules, a client would just make a DELETE request to the same URL:: + + curl -X DELETE "http://localhost:8008/_matrix/client/api/v1/pushrules/global/room/%23spam%3Amatrix.org?access_token=123456" + + +Retrieving the current ruleset can be done either by fetching individual rules +using the scheme as specified above. This returns the rule in the same format as +would be given in the PUT API with the addition of a rule_id:: + + curl "http://localhost:8008/_matrix/client/api/v1/pushrules/global/room/%23spam%3Amatrix.org?access_token=123456" + +Returns:: + + { + "actions": [ + "dont_notify" + ], + "rule_id": "#spam:matrix.org" + } + +Clients can also fetch broader sets of rules by removing path components. +Requesting the root level returns a structure as follows:: + + { + "device": { + "exampledevice": { + "content": [], + "override": [], + "room": [ + { + "actions": [ + "dont_notify" + ], + "rule_id": "#spam:matrix.org" + } + ], + "sender": [], + "underride": [] + } + }, + "global": { + "content": [], + "override": [], + "room": [], + "sender": [], + "underride": [] + } + } + +Adding patch components to the request drills down into this structure to filter +to only the requested set of rules. + diff --git a/drafts/push_overview.rst b/drafts/push_overview.rst new file mode 100644 index 00000000..216ebdcd --- /dev/null +++ b/drafts/push_overview.rst @@ -0,0 +1,32 @@ +Push Notifications +================== + +Matrix supports push notifications as a first class citizen. Home Servers send +notifications of user events to user-configured HTTP endpoints. User may also +configure a number of rules that determine what events generate notifications. +These are all stored and managed by the users home server such that settings can +be reused between client apps as appropriate. + +Nomenclature +------------ + +Pusher + A 'pusher' is an activity in the Home Server that manages the sending + of HTTP notifications for a single device of a single user. + +Push Rules + A push rule is a single rule, configured by a matrix user, that gives + instructions to the Home Server about whether an event should be notified + about and how given a set of conditions. Matrix clients allow the user to + configure these. They create and view them via the Client to Server REST API. + +Push Gateway + A push gateway is a server that receives HTTP event notifications from Home + Servers and passes them on to a different protocol such as APNS for iOS + devices or GCM for Android devices. Matrix.org provides a reference push + gateway, 'sygnal'. A client app tells a Home Server what push gateway + to send notifications to when it sets up a pusher. + +For information on the client-server API for setting pushers and push rules, see +the Client Server API section. For more information on the format of HTTP +notifications, see the HTTP Notification Protocol section. diff --git a/drafts/push_pgwapi.rst b/drafts/push_pgwapi.rst new file mode 100644 index 00000000..92035581 --- /dev/null +++ b/drafts/push_pgwapi.rst @@ -0,0 +1,135 @@ +Push Notifications: HTTP Notification Protocol +============================================== +This describes the format used by "http" pushers to send notifications of +events. + +Notifications are sent as HTTP POST requests to the URL configured when the +pusher is created, but Matrix strongly recommends that the path should be:: + + /_matrix/push/v1/notify + +The body of the POST request is a JSON dictionary. The format +is as follows:: + + { + "notification": { + "id": "$3957tyerfgewrf384", + "room_id": "!slw48wfj34rtnrf:example.com", + "type": "m.room.message", + "sender": "@exampleuser:matrix.org", + "sender_display_name": "Major Tom", + "room_name": "Mission Control", + "room_alias": "#exampleroom:matrix.org", + "prio": "high", + "content": { + "msgtype": "m.text", + "body": "I'm floating in a most peculiar way." + } + }, + "counts": { + "unread" : 2, + "missed_calls": 1 + } + "devices": [ + { + "app_id": "org.matrix.matrixConsole.ios", + "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/", + "pushkey_ts": 12345678, + "data" : { + }, + "tweaks": { + "sound": "bing.wav" + } + } + ] + } + } + +The contents of this dictionary are defined as follows: + +id + An identifier for this notification that may be used to detect duplicate + notification requests. This is not necessarily the ID of the event that + triggered the notification. +room_id + The ID of the room in which this event occurred. +type + The type of the event as in the event's 'type' field. +sender + The sender of the event as in the corresponding event field. +sender_display_name + The current display name of the sender in the room in which the event + occurred. +room_name + The name of the room in which the event occurred. +room_alias + An alias to display for the room in which the event occurred. +prio + The priority of the notification. Acceptable values are 'high' or 'low. If + omitted, 'high' is assumed. This may be used by push gateways to deliver less + time-sensitive notifications in a way that will preserve battery power on + mobile devices. +content + The 'content' field from the event, if present. If the event had no content + field, this field is omitted. +counts + This is a dictionary of the current number of unacknowledged communications + for the recipient user. Counts whose value is zero are omitted. +unread + The number of unread messages a user has accross all of the rooms they are a + member of. +missed_calls + The number of unacknowledged missed calls a user has accross all rooms of + which they are a member. +device + This is an array of devices that the notification should be sent to. +app_id + The app_id given when the pusher was created. +pushkey + The pushkey given when the pusher was created. +pushkey_ts + The unix timestamp (in seconds) when the pushkey was last updated. +data + A dictionary of additional pusher-specific data. For 'http' pushers, this is + the data dictionary passed in at pusher creation minus the 'url' key. +tweaks + A dictionary of customisations made to the way this notification is to be + presented. These are added by push rules. +sound + Sets the sound file that should be played. 'default' means that a default + sound should be played. + +The recipient of an HTTP notification should respond with an HTTP 2xx response +when the notification has been processed. If the endpoint returns an HTTP error +code, the Home Server should retry for a reasonable amount of time with a +reasonable backoff scheme. + +The endpoint should return a JSON dictionary as follows:: + + { + "rejected": [ "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/" ] + } + +Whose keys are: + +rejected + A list of all pushkeys given in the notification request that are not valid. + These could have been rejected by an upstream gateway because they have + expired or have never been valid. Home Servers must cease sending notification + requests for these pushkeys and remove the associated pushers. It may not + necessarily be the notification in the request that failed: it could be that + a previous notification to the same pushkey failed. + +Push: Recommendations for APNS +------------------------------ +For sending APNS notifications, the exact format is flexible and up to the +client app and its push gateway to agree on (since APNS requires that the sender +have a private key owned by the app developer, each app must have its own push +gateway). However, Matrix strongly recommends: + + * That the APNS token be base64 encoded and used as the pushkey. + * That a different app_id be used for apps on the production and sandbox + APS environments. + * That APNS push gateways do not attempt to wait for errors from the APNS + gateway before returning and instead to store failures and return + 'rejected' responses next time that pushkey is used.