mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-14 18:53:53 +01:00
Merge branch develop into server2server_signing
Conflicts: synapse/app/homeserver.py
This commit is contained in:
commit
984e207b59
22 changed files with 911 additions and 527 deletions
83
README.rst
83
README.rst
|
@ -20,18 +20,21 @@ The overall architecture is::
|
||||||
WARNING
|
WARNING
|
||||||
=======
|
=======
|
||||||
|
|
||||||
**Synapse is currently in a state of rapid development, and not all features are yet functional.
|
**Synapse is currently in a state of rapid development, and not all features
|
||||||
Critically, some security features are still in development, which means Synapse can *not*
|
are yet functional. Critically, some security features are still in
|
||||||
be considered secure or reliable at this point.** For instance:
|
development, which means Synapse can *not* be considered secure or reliable at
|
||||||
|
this point.** For instance:
|
||||||
|
|
||||||
- **SSL Certificates used by server-server federation are not yet validated.**
|
- **SSL Certificates used by server-server federation are not yet validated.**
|
||||||
- **Room permissions are not yet enforced on traffic received via federation.**
|
- **Room permissions are not yet enforced on traffic received via federation.**
|
||||||
- **Homeservers do not yet cryptographically sign their events to avoid tampering**
|
- **Homeservers do not yet cryptographically sign their events to avoid
|
||||||
|
tampering**
|
||||||
- Default configuration provides open signup to the service from the internet
|
- Default configuration provides open signup to the service from the internet
|
||||||
|
|
||||||
Despite this, we believe Synapse is more than useful as a way for experimenting and
|
Despite this, we believe Synapse is more than useful as a way for experimenting
|
||||||
exploring Synapse, and the missing features will land shortly. **Until then, please do *NOT*
|
and exploring Synapse, and the missing features will land shortly. **Until
|
||||||
use Synapse for any remotely important or secure communication.**
|
then, please do *NOT* use Synapse for any remotely important or secure
|
||||||
|
communication.**
|
||||||
|
|
||||||
|
|
||||||
Quick Start
|
Quick Start
|
||||||
|
@ -46,17 +49,21 @@ To get up and running:
|
||||||
- To simply play with an **existing** homeserver you can
|
- To simply play with an **existing** homeserver you can
|
||||||
just go straight to http://matrix.org/alpha.
|
just go straight to http://matrix.org/alpha.
|
||||||
|
|
||||||
- To run your own **private** homeserver on localhost:8008, install synapse with
|
- To run your own **private** homeserver on localhost:8008, generate a basic
|
||||||
``python setup.py develop --user`` and then run ``./synctl start`` twice (once to
|
config file: ``./synctl start`` will give you instructions on how to do this.
|
||||||
generate a config; once to actually run) - you will find a webclient running at
|
For this purpose, you can use 'localhost' or your hostname as a server name.
|
||||||
http://localhost:8008. Please use a recent Chrome, Safari or Firefox for now...
|
Once you've done so, running ``./synctl start`` again will start your private
|
||||||
|
home sserver. You will find a webclient running at http://localhost:8008.
|
||||||
|
Please use a recent Chrome or Firefox for now (or Safari if you don't need
|
||||||
|
VoIP support).
|
||||||
|
|
||||||
- To run a **public** homeserver and let it exchange messages with other homeservers
|
- To run a **public** homeserver and let it exchange messages with other
|
||||||
and participate in the global Matrix federation, you must expose port 8448 to the
|
homeservers and participate in the global Matrix federation, you must expose
|
||||||
internet and edit homeserver.yaml to specify server_name (the public DNS entry for
|
port 8448 to the internet and edit homeserver.yaml to specify server_name
|
||||||
this server) and then run ``synctl start``. If you changed the server_name, you may
|
(the public DNS entry for this server) and then run ``synctl start``. If you
|
||||||
need to move the old database (homeserver.db) out of the way first. Then come join
|
changed the server_name, you may need to move the old database
|
||||||
``#matrix:matrix.org`` and say hi! :)
|
(homeserver.db) out of the way first. Then come join ``#matrix:matrix.org``
|
||||||
|
and say hi! :)
|
||||||
|
|
||||||
For more detailed setup instructions, please see further down this document.
|
For more detailed setup instructions, please see further down this document.
|
||||||
|
|
||||||
|
@ -80,8 +87,8 @@ which handle:
|
||||||
- Placing 1:1 VoIP and Video calls
|
- Placing 1:1 VoIP and Video calls
|
||||||
|
|
||||||
These APIs are intended to be implemented on a wide range of servers, services
|
These APIs are intended to be implemented on a wide range of servers, services
|
||||||
and clients, letting developers build messaging and VoIP functionality on top of
|
and clients, letting developers build messaging and VoIP functionality on top
|
||||||
the entirely open Matrix ecosystem rather than using closed or proprietary
|
of the entirely open Matrix ecosystem rather than using closed or proprietary
|
||||||
solutions. The hope is for Matrix to act as the building blocks for a new
|
solutions. The hope is for Matrix to act as the building blocks for a new
|
||||||
generation of fully open and interoperable messaging and VoIP apps for the
|
generation of fully open and interoperable messaging and VoIP apps for the
|
||||||
internet.
|
internet.
|
||||||
|
@ -96,17 +103,17 @@ In Matrix, every user runs one or more Matrix clients, which connect through to
|
||||||
a Matrix homeserver which stores all their personal chat history and user
|
a Matrix homeserver which stores all their personal chat history and user
|
||||||
account information - much as a mail client connects through to an IMAP/SMTP
|
account information - much as a mail client connects through to an IMAP/SMTP
|
||||||
server. Just like email, you can either run your own Matrix homeserver and
|
server. Just like email, you can either run your own Matrix homeserver and
|
||||||
control and own your own communications and history or use one hosted by someone
|
control and own your own communications and history or use one hosted by
|
||||||
else (e.g. matrix.org) - there is no single point of control or mandatory
|
someone else (e.g. matrix.org) - there is no single point of control or
|
||||||
service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
|
mandatory service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
|
||||||
|
|
||||||
Synapse ships with two basic demo Matrix clients: webclient (a basic group chat
|
Synapse ships with two basic demo Matrix clients: webclient (a basic group chat
|
||||||
web client demo implemented in AngularJS) and cmdclient (a basic Python
|
web client demo implemented in AngularJS) and cmdclient (a basic Python
|
||||||
command line utility which lets you easily see what the JSON APIs are up to).
|
command line utility which lets you easily see what the JSON APIs are up to).
|
||||||
|
|
||||||
We'd like to invite you to take a look at the Matrix spec, try to run a
|
We'd like to invite you to take a look at the Matrix spec, try to run a
|
||||||
homeserver, and join the existing Matrix chatrooms already out there, experiment
|
homeserver, and join the existing Matrix chatrooms already out there,
|
||||||
with the APIs and the demo clients, and let us know your thoughts at
|
experiment with the APIs and the demo clients, and let us know your thoughts at
|
||||||
https://github.com/matrix-org/synapse/issues or at matrix@matrix.org.
|
https://github.com/matrix-org/synapse/issues or at matrix@matrix.org.
|
||||||
|
|
||||||
Thanks for trying Matrix!
|
Thanks for trying Matrix!
|
||||||
|
@ -136,20 +143,20 @@ to install by making setup.py do so, in --user mode::
|
||||||
$ python setup.py develop --user
|
$ python setup.py develop --user
|
||||||
|
|
||||||
You'll need a version of setuptools new enough to know about git, so you
|
You'll need a version of setuptools new enough to know about git, so you
|
||||||
may need to also run:
|
may need to also run::
|
||||||
|
|
||||||
$ sudo apt-get install python-pip
|
$ sudo apt-get install python-pip
|
||||||
$ sudo pip install --upgrade setuptools
|
$ sudo pip install --upgrade setuptools
|
||||||
|
|
||||||
If you don't have access to github, then you may need to install ``syutil``
|
If you don't have access to github, then you may need to install ``syutil``
|
||||||
manually by checking it out and running ``python setup.py develop --user`` on it
|
manually by checking it out and running ``python setup.py develop --user`` on
|
||||||
too.
|
it too.
|
||||||
|
|
||||||
If you get errors about ``sodium.h`` being missing, you may also need to
|
If you get errors about ``sodium.h`` being missing, you may also need to
|
||||||
manually install a newer PyNaCl via pip as setuptools installs an old one. Or
|
manually install a newer PyNaCl via pip as setuptools installs an old one. Or
|
||||||
you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
|
you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
|
||||||
installing it. Installing PyNaCl using pip may also work (remember to remove any
|
installing it. Installing PyNaCl using pip may also work (remember to remove
|
||||||
other versions installed by setuputils in, for example, ~/.local/lib).
|
any other versions installed by setuputils in, for example, ~/.local/lib).
|
||||||
|
|
||||||
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
||||||
you will need to ``export CFLAGS=-Qunused-arguments``.
|
you will need to ``export CFLAGS=-Qunused-arguments``.
|
||||||
|
@ -185,9 +192,9 @@ be publicly visible on the internet, and they will need to know its host name.
|
||||||
You have two choices here, which will influence the form of your Matrix user
|
You have two choices here, which will influence the form of your Matrix user
|
||||||
IDs:
|
IDs:
|
||||||
|
|
||||||
1) Use the machine's own hostname as available on public DNS in the form of its
|
1) Use the machine's own hostname as available on public DNS in the form of
|
||||||
A or AAAA records. This is easier to set up initially, perhaps for testing,
|
its A or AAAA records. This is easier to set up initially, perhaps for
|
||||||
but lacks the flexibility of SRV.
|
testing, but lacks the flexibility of SRV.
|
||||||
|
|
||||||
2) Set up a SRV record for your domain name. This requires you create a SRV
|
2) Set up a SRV record for your domain name. This requires you create a SRV
|
||||||
record in DNS, but gives the flexibility to run the server on your own
|
record in DNS, but gives the flexibility to run the server on your own
|
||||||
|
@ -247,7 +254,7 @@ http://localhost:8080. Simply run::
|
||||||
Running The Demo Web Client
|
Running The Demo Web Client
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
The homeserver runs a web client by default at http://localhost:8080.
|
The homeserver runs a web client by default at https://localhost:8448/.
|
||||||
|
|
||||||
If this is the first time you have used the client from that browser (it uses
|
If this is the first time you have used the client from that browser (it uses
|
||||||
HTML5 local storage to remember its config), you will need to log in to your
|
HTML5 local storage to remember its config), you will need to log in to your
|
||||||
|
@ -267,8 +274,8 @@ account. Your name will take the form of::
|
||||||
|
|
||||||
Specify your desired localpart in the topmost box of the "Register for an
|
Specify your desired localpart in the topmost box of the "Register for an
|
||||||
account" form, and click the "Register" button. Hostnames can contain ports if
|
account" form, and click the "Register" button. Hostnames can contain ports if
|
||||||
required due to lack of SRV records (e.g. @matthew:localhost:8080 on an internal
|
required due to lack of SRV records (e.g. @matthew:localhost:8448 on an
|
||||||
synapse sandbox running on localhost)
|
internal synapse sandbox running on localhost)
|
||||||
|
|
||||||
|
|
||||||
Logging In To An Existing Account
|
Logging In To An Existing Account
|
||||||
|
@ -283,9 +290,9 @@ Identity Servers
|
||||||
|
|
||||||
The job of authenticating 3PIDs and tracking which 3PIDs are associated with a
|
The job of authenticating 3PIDs and tracking which 3PIDs are associated with a
|
||||||
given Matrix user is very security-sensitive, as there is obvious risk of spam
|
given Matrix user is very security-sensitive, as there is obvious risk of spam
|
||||||
if it is too easy to sign up for Matrix accounts or harvest 3PID data. Meanwhile
|
if it is too easy to sign up for Matrix accounts or harvest 3PID data.
|
||||||
the job of publishing the end-to-end encryption public keys for Matrix users is
|
Meanwhile the job of publishing the end-to-end encryption public keys for
|
||||||
also very security-sensitive for similar reasons.
|
Matrix users is also very security-sensitive for similar reasons.
|
||||||
|
|
||||||
Therefore the role of managing trusted identity in the Matrix ecosystem is
|
Therefore the role of managing trusted identity in the Matrix ecosystem is
|
||||||
farmed out to a cluster of known trusted ecosystem partners, who run 'Matrix
|
farmed out to a cluster of known trusted ecosystem partners, who run 'Matrix
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
Broad-sweeping stuff which would be nice to have
|
|
||||||
================================================
|
|
||||||
|
|
||||||
- Additional SQL backends beyond sqlite
|
|
||||||
- homeserver implementation in go
|
|
||||||
- homeserver implementation in node.js
|
|
||||||
- client SDKs
|
|
||||||
- libpurple library
|
|
||||||
- irssi plugin?
|
|
|
@ -31,7 +31,7 @@ Registration
|
||||||
The aim of registration is to get a user ID and access token which you will need
|
The aim of registration is to get a user ID and access token which you will need
|
||||||
when accessing other APIs::
|
when accessing other APIs::
|
||||||
|
|
||||||
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/register"
|
curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register"
|
||||||
|
|
||||||
{
|
{
|
||||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
||||||
|
@ -39,14 +39,15 @@ when accessing other APIs::
|
||||||
"user_id": "@example:localhost"
|
"user_id": "@example:localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
NB: If a ``user_id`` is not specified, one will be randomly generated for you.
|
NB: If a ``user`` is not specified, one will be randomly generated for you.
|
||||||
If you do not specify a ``password``, you will be unable to login to the account
|
If you do not specify a ``password``, you will be unable to login to the account
|
||||||
if you forget the ``access_token``.
|
if you forget the ``access_token``.
|
||||||
|
|
||||||
Implementation note: The matrix specification does not enforce how users
|
Implementation note: The matrix specification does not enforce how users
|
||||||
register with a server. It just specifies the URL path and absolute minimum
|
register with a server. It just specifies the URL path and absolute minimum
|
||||||
keys. The reference home server uses a username/password to authenticate user,
|
keys. The reference home server uses a username/password to authenticate user,
|
||||||
but other home servers may use different methods.
|
but other home servers may use different methods. This is why you need to
|
||||||
|
specify the ``type`` of method.
|
||||||
|
|
||||||
Login
|
Login
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -1,103 +1,3 @@
|
||||||
========
|
|
||||||
Presence
|
|
||||||
========
|
|
||||||
|
|
||||||
A description of presence information and visibility between users.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Each user has the concept of Presence information. This encodes a sense of the
|
|
||||||
"availability" of that user, suitable for display on other user's clients.
|
|
||||||
|
|
||||||
|
|
||||||
Presence Information
|
|
||||||
====================
|
|
||||||
|
|
||||||
The basic piece of presence information is an enumeration of a small set of
|
|
||||||
state; such as "free to chat", "online", "busy", or "offline". The default state
|
|
||||||
unless the user changes it is "online". Lower states suggest some amount of
|
|
||||||
decreased availability from normal, which might have some client-side effect
|
|
||||||
like muting notification sounds and suggests to other users not to bother them
|
|
||||||
unless it is urgent. Equally, the "free to chat" state exists to let the user
|
|
||||||
announce their general willingness to receive messages moreso than default.
|
|
||||||
|
|
||||||
Home servers should also allow a user to set their state as "hidden" - a state
|
|
||||||
which behaves as offline, but allows the user to see the client state anyway and
|
|
||||||
generally interact with client features such as reading message history or
|
|
||||||
accessing contacts in the address book.
|
|
||||||
|
|
||||||
This basic state field applies to the user as a whole, regardless of how many
|
|
||||||
client devices they have connected. The home server should synchronise this
|
|
||||||
status choice among multiple devices to ensure the user gets a consistent
|
|
||||||
experience.
|
|
||||||
|
|
||||||
Idle Time
|
|
||||||
---------
|
|
||||||
|
|
||||||
As well as the basic state field, the presence information can also show a sense
|
|
||||||
of an "idle timer". This should be maintained individually by the user's
|
|
||||||
clients, and the homeserver can take the highest reported time as that to
|
|
||||||
report. Likely this should be presented in fairly coarse granularity; possibly
|
|
||||||
being limited to letting the home server automatically switch from a "free to
|
|
||||||
chat" or "online" mode into "idle".
|
|
||||||
|
|
||||||
When a user is offline, the Home Server can still report when the user was last
|
|
||||||
seen online, again perhaps in a somewhat coarse manner.
|
|
||||||
|
|
||||||
Device Type
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Client devices that may limit the user experience somewhat (such as "mobile"
|
|
||||||
devices with limited ability to type on a real keyboard or read large amounts of
|
|
||||||
text) should report this to the home server, as this is also useful information
|
|
||||||
to report as "presence" if the user cannot be expected to provide a good typed
|
|
||||||
response to messages.
|
|
||||||
|
|
||||||
|
|
||||||
Presence List
|
|
||||||
=============
|
|
||||||
|
|
||||||
Each user's home server stores a "presence list" for that user. This stores a
|
|
||||||
list of other user IDs the user has chosen to add to it (remembering any ACL
|
|
||||||
Pointer if appropriate).
|
|
||||||
|
|
||||||
To be added to a contact list, the user being added must grant permission. Once
|
|
||||||
granted, both user's HS(es) store this information, as it allows the user who
|
|
||||||
has added the contact some more abilities; see below. Since such subscriptions
|
|
||||||
are likely to be bidirectional, HSes may wish to automatically accept requests
|
|
||||||
when a reverse subscription already exists.
|
|
||||||
|
|
||||||
As a convenience, presence lists should support the ability to collect users
|
|
||||||
into groups, which could allow things like inviting the entire group to a new
|
|
||||||
("ad-hoc") chat room, or easy interaction with the profile information ACL
|
|
||||||
implementation of the HS.
|
|
||||||
|
|
||||||
|
|
||||||
Presence and Permissions
|
|
||||||
========================
|
|
||||||
|
|
||||||
For a viewing user to be allowed to see the presence information of a target
|
|
||||||
user, either
|
|
||||||
|
|
||||||
* The target user has allowed the viewing user to add them to their presence
|
|
||||||
list, or
|
|
||||||
|
|
||||||
* The two users share at least one room in common
|
|
||||||
|
|
||||||
In the latter case, this allows for clients to display some minimal sense of
|
|
||||||
presence information in a user list for a room.
|
|
||||||
|
|
||||||
Home servers can also use the user's choice of presence state as a signal for
|
|
||||||
how to handle new private one-to-one chat message requests. For example, it
|
|
||||||
might decide:
|
|
||||||
|
|
||||||
"free to chat": accept anything
|
|
||||||
"online": accept from anyone in my addres book list
|
|
||||||
"busy": accept from anyone in this "important people" group in my address
|
|
||||||
book list
|
|
||||||
|
|
||||||
|
|
||||||
API Efficiency
|
API Efficiency
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,22 @@
|
||||||
"paramType": "body"
|
"paramType": "body"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "DELETE",
|
||||||
|
"summary": "Removes a mapping of room alias to room ID.",
|
||||||
|
"notes": "Only privileged users can perform this action.",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "remove_room_alias",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomAlias",
|
||||||
|
"description": "The room alias to remove.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
53
docs/definitions.rst
Normal file
53
docs/definitions.rst
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
Definitions
|
||||||
|
===========
|
||||||
|
|
||||||
|
# *Event* -- A JSON object that represents a piece of information to be
|
||||||
|
distributed to the the room. The object includes a payload and metadata,
|
||||||
|
including a `type` used to indicate what the payload is for and how to process
|
||||||
|
them. It also includes one or more references to previous events.
|
||||||
|
|
||||||
|
# *Event graph* -- Events and their references to previous events form a
|
||||||
|
directed acyclic graph. All events must be a descendant of the first event in a
|
||||||
|
room, except for a few special circumstances.
|
||||||
|
|
||||||
|
# *State event* -- A state event is an event that has a non-null string valued
|
||||||
|
`state_key` field. It may also include a `prev_state` key referencing exactly
|
||||||
|
one state event with the same type and state key, in the same event graph.
|
||||||
|
|
||||||
|
# *State tree* -- A state tree is a tree formed by a collection of state events
|
||||||
|
that have the same type and state key (all in the same event graph.
|
||||||
|
|
||||||
|
# *State resolution algorithm* -- An algorithm that takes a state tree as input
|
||||||
|
and selects a single leaf node.
|
||||||
|
|
||||||
|
# *Current state event* -- The leaf node of a given state tree that has been
|
||||||
|
selected by the state resolution algorithm.
|
||||||
|
|
||||||
|
# *Room state* / *state dictionary* / *current state* -- A mapping of the pair
|
||||||
|
(event type, state key) to the current state event for that pair.
|
||||||
|
|
||||||
|
# *Room* -- An event graph and its associated state dictionary. An event is in
|
||||||
|
the room if it is part of the event graph.
|
||||||
|
|
||||||
|
# *Topological ordering* -- The partial ordering that can be extracted from the
|
||||||
|
event graph due to it being a DAG.
|
||||||
|
|
||||||
|
(The state definitions are purposely slightly ill-defined, since if we allow
|
||||||
|
deleting events we might end up with multiple state trees for a given event
|
||||||
|
type and state key pair.)
|
||||||
|
|
||||||
|
Federation specific
|
||||||
|
-------------------
|
||||||
|
# *(Persistent data unit) PDU* -- An encoding of an event for distribution of
|
||||||
|
the server to server protocol.
|
||||||
|
|
||||||
|
# *(Ephemeral data unit) EDU* -- A piece of information that is sent between
|
||||||
|
servers and doesn't encode an event.
|
||||||
|
|
||||||
|
Client specific
|
||||||
|
---------------
|
||||||
|
# *Child events* -- Events that reference a single event in the same room
|
||||||
|
independently of the event graph.
|
||||||
|
|
||||||
|
# *Collapsed events* -- Events that have all child events that reference it
|
||||||
|
included in the JSON object.
|
30
docs/specification-NOTHAVE.rst
Normal file
30
docs/specification-NOTHAVE.rst
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Matrix Specification NOTHAVEs
|
||||||
|
=============================
|
||||||
|
|
||||||
|
This document contains sections of the main specification that have been
|
||||||
|
temporarily removed, because they specify intentions or aspirations that have
|
||||||
|
in no way yet been implemented. Rather than outright-deleting them, they have
|
||||||
|
been moved here so as to stand as an initial version for such time as they
|
||||||
|
become extant.
|
||||||
|
|
||||||
|
|
||||||
|
Presence
|
||||||
|
========
|
||||||
|
|
||||||
|
Idle Time
|
||||||
|
---------
|
||||||
|
As well as the basic ``presence`` field, the presence information can also show
|
||||||
|
a sense of an "idle timer". This should be maintained individually by the
|
||||||
|
user's clients, and the home server can take the highest reported time as that
|
||||||
|
to report. When a user is offline, the home server can still report when the
|
||||||
|
user was last seen online.
|
||||||
|
|
||||||
|
Device Type
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Client devices that may limit the user experience somewhat (such as "mobile"
|
||||||
|
devices with limited ability to type on a real keyboard or read large amounts of
|
||||||
|
text) should report this to the home server, as this is also useful information
|
||||||
|
to report as "presence" if the user cannot be expected to provide a good typed
|
||||||
|
response to messages.
|
||||||
|
|
|
@ -118,7 +118,7 @@ the account and looks like::
|
||||||
The ``localpart`` of a user ID may be a user name, or an opaque ID identifying
|
The ``localpart`` of a user ID may be a user name, or an opaque ID identifying
|
||||||
this user. They are case-insensitive.
|
this user. They are case-insensitive.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
- Need to specify precise grammar for Matrix IDs
|
- Need to specify precise grammar for Matrix IDs
|
||||||
|
|
||||||
A "Home Server" is a server which provides C-S APIs and has the ability to
|
A "Home Server" is a server which provides C-S APIs and has the ability to
|
||||||
|
@ -167,7 +167,7 @@ The following diagram shows an ``m.room.message`` event being sent in the room
|
||||||
| matrix.org |<-------Federation------->| domain.com |
|
| matrix.org |<-------Federation------->| domain.com |
|
||||||
+------------------+ +------------------+
|
+------------------+ +------------------+
|
||||||
| ................................. |
|
| ................................. |
|
||||||
|______| Partially Shared State |_______|
|
|______| Shared State |_______|
|
||||||
| Room ID: !qporfwt:matrix.org |
|
| Room ID: !qporfwt:matrix.org |
|
||||||
| Servers: matrix.org, domain.com |
|
| Servers: matrix.org, domain.com |
|
||||||
| Members: |
|
| Members: |
|
||||||
|
@ -177,11 +177,10 @@ The following diagram shows an ``m.room.message`` event being sent in the room
|
||||||
|
|
||||||
Federation maintains shared state between multiple home servers, such that when
|
Federation maintains shared state between multiple home servers, such that when
|
||||||
an event is sent to a room, the home server knows where to forward the event on
|
an event is sent to a room, the home server knows where to forward the event on
|
||||||
to, and how to process the event. Home servers do not need to have completely
|
to, and how to process the event. State is scoped to a single room, and
|
||||||
shared state in order to participate in a room. State is scoped to a single
|
federation ensures that all home servers have the information they need, even
|
||||||
room, and federation ensures that all home servers have the information they
|
if that means the home server has to request more information from another home
|
||||||
need, even if that means the home server has to request more information from
|
server before processing the event.
|
||||||
another home server before processing the event.
|
|
||||||
|
|
||||||
Room Aliases
|
Room Aliases
|
||||||
------------
|
------------
|
||||||
|
@ -191,7 +190,7 @@ Each room can also have multiple "Room Aliases", which looks like::
|
||||||
#room_alias:domain
|
#room_alias:domain
|
||||||
|
|
||||||
.. TODO
|
.. TODO
|
||||||
- Need to specify precise grammar for Room IDs
|
- Need to specify precise grammar for Room Aliases
|
||||||
|
|
||||||
A room alias "points" to a room ID and is the human-readable label by which
|
A room alias "points" to a room ID and is the human-readable label by which
|
||||||
rooms are publicised and discovered. The room ID the alias is pointing to can
|
rooms are publicised and discovered. The room ID the alias is pointing to can
|
||||||
|
@ -201,6 +200,9 @@ over time to point to a different room ID. For this reason, Clients SHOULD
|
||||||
resolve the room alias to a room ID once and then use that ID on subsequent
|
resolve the room alias to a room ID once and then use that ID on subsequent
|
||||||
requests.
|
requests.
|
||||||
|
|
||||||
|
When resolving a room alias the server will also respond with a list of servers
|
||||||
|
that are in the room that can be used to join via.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
GET
|
GET
|
||||||
|
@ -215,10 +217,6 @@ requests.
|
||||||
| #bike >> !4rguxf:matrix.org |
|
| #bike >> !4rguxf:matrix.org |
|
||||||
|________________________________|
|
|________________________________|
|
||||||
|
|
||||||
.. TODO kegan
|
|
||||||
- show the actual API rather than pseudo-API?
|
|
||||||
|
|
||||||
|
|
||||||
Identity
|
Identity
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -239,8 +237,8 @@ authentication of the 3PID. Identity servers are also used to preserve the
|
||||||
mapping indefinitely, by replicating the mappings across multiple ISes.
|
mapping indefinitely, by replicating the mappings across multiple ISes.
|
||||||
|
|
||||||
Usage of an IS is not required in order for a client application to be part of
|
Usage of an IS is not required in order for a client application to be part of
|
||||||
the Matrix ecosystem. However, by not using an IS, discovery of users is
|
the Matrix ecosystem. However, without one clients will not be able to look up
|
||||||
greatly impacted.
|
user IDs using 3PIDs.
|
||||||
|
|
||||||
API Standards
|
API Standards
|
||||||
-------------
|
-------------
|
||||||
|
@ -366,7 +364,7 @@ events which are visible to the client will appear in the event stream. When
|
||||||
the request returns, an ``end`` token is included in the response. This token
|
the request returns, an ``end`` token is included in the response. This token
|
||||||
can be used in the next request to continue where the client left off.
|
can be used in the next request to continue where the client left off.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
How do we filter the event stream?
|
How do we filter the event stream?
|
||||||
Do we ever return multiple events in a single request? Don't we get lots of request
|
Do we ever return multiple events in a single request? Don't we get lots of request
|
||||||
setup RTT latency if we only do one event per request? Do we ever support streaming
|
setup RTT latency if we only do one event per request? Do we ever support streaming
|
||||||
|
@ -703,9 +701,6 @@ Rooms
|
||||||
|
|
||||||
Creation
|
Creation
|
||||||
--------
|
--------
|
||||||
.. TODO kegan
|
|
||||||
- TODO: Key for invite these users?
|
|
||||||
|
|
||||||
To create a room, a client has to use the |createRoom|_ API. There are various
|
To create a room, a client has to use the |createRoom|_ API. There are various
|
||||||
options which can be set when creating a room:
|
options which can be set when creating a room:
|
||||||
|
|
||||||
|
@ -719,7 +714,7 @@ options which can be set when creating a room:
|
||||||
Description:
|
Description:
|
||||||
A ``public`` visibility indicates that the room will be shown in the public
|
A ``public`` visibility indicates that the room will be shown in the public
|
||||||
room list. A ``private`` visibility will hide the room from the public room
|
room list. A ``private`` visibility will hide the room from the public room
|
||||||
list. Rooms default to ``public`` visibility if this key is not included.
|
list. Rooms default to ``private`` visibility if this key is not included.
|
||||||
|
|
||||||
``room_alias_name``
|
``room_alias_name``
|
||||||
Type:
|
Type:
|
||||||
|
@ -790,63 +785,98 @@ includes:
|
||||||
- ``m.room.send_event_level`` : The power level required in order to send a
|
- ``m.room.send_event_level`` : The power level required in order to send a
|
||||||
message in this room.
|
message in this room.
|
||||||
- ``m.room.ops_level`` : The power level required in order to kick or ban a
|
- ``m.room.ops_level`` : The power level required in order to kick or ban a
|
||||||
user from the room.
|
user from the room or redact an event in the room.
|
||||||
|
|
||||||
See `Room Events`_ for more information on these events.
|
See `Room Events`_ for more information on these events.
|
||||||
|
|
||||||
Modifying aliases
|
Room aliases
|
||||||
-----------------
|
------------
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO kegan
|
Room aliases can be created by sending a ``PUT /directory/room/<room alias>``::
|
||||||
- path to edit aliases
|
|
||||||
- PUT /directory/room/<room alias> { room_id : foo }
|
{
|
||||||
- GET /directory/room/<room alias> { room_id : foo, servers: [a.com, b.com] }
|
"room_id": <room id>
|
||||||
- format when retrieving list of aliases. NOT complete list.
|
}
|
||||||
- format for adding/removing aliases.
|
|
||||||
|
They can be deleted by sending a ``DELETE /directory/room/<room alias>`` with
|
||||||
|
no content. Only some privileged users may be able to delete room aliases, e.g.
|
||||||
|
server admins, the creator of the room alias, etc. This specification does not
|
||||||
|
outline the privilege level required for deleting room aliases.
|
||||||
|
|
||||||
|
As room aliases are scoped to a particular home server domain name, it is
|
||||||
|
likely that a home server will reject attempts to maintain aliases on other
|
||||||
|
domain names. This specification does not provide a way for home servers to
|
||||||
|
send update requests to other servers.
|
||||||
|
|
||||||
|
Rooms store a *partial* list of room aliases via the ``m.room.aliases`` state
|
||||||
|
event. This alias list is partial because it cannot guarantee that the alias
|
||||||
|
list is in any way accurate or up-to-date, as room aliases can point to
|
||||||
|
different room IDs over time. Crucially, the aliases in this event are
|
||||||
|
**purely informational** and SHOULD NOT be treated as accurate. They SHOULD
|
||||||
|
be checked before they are used or shared with another user. If a room
|
||||||
|
appears to have a room alias of ``#alias:example.com``, this SHOULD be checked
|
||||||
|
to make sure that the room's ID matches the ``room_id`` returned from the
|
||||||
|
request.
|
||||||
|
|
||||||
|
Room aliases can be checked in the same way they are resolved; by sending a
|
||||||
|
``GET /directory/room/<room alias>``::
|
||||||
|
|
||||||
|
{
|
||||||
|
"room_id": <room id>,
|
||||||
|
"servers": [ <domain>, <domain2>, <domain3> ]
|
||||||
|
}
|
||||||
|
|
||||||
|
Home servers can respond to resolve requests for aliases on other domains than
|
||||||
|
their own by using the federation API to ask other domain name home servers.
|
||||||
|
|
||||||
|
|
||||||
Permissions
|
Permissions
|
||||||
-----------
|
-----------
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO kegan
|
|
||||||
- TODO: What is a power level? How do they work? Defaults / required levels for X. How do they change
|
|
||||||
as people join and leave rooms? What do you do if you get a clash? Examples.
|
|
||||||
- TODO: List all actions which use power levels (sending msgs, inviting users, banning people, etc...)
|
|
||||||
- TODO: Room config - what is the event and what are the keys/values and explanations for them.
|
|
||||||
Link through to respective sections where necessary. How does this tie in with permissions, e.g.
|
|
||||||
give example of creating a read-only room.
|
|
||||||
|
|
||||||
Permissions for rooms are done via the concept of power levels - to do any
|
Permissions for rooms are done via the concept of power levels - to do any
|
||||||
action in a room a user must have a suitable power level.
|
action in a room a user must have a suitable power level. Power levels are
|
||||||
|
stored as state events in a given room.
|
||||||
|
|
||||||
Power levels for users are defined in ``m.room.power_levels``, where both a
|
Power levels for users are defined in ``m.room.power_levels``, where both a
|
||||||
default and specific users' power levels can be set. By default all users have
|
default and specific users' power levels can be set::
|
||||||
a power level of 0, other than the room creator whose power level defaults to
|
|
||||||
100. Power levels for users are tracked per-room even if the user is not
|
{
|
||||||
present in the room.
|
"<user id 1>": <power level int>,
|
||||||
|
"<user id 2>": <power level int>,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
By default all users have a power level of 0, other than the room creator whose
|
||||||
|
power level defaults to 100. Users can grant other users increased power levels
|
||||||
|
up to their own power level. For example, user A with a power level of 50 could
|
||||||
|
increase the power level of user B to a maximum of level 50. Power levels for
|
||||||
|
users are tracked per-room even if the user is not present in the room.
|
||||||
|
|
||||||
State events may contain a ``required_power_level`` key, which indicates the
|
State events may contain a ``required_power_level`` key, which indicates the
|
||||||
minimum power a user must have before they can update that state key. The only
|
minimum power a user must have before they can update that state key. The only
|
||||||
exception to this is when a user leaves a room.
|
exception to this is when a user leaves a room, which revokes the user's right
|
||||||
|
to update state events in that room.
|
||||||
|
|
||||||
To perform certain actions there are additional power level requirements
|
To perform certain actions there are additional power level requirements
|
||||||
defined in the following state events:
|
defined in the following state events:
|
||||||
|
|
||||||
- ``m.room.send_event_level`` defines the minimum level for sending non-state
|
- ``m.room.send_event_level`` defines the minimum ``level`` for sending
|
||||||
events. Defaults to 50.
|
non-state events. Defaults to 50.
|
||||||
- ``m.room.add_state_level`` defines the minimum level for adding new state,
|
- ``m.room.add_state_level`` defines the minimum ``level`` for adding new
|
||||||
rather than updating existing state. Defaults to 50.
|
state, rather than updating existing state. Defaults to 50.
|
||||||
- ``m.room.ops_level`` defines the minimum levels to ban and kick other users.
|
- ``m.room.ops_level`` defines the minimum ``ban_level`` and ``kick_level`` to
|
||||||
This defaults to a kick and ban levels of 50 each.
|
ban and kick other users respectively. This defaults to a kick and ban levels
|
||||||
|
of 50 each.
|
||||||
|
|
||||||
|
|
||||||
Joining rooms
|
Joining rooms
|
||||||
-------------
|
-------------
|
||||||
.. TODO kegan
|
.. TODO-doc What does the home server have to do to join a user to a room?
|
||||||
- TODO: What does the home server have to do to join a user to a room?
|
- See SPEC-30.
|
||||||
|
|
||||||
Users need to join a room in order to send and receive events in that room. A
|
Users need to join a room in order to send and receive events in that room. A
|
||||||
user can join a room by making a request to |/join/<room_alias_or_id>|_ with::
|
user can join a room by making a request to |/join/<room_alias_or_id>|_ with::
|
||||||
|
@ -883,20 +913,21 @@ received an invite.
|
||||||
|
|
||||||
Inviting users
|
Inviting users
|
||||||
--------------
|
--------------
|
||||||
.. TODO kegan
|
.. TODO-doc Invite-join dance
|
||||||
- Can invite users to a room if the room config key TODO is set to TODO. Must have required power level.
|
|
||||||
- Outline invite join dance. What is it? Why is it required? How does it work?
|
- Outline invite join dance. What is it? Why is it required? How does it work?
|
||||||
- What does the home server have to do?
|
- What does the home server have to do?
|
||||||
- TODO: In what circumstances will direct member editing NOT be equivalent to ``/invite``?
|
|
||||||
|
|
||||||
The purpose of inviting users to a room is to notify them that the room exists
|
The purpose of inviting users to a room is to notify them that the room exists
|
||||||
so they can choose to become a member of that room. Some rooms require that all
|
so they can choose to become a member of that room. Some rooms require that all
|
||||||
users who join a room are previously invited to it (an "invite-only" room).
|
users who join a room are previously invited to it (an "invite-only" room).
|
||||||
Whether a given room is an "invite-only" room is determined by the room config
|
Whether a given room is an "invite-only" room is determined by the room config
|
||||||
key ``TODO``. It can have one of the following values:
|
key ``m.room.join_rules``. It can have one of the following values:
|
||||||
|
|
||||||
- TODO Room config invite only value explanation
|
``public``
|
||||||
- TODO Room config free-to-join value explanation
|
This room is free for anyone to join without an invite.
|
||||||
|
|
||||||
|
``invite``
|
||||||
|
This room can only be joined if you were invited.
|
||||||
|
|
||||||
Only users who have a membership state of ``join`` in a room can invite new
|
Only users who have a membership state of ``join`` in a room can invite new
|
||||||
users to said room. The person being invited must not be in the ``join`` state
|
users to said room. The person being invited must not be in the ``join`` state
|
||||||
|
@ -921,9 +952,14 @@ See the `Room events`_ section for more information on ``m.room.member``.
|
||||||
|
|
||||||
Leaving rooms
|
Leaving rooms
|
||||||
-------------
|
-------------
|
||||||
.. TODO kegan
|
.. TODO-spec - HS deleting rooms they are no longer a part of. Not implemented.
|
||||||
- TODO: Grace period before deletion?
|
- This is actually Very Tricky. If all clients a HS is serving leave a room,
|
||||||
- TODO: Under what conditions should a room NOT be purged?
|
the HS will no longer get any new events for that room, because the servers
|
||||||
|
who get the events are determined on the *membership list*. There should
|
||||||
|
probably be a way for a HS to lurk on a room even if there are 0 of their
|
||||||
|
members in the room.
|
||||||
|
- Grace period before deletion?
|
||||||
|
- Under what conditions should a room NOT be purged?
|
||||||
|
|
||||||
|
|
||||||
A user can leave a room to stop receiving events for that room. A user must
|
A user can leave a room to stop receiving events for that room. A user must
|
||||||
|
@ -945,11 +981,7 @@ directly by sending the following request to
|
||||||
See the `Room events`_ section for more information on ``m.room.member``.
|
See the `Room events`_ section for more information on ``m.room.member``.
|
||||||
|
|
||||||
Once a user has left a room, that room will no longer appear on the
|
Once a user has left a room, that room will no longer appear on the
|
||||||
|initialSync|_ API. Be aware that leaving a room is not equivalent to have
|
|initialSync|_ API.
|
||||||
never been in that room. A user who has previously left a room still maintains
|
|
||||||
some residual state in that room. Their membership state will be marked as
|
|
||||||
``leave``. This contrasts with a user who has *never been invited or joined to
|
|
||||||
that room* who will not have any membership state for that room.
|
|
||||||
|
|
||||||
If all members in a room leave, that room becomes eligible for deletion.
|
If all members in a room leave, that room becomes eligible for deletion.
|
||||||
|
|
||||||
|
@ -1068,17 +1100,59 @@ When a client logs in, they may have a list of rooms which they have already
|
||||||
joined. These rooms may also have a list of events associated with them. The
|
joined. These rooms may also have a list of events associated with them. The
|
||||||
purpose of 'syncing' is to present the current room and event information in a
|
purpose of 'syncing' is to present the current room and event information in a
|
||||||
convenient, compact manner. The events returned are not limited to room events;
|
convenient, compact manner. The events returned are not limited to room events;
|
||||||
presence events will also be returned. There are two APIs provided:
|
presence events will also be returned. A single syncing API is provided:
|
||||||
|
|
||||||
- |initialSync|_ : A global sync which will present room and event information
|
- |initialSync|_ : A global sync which will present room and event information
|
||||||
for all rooms the user has joined.
|
for all rooms the user has joined.
|
||||||
|
|
||||||
|
.. TODO-spec room-scoped initial sync
|
||||||
- |/rooms/<room_id>/initialSync|_ : A sync scoped to a single room. Presents
|
- |/rooms/<room_id>/initialSync|_ : A sync scoped to a single room. Presents
|
||||||
room and event information for this room only.
|
room and event information for this room only.
|
||||||
|
- Room-scoped initial sync is Very Tricky because typically people would
|
||||||
|
want to sync the room then listen for any new content from that point
|
||||||
|
onwards. The event stream cannot do this for a single room currently.
|
||||||
|
As a result, commenting room-scoped initial sync at this time.
|
||||||
|
|
||||||
.. TODO kegan
|
The |initialSync|_ API contains the following keys:
|
||||||
- TODO: JSON response format for both types
|
|
||||||
- TODO: when would you use global? when would you use scoped?
|
``presence``
|
||||||
|
Description:
|
||||||
|
Contains a list of presence information for users the client is interested
|
||||||
|
in.
|
||||||
|
Format:
|
||||||
|
A JSON array of ``m.presence`` events.
|
||||||
|
|
||||||
|
``end``
|
||||||
|
Description:
|
||||||
|
Contains an event stream token which can be used with the `Event Stream`_.
|
||||||
|
Format:
|
||||||
|
A string containing the event stream token.
|
||||||
|
|
||||||
|
``rooms``
|
||||||
|
Description:
|
||||||
|
Contains a list of room information for all rooms the client has joined,
|
||||||
|
and limited room information on rooms the client has been invited to.
|
||||||
|
Format:
|
||||||
|
A JSON array containing Room Information JSON objects.
|
||||||
|
|
||||||
|
Room Information:
|
||||||
|
Description:
|
||||||
|
Contains all state events for the room, along with a limited amount of
|
||||||
|
the most recent non-state events, configured via the ``limit`` query
|
||||||
|
parameter. Also contains additional keys with room metadata, such as the
|
||||||
|
``room_id`` and the client's ``membership`` to the room.
|
||||||
|
Format:
|
||||||
|
A JSON object with the following keys:
|
||||||
|
``room_id``
|
||||||
|
A string containing the ID of the room being described.
|
||||||
|
``membership``
|
||||||
|
A string representing the client's membership status in this room.
|
||||||
|
``messages``
|
||||||
|
An event stream JSON object containing a ``chunk`` of recent non-state
|
||||||
|
events, along with an ``end`` token. *NB: The name of this key will be
|
||||||
|
changed in a later version.*
|
||||||
|
``state``
|
||||||
|
A JSON array containing all the current state events for this room.
|
||||||
|
|
||||||
Getting events for a room
|
Getting events for a room
|
||||||
-------------------------
|
-------------------------
|
||||||
|
@ -1098,7 +1172,7 @@ There are several APIs provided to ``GET`` events for a room:
|
||||||
Response format:
|
Response format:
|
||||||
``[ { state event }, { state event }, ... ]``
|
``[ { state event }, { state event }, ... ]``
|
||||||
Example:
|
Example:
|
||||||
TODO
|
TODO-doc
|
||||||
|
|
||||||
|
|
||||||
|/rooms/<room_id>/members|_
|
|/rooms/<room_id>/members|_
|
||||||
|
@ -1107,7 +1181,7 @@ There are several APIs provided to ``GET`` events for a room:
|
||||||
Response format:
|
Response format:
|
||||||
``{ "start": "<token>", "end": "<token>", "chunk": [ { m.room.member event }, ... ] }``
|
``{ "start": "<token>", "end": "<token>", "chunk": [ { m.room.member event }, ... ] }``
|
||||||
Example:
|
Example:
|
||||||
TODO
|
TODO-doc
|
||||||
|
|
||||||
|/rooms/<room_id>/messages|_
|
|/rooms/<room_id>/messages|_
|
||||||
Description:
|
Description:
|
||||||
|
@ -1117,16 +1191,16 @@ There are several APIs provided to ``GET`` events for a room:
|
||||||
Response format:
|
Response format:
|
||||||
``{ "start": "<token>", "end": "<token>" }``
|
``{ "start": "<token>", "end": "<token>" }``
|
||||||
Example:
|
Example:
|
||||||
TODO
|
TODO-doc
|
||||||
|
|
||||||
|/rooms/<room_id>/initialSync|_
|
|/rooms/<room_id>/initialSync|_
|
||||||
Description:
|
Description:
|
||||||
Get all relevant events for a room. This includes state events, paginated
|
Get all relevant events for a room. This includes state events, paginated
|
||||||
non-state events and presence events.
|
non-state events and presence events.
|
||||||
Response format:
|
Response format:
|
||||||
`` { TODO } ``
|
`` { TODO-doc } ``
|
||||||
Example:
|
Example:
|
||||||
TODO
|
TODO-doc
|
||||||
|
|
||||||
Redactions
|
Redactions
|
||||||
----------
|
----------
|
||||||
|
@ -1143,19 +1217,46 @@ is the event that caused it to be redacted, which may include a reason.
|
||||||
Redacting an event cannot be undone, allowing server owners to delete the
|
Redacting an event cannot be undone, allowing server owners to delete the
|
||||||
offending content from the databases.
|
offending content from the databases.
|
||||||
|
|
||||||
Currently, only room admins can redact events by sending a ``m.room.redacted``
|
Currently, only room admins can redact events by sending a ``m.room.redaction``
|
||||||
event, but server admins also need to be able to redact events by a similar
|
event, but server admins also need to be able to redact events by a similar
|
||||||
mechanism.
|
mechanism.
|
||||||
|
|
||||||
|
Upon receipt of a redaction event, the server should strip off any keys not in
|
||||||
|
the following list:
|
||||||
|
|
||||||
|
- ``event_id``
|
||||||
|
- ``type``
|
||||||
|
- ``room_id``
|
||||||
|
- ``user_id``
|
||||||
|
- ``state_key``
|
||||||
|
- ``prev_state``
|
||||||
|
- ``content``
|
||||||
|
|
||||||
|
The content object should also be stripped of all keys, unless it is one of
|
||||||
|
one of the following event types:
|
||||||
|
|
||||||
|
- ``m.room.member`` allows key ``membership``
|
||||||
|
- ``m.room.create`` allows key ``creator``
|
||||||
|
- ``m.room.join_rules`` allows key ``join_rule``
|
||||||
|
- ``m.room.power_levels`` allows keys that are user ids or ``default``
|
||||||
|
- ``m.room.add_state_level`` allows key ``level``
|
||||||
|
- ``m.room.send_event_level`` allows key ``level``
|
||||||
|
- ``m.room.ops_levels`` allows keys ``kick_level``, ``ban_level``
|
||||||
|
and ``redact_level``
|
||||||
|
- ``m.room.aliases`` allows key ``aliases``
|
||||||
|
|
||||||
|
The redaction event should be added under the key ``redacted_because``.
|
||||||
|
|
||||||
|
|
||||||
|
When a client receives a redaction event it should change the redacted event
|
||||||
|
in the same way a server does.
|
||||||
|
|
||||||
|
|
||||||
Room Events
|
Room Events
|
||||||
===========
|
===========
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO dave?
|
|
||||||
- voip events?
|
|
||||||
|
|
||||||
This specification outlines several standard event types, all of which are
|
This specification outlines several standard event types, all of which are
|
||||||
prefixed with ``m.``
|
prefixed with ``m.``
|
||||||
|
|
||||||
|
@ -1232,7 +1333,7 @@ prefixed with ``m.``
|
||||||
Example:
|
Example:
|
||||||
``{ "join_rule": "public" }``
|
``{ "join_rule": "public" }``
|
||||||
Description:
|
Description:
|
||||||
TODO : Use docs/models/rooms.rst
|
TODO-doc : Use docs/models/rooms.rst
|
||||||
|
|
||||||
``m.room.power_levels``
|
``m.room.power_levels``
|
||||||
Summary:
|
Summary:
|
||||||
|
@ -1284,7 +1385,7 @@ prefixed with ``m.``
|
||||||
Type:
|
Type:
|
||||||
State event
|
State event
|
||||||
JSON format:
|
JSON format:
|
||||||
``{ "ban_level": <int>, "kick_level": <int> }``
|
``{ "ban_level": <int>, "kick_level": <int>, "redact_level": <int> }``
|
||||||
Example:
|
Example:
|
||||||
``{ "ban_level": 5, "kick_level": 5 }``
|
``{ "ban_level": 5, "kick_level": 5 }``
|
||||||
Description:
|
Description:
|
||||||
|
@ -1303,10 +1404,32 @@ prefixed with ``m.``
|
||||||
Example:
|
Example:
|
||||||
``{ "aliases": ["#foo:example.com"] }``
|
``{ "aliases": ["#foo:example.com"] }``
|
||||||
Description:
|
Description:
|
||||||
A server `may` inform the room that it has added or removed an alias for
|
This event is sent by a homeserver directly to inform of changes to the
|
||||||
the room. This is purely for informational purposes and may become stale.
|
list of aliases it knows about for that room. As a special-case, the
|
||||||
Clients `should` check that the room alias is still valid before using it.
|
``state_key`` of the event is the homeserver which owns the room alias.
|
||||||
The ``state_key`` of the event is the homeserver which owns the room alias.
|
For example, an event might look like::
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "m.room.aliases",
|
||||||
|
"event_id": "012345678ab",
|
||||||
|
"room_id": "!xAbCdEfG:example.com",
|
||||||
|
"state_key": "example.com",
|
||||||
|
"content": {
|
||||||
|
"aliases": ["#foo:example.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The event contains the full list of aliases now stored by the home server
|
||||||
|
that emitted it; additions or deletions are not explicitly mentioned as
|
||||||
|
being such. The entire set of known aliases for the room is then the union
|
||||||
|
of the individual lists declared by all such keys, one from each home
|
||||||
|
server holding at least one alias.
|
||||||
|
|
||||||
|
Clients `should` check the validity of any room alias given in this list
|
||||||
|
before presenting it to the user as trusted fact. The lists given by this
|
||||||
|
event should be considered simply as advice on which aliases might exist,
|
||||||
|
for which the client can perform the lookup to confirm whether it receives
|
||||||
|
the correct room ID.
|
||||||
|
|
||||||
``m.room.message``
|
``m.room.message``
|
||||||
Summary:
|
Summary:
|
||||||
|
@ -1361,6 +1484,10 @@ prefixed with ``m.``
|
||||||
|
|
||||||
m.room.message msgtypes
|
m.room.message msgtypes
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
.. TODO-spec
|
||||||
|
How a client should handle unknown message types.
|
||||||
|
|
||||||
Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type
|
Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type
|
||||||
of message being sent. Each type has their own required and optional keys, as
|
of message being sent. Each type has their own required and optional keys, as
|
||||||
outlined below:
|
outlined below:
|
||||||
|
@ -1480,8 +1607,9 @@ the following:
|
||||||
- ``offline`` : The user is not connected to an event stream.
|
- ``offline`` : The user is not connected to an event stream.
|
||||||
- ``free_for_chat`` : The user is generally willing to receive messages
|
- ``free_for_chat`` : The user is generally willing to receive messages
|
||||||
moreso than default.
|
moreso than default.
|
||||||
- ``hidden`` : TODO. Behaves as offline, but allows the user to see the
|
- ``hidden`` : Behaves as offline, but allows the user to see the client
|
||||||
client state anyway and generally interact with client features.
|
state anyway and generally interact with client features. (Not yet
|
||||||
|
implemented in synapse).
|
||||||
|
|
||||||
This basic ``presence`` field applies to the user as a whole, regardless of how
|
This basic ``presence`` field applies to the user as a whole, regardless of how
|
||||||
many client devices they have connected. The home server should synchronise
|
many client devices they have connected. The home server should synchronise
|
||||||
|
@ -1496,22 +1624,14 @@ in the other direction will not). This timestamp is presented via a key called
|
||||||
``last_active_ago``, which gives the relative number of miliseconds since the
|
``last_active_ago``, which gives the relative number of miliseconds since the
|
||||||
message is generated/emitted, that the user was last seen active.
|
message is generated/emitted, that the user was last seen active.
|
||||||
|
|
||||||
Idle Time
|
Home servers can also use the user's choice of presence state as a signal for
|
||||||
---------
|
how to handle new private one-to-one chat message requests. For example, it
|
||||||
As well as the basic ``presence`` field, the presence information can also show
|
might decide:
|
||||||
a sense of an "idle timer". This should be maintained individually by the
|
|
||||||
user's clients, and the home server can take the highest reported time as that
|
|
||||||
to report. When a user is offline, the home server can still report when the
|
|
||||||
user was last seen online.
|
|
||||||
|
|
||||||
Transmission
|
- ``free_for_chat`` : accept anything
|
||||||
------------
|
- ``online`` : accept from anyone in my addres book list
|
||||||
.. NOTE::
|
- ``busy`` : accept from anyone in this "important people" group in my
|
||||||
This section is a work in progress.
|
address book list
|
||||||
|
|
||||||
.. TODO:
|
|
||||||
- Transmitted as an EDU.
|
|
||||||
- Presence lists determine who to send to.
|
|
||||||
|
|
||||||
Presence List
|
Presence List
|
||||||
-------------
|
-------------
|
||||||
|
@ -1522,6 +1642,11 @@ granted, both user's HS(es) store this information. Since such subscriptions
|
||||||
are likely to be bidirectional, HSes may wish to automatically accept requests
|
are likely to be bidirectional, HSes may wish to automatically accept requests
|
||||||
when a reverse subscription already exists.
|
when a reverse subscription already exists.
|
||||||
|
|
||||||
|
As a convenience, presence lists should support the ability to collect users
|
||||||
|
into groups, which could allow things like inviting the entire group to a new
|
||||||
|
("ad-hoc") chat room, or easy interaction with the profile information ACL
|
||||||
|
implementation of the HS.
|
||||||
|
|
||||||
Presence and Permissions
|
Presence and Permissions
|
||||||
------------------------
|
------------------------
|
||||||
For a viewing user to be allowed to see the presence information of a target
|
For a viewing user to be allowed to see the presence information of a target
|
||||||
|
@ -1534,16 +1659,114 @@ user, either:
|
||||||
In the latter case, this allows for clients to display some minimal sense of
|
In the latter case, this allows for clients to display some minimal sense of
|
||||||
presence information in a user list for a room.
|
presence information in a user list for a room.
|
||||||
|
|
||||||
Typing notifications
|
Client API
|
||||||
====================
|
----------
|
||||||
.. NOTE::
|
The client API for presence is on the following set of REST calls.
|
||||||
This section is a work in progress.
|
|
||||||
|
Fetching basic status::
|
||||||
|
|
||||||
|
GET $PREFIX/presence/:user_id/status
|
||||||
|
|
||||||
|
Returned content: JSON object containing the following keys:
|
||||||
|
presence: "offline"|"unavailable"|"online"|"free_for_chat"
|
||||||
|
status_msg: (optional) string of freeform text
|
||||||
|
last_active_ago: miliseconds since the last activity by the user
|
||||||
|
|
||||||
|
Setting basic status::
|
||||||
|
|
||||||
|
PUT $PREFIX/presence/:user_id/status
|
||||||
|
|
||||||
|
Content: JSON object containing the following keys:
|
||||||
|
presence and status_msg: as above
|
||||||
|
|
||||||
|
When setting the status, the activity time is updated to reflect that activity;
|
||||||
|
the client does not need to specify the ``last_active_ago`` field.
|
||||||
|
|
||||||
|
Fetching the presence list::
|
||||||
|
|
||||||
|
GET $PREFIX/presence/list
|
||||||
|
|
||||||
|
Returned content: JSON array containing objects; each object containing the
|
||||||
|
following keys:
|
||||||
|
user_id: observed user ID
|
||||||
|
presence: "offline"|"unavailable"|"online"|"free_for_chat"
|
||||||
|
status_msg: (optional) string of freeform text
|
||||||
|
last_active_ago: miliseconds since the last activity by the user
|
||||||
|
|
||||||
|
Maintaining the presence list::
|
||||||
|
|
||||||
|
POST $PREFIX/presence/list
|
||||||
|
|
||||||
|
Content: JSON object containing either or both of the following keys:
|
||||||
|
invite: JSON array of strings giving user IDs to send invites to
|
||||||
|
drop: JSON array of strings giving user IDs to remove from the list
|
||||||
|
|
||||||
|
.. TODO-spec
|
||||||
|
- Define how users receive presence invites, and how they accept/decline them
|
||||||
|
|
||||||
|
Server API
|
||||||
|
----------
|
||||||
|
The server API for presence is based entirely on exchange of the following
|
||||||
|
EDUs. There are no PDUs or Federation Queries involved.
|
||||||
|
|
||||||
|
Performing a presence update and poll subscription request::
|
||||||
|
|
||||||
|
EDU type: m.presence
|
||||||
|
|
||||||
|
Content keys:
|
||||||
|
push: (optional): list of push operations.
|
||||||
|
Each should be an object with the following keys:
|
||||||
|
user_id: string containing a User ID
|
||||||
|
presence: "offline"|"unavailable"|"online"|"free_for_chat"
|
||||||
|
status_msg: (optional) string of freeform text
|
||||||
|
last_active_ago: miliseconds since the last activity by the user
|
||||||
|
|
||||||
|
poll: (optional): list of strings giving User IDs
|
||||||
|
|
||||||
|
unpoll: (optional): list of strings giving User IDs
|
||||||
|
|
||||||
|
The presence of this combined message is two-fold: it informs the recipient
|
||||||
|
server of the current status of one or more users on the sending server (by the
|
||||||
|
``push`` key), and it maintains the list of users on the recipient server that
|
||||||
|
the sending server is interested in receiving updates for, by adding (by the
|
||||||
|
``poll`` key) or removing them (by the ``unpoll`` key). The ``poll`` and
|
||||||
|
``unpoll`` lists apply *changes* to the implied list of users; any existing IDs
|
||||||
|
that the server sent as ``poll`` operations in a previous message are not
|
||||||
|
removed until explicitly requested by a later ``unpoll``.
|
||||||
|
|
||||||
|
On receipt of a message containing a non-empty ``poll`` list, the receiving
|
||||||
|
server should immediately send the sending server a presence update EDU of its
|
||||||
|
own, containing in a ``push`` list the current state of every user that was in
|
||||||
|
the orginal EDU's ``poll`` list.
|
||||||
|
|
||||||
|
Sending a presence invite::
|
||||||
|
|
||||||
|
EDU type: m.presence_invite
|
||||||
|
|
||||||
|
Content keys:
|
||||||
|
observed_user: string giving the User ID of the user whose presence is
|
||||||
|
requested (i.e. the recipient of the invite)
|
||||||
|
observer_user: string giving the User ID of the user who is requesting to
|
||||||
|
observe the presence (i.e. the sender of the invite)
|
||||||
|
|
||||||
|
Accepting a presence invite::
|
||||||
|
|
||||||
|
EDU type: m.presence_accept
|
||||||
|
|
||||||
|
Content keys - as for m.presence_invite
|
||||||
|
|
||||||
|
Rejecting a presence invite::
|
||||||
|
|
||||||
|
EDU type: m.presence_deny
|
||||||
|
|
||||||
|
Content keys - as for m.presence_invite
|
||||||
|
|
||||||
|
.. TODO-doc
|
||||||
|
- Explain the timing-based roundtrip reduction mechanism for presence
|
||||||
|
messages
|
||||||
|
- Explain the zero-byte presence inference logic
|
||||||
|
See also: docs/client-server/model/presence
|
||||||
|
|
||||||
.. TODO Leo
|
|
||||||
- what is the event type. Are they bundled with other event types? If so, which.
|
|
||||||
- what are the valid keys / values. What do they represent. Any gotchas?
|
|
||||||
- Timeouts. How do they work, who sets them and how do they expire. Does one
|
|
||||||
have priority over another? Give examples.
|
|
||||||
|
|
||||||
Voice over IP
|
Voice over IP
|
||||||
=============
|
=============
|
||||||
|
@ -1681,19 +1904,14 @@ a call and the other party had accepted. Thusly, any media stream that had been
|
||||||
setup for use on a call should be transferred and used for the call that
|
setup for use on a call should be transferred and used for the call that
|
||||||
replaces it.
|
replaces it.
|
||||||
|
|
||||||
|
|
||||||
Profiles
|
Profiles
|
||||||
========
|
========
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
- Metadata extensibility
|
- Metadata extensibility
|
||||||
- Changing profile info generates m.presence events ("presencelike")
|
|
||||||
- keys on m.presence are optional, except presence which is required
|
|
||||||
- m.room.member is populated with the current displayname at that point in time.
|
|
||||||
- That is added by the HS, not you.
|
|
||||||
- Display name changes also generates m.room.member with displayname key f.e. room
|
|
||||||
the user is in.
|
|
||||||
|
|
||||||
Internally within Matrix users are referred to by their user ID, which is
|
Internally within Matrix users are referred to by their user ID, which is
|
||||||
typically a compact unique identifier. Profiles grant users the ability to see
|
typically a compact unique identifier. Profiles grant users the ability to see
|
||||||
|
@ -1704,7 +1922,106 @@ age or location.
|
||||||
A Profile consists of a display name, an avatar picture, and a set of other
|
A Profile consists of a display name, an avatar picture, and a set of other
|
||||||
metadata fields that the user may wish to publish (email address, phone
|
metadata fields that the user may wish to publish (email address, phone
|
||||||
numbers, website URLs, etc...). This specification puts no requirements on the
|
numbers, website URLs, etc...). This specification puts no requirements on the
|
||||||
display name other than it being a valid unicode string.
|
display name other than it being a valid unicode string. Avatar images are not
|
||||||
|
stored directly; instead the home server stores an ``http``-scheme URL where
|
||||||
|
clients may fetch it from.
|
||||||
|
|
||||||
|
Client API
|
||||||
|
----------
|
||||||
|
The client API for profile management consists of the following REST calls.
|
||||||
|
|
||||||
|
Fetching a user account displayname::
|
||||||
|
|
||||||
|
GET $PREFIX/profile/:user_id/displayname
|
||||||
|
|
||||||
|
Returned content: JSON object containing the following keys:
|
||||||
|
displayname: string of freeform text
|
||||||
|
|
||||||
|
This call may be used to fetch the user's own displayname or to query the name
|
||||||
|
of other users; either locally or on remote systems hosted on other home
|
||||||
|
servers.
|
||||||
|
|
||||||
|
Setting a new displayname::
|
||||||
|
|
||||||
|
PUT $PREFIX/profile/:user_id/displayname
|
||||||
|
|
||||||
|
Content: JSON object containing the following keys:
|
||||||
|
displayname: string of freeform text
|
||||||
|
|
||||||
|
Fetching a user account avatar URL::
|
||||||
|
|
||||||
|
GET $PREFIX/profile/:user_id/avatar_url
|
||||||
|
|
||||||
|
Returned content: JSON object containing the following keys:
|
||||||
|
avatar_url: string containing an http-scheme URL
|
||||||
|
|
||||||
|
As with displayname, this call may be used to fetch either the user's own, or
|
||||||
|
other users' avatar URL.
|
||||||
|
|
||||||
|
Setting a new avatar URL::
|
||||||
|
|
||||||
|
PUT $PREFIX/profile/:user_id/avatar_url
|
||||||
|
|
||||||
|
Content: JSON object containing the following keys:
|
||||||
|
avatar_url: string containing an http-scheme URL
|
||||||
|
|
||||||
|
Fetching combined account profile information::
|
||||||
|
|
||||||
|
GET $PREFIX/profile/:user_id
|
||||||
|
|
||||||
|
Returned content: JSON object containing the following keys:
|
||||||
|
displayname: string of freeform text
|
||||||
|
avatar_url: string containing an http-scheme URL
|
||||||
|
|
||||||
|
At the current time, this API simply returns the displayname and avatar URL
|
||||||
|
information, though it is intended to return more fields about the user's
|
||||||
|
profile once they are defined. Client implementations should take care not to
|
||||||
|
expect that these are the only two keys returned as future versions of this
|
||||||
|
specification may yield more keys here.
|
||||||
|
|
||||||
|
Server API
|
||||||
|
----------
|
||||||
|
The server API for profiles is based entirely on the following Federation
|
||||||
|
Queries. There are no additional EDU or PDU types involved, other than the
|
||||||
|
implicit ``m.presence`` and ``m.room.member`` events (see section below).
|
||||||
|
|
||||||
|
Querying profile information::
|
||||||
|
|
||||||
|
Query type: profile
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
user_id: the ID of the user whose profile to return
|
||||||
|
field: (optional) string giving a field name
|
||||||
|
|
||||||
|
Returns: JSON object containing the following keys:
|
||||||
|
displayname: string of freeform text
|
||||||
|
avatar_url: string containing an http-scheme URL
|
||||||
|
|
||||||
|
If the query contains the optional ``field`` key, it should give the name of a
|
||||||
|
result field. If such is present, then the result should contain only a field
|
||||||
|
of that name, with no others present. If not, the result should contain as much
|
||||||
|
of the user's profile as the home server has available and can make public.
|
||||||
|
|
||||||
|
Events on Change of Profile Information
|
||||||
|
---------------------------------------
|
||||||
|
Because the profile displayname and avatar information are likely to be used in
|
||||||
|
many places of a client's display, changes to these fields cause an automatic
|
||||||
|
propagation event to occur, informing likely-interested parties of the new
|
||||||
|
values. This change is conveyed using two separate mechanisms:
|
||||||
|
|
||||||
|
- a ``m.room.member`` event is sent to every room the user is a member of,
|
||||||
|
to update the ``displayname`` and ``avatar_url``.
|
||||||
|
- a presence status update is sent, again containing the new values of the
|
||||||
|
``displayname`` and ``avatar_url`` keys, in addition to the required
|
||||||
|
``presence`` key containing the current presence state of the user.
|
||||||
|
|
||||||
|
Both of these should be done automatically by the home server when a user
|
||||||
|
successfully changes their displayname or avatar URL fields.
|
||||||
|
|
||||||
|
Additionally, when home servers emit room membership events for their own
|
||||||
|
users, they should include the displayname and avatar URL fields in these
|
||||||
|
events so that clients already have these details to hand, and do not have to
|
||||||
|
perform extra roundtrips to query it.
|
||||||
|
|
||||||
|
|
||||||
Identity
|
Identity
|
||||||
|
@ -1712,9 +2029,10 @@ Identity
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO Dave
|
.. TODO-doc Dave
|
||||||
- 3PIDs and identity server, functions
|
- 3PIDs and identity server, functions
|
||||||
|
|
||||||
|
|
||||||
Federation
|
Federation
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
@ -1823,9 +2141,7 @@ is another list containing the EDUs. This key may be entirely absent if there
|
||||||
are no EDUs to transfer.
|
are no EDUs to transfer.
|
||||||
|
|
||||||
(* Normally the PDU list will be non-empty, but the server should cope with
|
(* Normally the PDU list will be non-empty, but the server should cope with
|
||||||
receiving an "empty" transaction, as this is useful for informing peers of
|
receiving an "empty" transaction.)
|
||||||
other transaction IDs they should be aware of. This effectively acts as a push
|
|
||||||
mechanism to encourage peers to continue to replicate content.)
|
|
||||||
|
|
||||||
PDUs and EDUs
|
PDUs and EDUs
|
||||||
-------------
|
-------------
|
||||||
|
@ -1884,10 +2200,9 @@ All PDUs have:
|
||||||
The maximum depth of the previous PDUs plus one.
|
The maximum depth of the previous PDUs plus one.
|
||||||
|
|
||||||
|
|
||||||
.. TODO paul
|
.. TODO-spec paul
|
||||||
[[TODO(paul): Update this structure so that 'pdu_id' is a two-element
|
- Update this structure so that 'pdu_id' is a two-element [origin,ref] pair
|
||||||
[origin,ref] pair like the prev_pdus are]]
|
like the prev_pdus are
|
||||||
|
|
||||||
|
|
||||||
For state updates:
|
For state updates:
|
||||||
|
|
||||||
|
@ -1909,7 +2224,7 @@ For state updates:
|
||||||
Description:
|
Description:
|
||||||
The asserted power level of the user performing the update.
|
The asserted power level of the user performing the update.
|
||||||
|
|
||||||
``min_update``
|
``required_power_level``
|
||||||
Type:
|
Type:
|
||||||
Integer
|
Integer
|
||||||
Description:
|
Description:
|
||||||
|
@ -1927,7 +2242,7 @@ For state updates:
|
||||||
Description:
|
Description:
|
||||||
The PDU id of the update this replaces.
|
The PDU id of the update this replaces.
|
||||||
|
|
||||||
``user``
|
``user_id``
|
||||||
Type:
|
Type:
|
||||||
String
|
String
|
||||||
Description:
|
Description:
|
||||||
|
@ -1967,18 +2282,10 @@ keys exist to support this:
|
||||||
|
|
||||||
{...,
|
{...,
|
||||||
"is_state":true,
|
"is_state":true,
|
||||||
"state_key":TODO
|
"state_key":TODO-doc
|
||||||
"power_level":TODO
|
"power_level":TODO-doc
|
||||||
"prev_state_id":TODO
|
"prev_state_id":TODO-doc
|
||||||
"prev_state_origin":TODO}
|
"prev_state_origin":TODO-doc}
|
||||||
|
|
||||||
.. TODO paul
|
|
||||||
[[TODO(paul): At this point we should probably have a long description of how
|
|
||||||
State management works, with descriptions of clobbering rules, power levels, etc
|
|
||||||
etc... But some of that detail is rather up-in-the-air, on the whiteboard, and
|
|
||||||
so on. This part needs refining. And writing in its own document as the details
|
|
||||||
relate to the server/system as a whole, not specifically to server-server
|
|
||||||
federation.]]
|
|
||||||
|
|
||||||
EDUs, by comparison to PDUs, do not have an ID, a context, or a list of
|
EDUs, by comparison to PDUs, do not have an ID, a context, or a list of
|
||||||
"previous" IDs. The only mandatory fields for these are the type, origin and
|
"previous" IDs. The only mandatory fields for these are the type, origin and
|
||||||
|
@ -1993,7 +2300,7 @@ destination home server names, and the actual nested content.
|
||||||
|
|
||||||
|
|
||||||
Protocol URLs
|
Protocol URLs
|
||||||
=============
|
-------------
|
||||||
.. WARNING::
|
.. WARNING::
|
||||||
This section may be misleading or inaccurate.
|
This section may be misleading or inaccurate.
|
||||||
|
|
||||||
|
@ -2005,7 +2312,7 @@ For active pushing of messages representing live activity "as it happens"::
|
||||||
|
|
||||||
PUT .../send/:transaction_id/
|
PUT .../send/:transaction_id/
|
||||||
Body: JSON encoding of a single Transaction
|
Body: JSON encoding of a single Transaction
|
||||||
Response: TODO
|
Response: TODO-doc
|
||||||
|
|
||||||
The transaction_id path argument will override any ID given in the JSON body.
|
The transaction_id path argument will override any ID given in the JSON body.
|
||||||
The destination name will be set to that of the receiving server itself. Each
|
The destination name will be set to that of the receiving server itself. Each
|
||||||
|
@ -2068,7 +2375,7 @@ Backfilling
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-doc
|
||||||
- What it is, when is it used, how is it done
|
- What it is, when is it used, how is it done
|
||||||
|
|
||||||
SRV Records
|
SRV Records
|
||||||
|
@ -2076,15 +2383,48 @@ SRV Records
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-doc
|
||||||
- Why it is needed
|
- Why it is needed
|
||||||
|
|
||||||
|
State Conflict Resolution
|
||||||
|
-------------------------
|
||||||
|
.. NOTE::
|
||||||
|
This section is a work in progress.
|
||||||
|
|
||||||
|
.. TODO-doc
|
||||||
|
- How do conflicts arise (diagrams?)
|
||||||
|
- How are they resolved (incl tie breaks)
|
||||||
|
- How does this work with deleting current state
|
||||||
|
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
|
Server-Server Authentication
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. TODO-doc
|
||||||
|
- Why is this needed.
|
||||||
|
- High level overview of process.
|
||||||
|
- Transaction/PDU signing
|
||||||
|
- How does this work with redactions? (eg hashing required keys only)
|
||||||
|
|
||||||
|
End-to-End Encryption
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. TODO-doc
|
||||||
|
- Why is this needed.
|
||||||
|
- Overview of process
|
||||||
|
- Implementation
|
||||||
|
|
||||||
|
Lawful Interception
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Key Escrow Servers
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Threat Model
|
Threat Model
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -2119,7 +2459,7 @@ victim would then include in their view of the chatroom history. Other servers
|
||||||
in the chatroom would reject the invalid messages and potentially reject the
|
in the chatroom would reject the invalid messages and potentially reject the
|
||||||
victims messages as well since they depended on the invalid messages.
|
victims messages as well since they depended on the invalid messages.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
Track trustworthiness of HS or users based on if they try to pretend they
|
Track trustworthiness of HS or users based on if they try to pretend they
|
||||||
haven't seen recent events, and fake a splitbrain... --M
|
haven't seen recent events, and fake a splitbrain... --M
|
||||||
|
|
||||||
|
@ -2227,39 +2567,42 @@ standard error response of the form::
|
||||||
The ``retry_after_ms`` key SHOULD be included to tell the client how long they
|
The ``retry_after_ms`` key SHOULD be included to tell the client how long they
|
||||||
have to wait in milliseconds before they can try again.
|
have to wait in milliseconds before they can try again.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
- Surely we should recommend an algorithm for the rate limiting, rather than letting every
|
- Surely we should recommend an algorithm for the rate limiting, rather than letting every
|
||||||
homeserver come up with their own idea, causing totally unpredictable performance over
|
homeserver come up with their own idea, causing totally unpredictable performance over
|
||||||
federated rooms?
|
federated rooms?
|
||||||
- crypto (s-s auth)
|
|
||||||
- E2E
|
|
||||||
- Lawful intercept + Key Escrow
|
|
||||||
TODO Mark
|
|
||||||
|
|
||||||
Policy Servers
|
Policy Servers
|
||||||
==============
|
==============
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
We should mention them in the Architecture section at least...
|
We should mention them in the Architecture section at least: how they fit
|
||||||
|
into the picture.
|
||||||
|
|
||||||
|
Enforcing policies
|
||||||
|
------------------
|
||||||
|
|
||||||
|
|
||||||
Content repository
|
Content repository
|
||||||
==================
|
==================
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
- path to upload
|
- path to upload
|
||||||
- format for thumbnail paths, mention what it is protecting against.
|
- format for thumbnail paths, mention what it is protecting against.
|
||||||
- content size limit and associated M_ERROR.
|
- content size limit and associated M_ERROR.
|
||||||
|
|
||||||
|
|
||||||
Address book repository
|
Address book repository
|
||||||
=======================
|
=======================
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
This section is a work in progress.
|
This section is a work in progress.
|
||||||
|
|
||||||
.. TODO
|
.. TODO-spec
|
||||||
- format: POST(?) wodges of json, some possible processing, then return wodges of json on GET.
|
- format: POST(?) wodges of json, some possible processing, then return wodges of json on GET.
|
||||||
- processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of
|
- processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of
|
||||||
contacts), etc.
|
contacts), etc.
|
||||||
|
|
51
docs/state_resolution.rst
Normal file
51
docs/state_resolution.rst
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
State Resolution
|
||||||
|
================
|
||||||
|
This section describes why we need state resolution and how it works.
|
||||||
|
|
||||||
|
|
||||||
|
Motivation
|
||||||
|
-----------
|
||||||
|
We want to be able to associate some shared state with rooms, e.g. a room name
|
||||||
|
or members list. This is done by having a current state dictionary that maps
|
||||||
|
from the pair event type and state key to an event.
|
||||||
|
|
||||||
|
However, since the servers involved in the room are distributed we need to be
|
||||||
|
able to handle the case when two (or more) servers try and update the state at
|
||||||
|
the same time. This is done via the state resolution algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
State Tree
|
||||||
|
------------
|
||||||
|
State events contain a reference to the state it is trying to replace. These
|
||||||
|
relations form a tree where the current state is one of the leaf nodes.
|
||||||
|
|
||||||
|
Note that state events are events, and so are part of the PDU graph. Thus we
|
||||||
|
can be sure that (modulo the internet being particularly broken) we will see
|
||||||
|
all state events eventually.
|
||||||
|
|
||||||
|
|
||||||
|
Algorithm requirements
|
||||||
|
----------------------
|
||||||
|
We want the algorithm to have the following properties:
|
||||||
|
- Since we aren't guaranteed what order we receive state events in, except that
|
||||||
|
we see parents before children, the state resolution algorithm must not depend
|
||||||
|
on the order and must always come to the same result.
|
||||||
|
- If we receive a state event whose parent is the current state, then the
|
||||||
|
algorithm will select it.
|
||||||
|
- The algorithm does not depend on internal state, ensuring all servers should
|
||||||
|
come to the same decision.
|
||||||
|
|
||||||
|
These three properties mean it is enough to keep track of the current state and
|
||||||
|
compare it with any new proposed state, rather than having to keep track of all
|
||||||
|
the leafs of the tree and recomputing across the entire state tree.
|
||||||
|
|
||||||
|
|
||||||
|
Current Implementation
|
||||||
|
----------------------
|
||||||
|
The current implementation works as follows: Upon receipt of a newly proposed
|
||||||
|
state change we first find the common ancestor. Then we take the maximum
|
||||||
|
across each branch of the users' power levels, if one is higher then it is
|
||||||
|
selected as the current state. Otherwise, we check if one chain is longer than
|
||||||
|
the other, if so we choose that one. If that also fails, then we concatenate
|
||||||
|
all the pdu ids and take a SHA1 hash and compare them to select a common
|
||||||
|
ancestor.
|
|
@ -110,7 +110,7 @@ $('.register').live('click', function() {
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user_id: user, password: password }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
onLoggedIn(data);
|
onLoggedIn(data);
|
||||||
|
|
|
@ -14,7 +14,7 @@ $('.register').live('click', function() {
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user_id: user, password: password }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
showLoggedIn(data);
|
showLoggedIn(data);
|
||||||
|
|
|
@ -25,8 +25,8 @@ from twisted.web.static import File
|
||||||
from twisted.web.server import Site
|
from twisted.web.server import Site
|
||||||
from synapse.http.server import JsonResource, RootRedirect
|
from synapse.http.server import JsonResource, RootRedirect
|
||||||
from synapse.http.content_repository import ContentRepoResource
|
from synapse.http.content_repository import ContentRepoResource
|
||||||
from synapse.http.client import TwistedHttpClient
|
|
||||||
from synapse.http.server_key_resource import LocalKey
|
from synapse.http.server_key_resource import LocalKey
|
||||||
|
from synapse.http.client import MatrixHttpClient
|
||||||
from synapse.api.urls import (
|
from synapse.api.urls import (
|
||||||
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
||||||
SERVER_KEY_PREFIX,
|
SERVER_KEY_PREFIX,
|
||||||
|
@ -49,7 +49,7 @@ logger = logging.getLogger(__name__)
|
||||||
class SynapseHomeServer(HomeServer):
|
class SynapseHomeServer(HomeServer):
|
||||||
|
|
||||||
def build_http_client(self):
|
def build_http_client(self):
|
||||||
return TwistedHttpClient(self)
|
return MatrixHttpClient(self)
|
||||||
|
|
||||||
def build_resource_for_client(self):
|
def build_resource_for_client(self):
|
||||||
return JsonResource()
|
return JsonResource()
|
||||||
|
|
|
@ -123,6 +123,8 @@ class Config(object):
|
||||||
# style mode markers into the file, to hint to people that
|
# style mode markers into the file, to hint to people that
|
||||||
# this is a YAML file.
|
# this is a YAML file.
|
||||||
yaml.dump(config, config_file, default_flow_style=False)
|
yaml.dump(config, config_file, default_flow_style=False)
|
||||||
|
print "A config file has been generated in %s for server name '%s') with corresponding SSL keys and self-signed certificates. Please review this file and customise it to your needs." % (config_args.config_path, config['server_name'])
|
||||||
|
print "If this server name is incorrect, you will need to regenerate the SSL certificates"
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
return cls(args)
|
return cls(args)
|
||||||
|
|
|
@ -163,7 +163,8 @@ class ReplicationLayer(object):
|
||||||
return defer.succeed(None)
|
return defer.succeed(None)
|
||||||
|
|
||||||
@log_function
|
@log_function
|
||||||
def make_query(self, destination, query_type, args):
|
def make_query(self, destination, query_type, args,
|
||||||
|
retry_on_dns_fail=True):
|
||||||
"""Sends a federation Query to a remote homeserver of the given type
|
"""Sends a federation Query to a remote homeserver of the given type
|
||||||
and arguments.
|
and arguments.
|
||||||
|
|
||||||
|
@ -178,7 +179,9 @@ class ReplicationLayer(object):
|
||||||
a Deferred which will eventually yield a JSON object from the
|
a Deferred which will eventually yield a JSON object from the
|
||||||
response
|
response
|
||||||
"""
|
"""
|
||||||
return self.transport_layer.make_query(destination, query_type, args)
|
return self.transport_layer.make_query(
|
||||||
|
destination, query_type, args, retry_on_dns_fail=retry_on_dns_fail
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
|
|
|
@ -195,13 +195,14 @@ class TransportLayer(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def make_query(self, destination, query_type, args):
|
def make_query(self, destination, query_type, args, retry_on_dns_fail):
|
||||||
path = PREFIX + "/query/%s" % query_type
|
path = PREFIX + "/query/%s" % query_type
|
||||||
|
|
||||||
response = yield self.client.get_json(
|
response = yield self.client.get_json(
|
||||||
destination=destination,
|
destination=destination,
|
||||||
path=path,
|
path=path,
|
||||||
args=args
|
args=args,
|
||||||
|
retry_on_dns_fail=retry_on_dns_fail,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
|
@ -18,7 +18,6 @@ from twisted.internet import defer
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.http.client import HttpClient
|
|
||||||
from synapse.api.events.room import RoomAliasesEvent
|
from synapse.api.events.room import RoomAliasesEvent
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -98,8 +97,8 @@ class DirectoryHandler(BaseHandler):
|
||||||
query_type="directory",
|
query_type="directory",
|
||||||
args={
|
args={
|
||||||
"room_alias": room_alias.to_string(),
|
"room_alias": room_alias.to_string(),
|
||||||
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
|
},
|
||||||
}
|
retry_on_dns_fail=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result and "room_id" in result and "servers" in result:
|
if result and "room_id" in result and "servers" in result:
|
||||||
|
|
|
@ -17,7 +17,7 @@ from twisted.internet import defer
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
from synapse.api.errors import LoginError, Codes
|
from synapse.api.errors import LoginError, Codes
|
||||||
from synapse.http.client import PlainHttpClient
|
from synapse.http.client import IdentityServerHttpClient
|
||||||
from synapse.util.emailutils import EmailException
|
from synapse.util.emailutils import EmailException
|
||||||
import synapse.util.emailutils as emailutils
|
import synapse.util.emailutils as emailutils
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ class LoginHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _query_email(self, email):
|
def _query_email(self, email):
|
||||||
httpCli = PlainHttpClient(self.hs)
|
httpCli = IdentityServerHttpClient(self.hs)
|
||||||
data = yield httpCli.get_json(
|
data = yield httpCli.get_json(
|
||||||
'matrix.org:8090', # TODO FIXME This should be configurable.
|
'matrix.org:8090', # TODO FIXME This should be configurable.
|
||||||
"/_matrix/identity/api/v1/lookup?medium=email&address=" +
|
"/_matrix/identity/api/v1/lookup?medium=email&address=" +
|
||||||
|
|
|
@ -22,7 +22,8 @@ from synapse.api.errors import (
|
||||||
)
|
)
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
import synapse.util.stringutils as stringutils
|
import synapse.util.stringutils as stringutils
|
||||||
from synapse.http.client import PlainHttpClient
|
from synapse.http.client import IdentityServerHttpClient
|
||||||
|
from synapse.http.client import CaptchaServerHttpClient
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
@ -154,7 +155,9 @@ class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _threepid_from_creds(self, creds):
|
def _threepid_from_creds(self, creds):
|
||||||
httpCli = PlainHttpClient(self.hs)
|
# TODO: get this from the homeserver rather than creating a new one for
|
||||||
|
# each request
|
||||||
|
httpCli = IdentityServerHttpClient(self.hs)
|
||||||
# XXX: make this configurable!
|
# XXX: make this configurable!
|
||||||
trustedIdServers = ['matrix.org:8090']
|
trustedIdServers = ['matrix.org:8090']
|
||||||
if not creds['idServer'] in trustedIdServers:
|
if not creds['idServer'] in trustedIdServers:
|
||||||
|
@ -173,7 +176,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _bind_threepid(self, creds, mxid):
|
def _bind_threepid(self, creds, mxid):
|
||||||
httpCli = PlainHttpClient(self.hs)
|
httpCli = IdentityServerHttpClient(self.hs)
|
||||||
data = yield httpCli.post_urlencoded_get_json(
|
data = yield httpCli.post_urlencoded_get_json(
|
||||||
creds['idServer'],
|
creds['idServer'],
|
||||||
"/_matrix/identity/api/v1/3pid/bind",
|
"/_matrix/identity/api/v1/3pid/bind",
|
||||||
|
@ -203,7 +206,9 @@ class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _submit_captcha(self, ip_addr, private_key, challenge, response):
|
def _submit_captcha(self, ip_addr, private_key, challenge, response):
|
||||||
client = PlainHttpClient(self.hs)
|
# TODO: get this from the homeserver rather than creating a new one for
|
||||||
|
# each request
|
||||||
|
client = CaptchaServerHttpClient(self.hs)
|
||||||
data = yield client.post_urlencoded_get_raw(
|
data = yield client.post_urlencoded_get_raw(
|
||||||
"www.google.com:80",
|
"www.google.com:80",
|
||||||
"/recaptcha/api/verify",
|
"/recaptcha/api/verify",
|
||||||
|
|
|
@ -35,56 +35,6 @@ import urllib
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# FIXME: SURELY these should be killed?!
|
|
||||||
_destination_mappings = {
|
|
||||||
"red": "localhost:8080",
|
|
||||||
"blue": "localhost:8081",
|
|
||||||
"green": "localhost:8082",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class HttpClient(object):
|
|
||||||
""" Interface for talking json over http
|
|
||||||
"""
|
|
||||||
RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
|
|
||||||
|
|
||||||
def put_json(self, destination, path, data):
|
|
||||||
""" Sends the specifed json data using PUT
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): The remote server to send the HTTP request
|
|
||||||
to.
|
|
||||||
path (str): The HTTP path.
|
|
||||||
data (dict): A dict containing the data that will be used as
|
|
||||||
the request body. This will be encoded as JSON.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Succeeds when we get a 2xx HTTP response. The result
|
|
||||||
will be the decoded JSON body. On a 4xx or 5xx error response a
|
|
||||||
CodeMessageException is raised.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_json(self, destination, path, args=None):
|
|
||||||
""" Get's some json from the given host homeserver and path
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): The remote server to send the HTTP request
|
|
||||||
to.
|
|
||||||
path (str): The HTTP path.
|
|
||||||
args (dict): A dictionary used to create query strings, defaults to
|
|
||||||
None.
|
|
||||||
**Note**: The value of each key is assumed to be an iterable
|
|
||||||
and *not* a string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Succeeds when we get *any* HTTP response.
|
|
||||||
|
|
||||||
The result of the deferred is a tuple of `(code, response)`,
|
|
||||||
where `response` is a dict representing the decoded JSON body.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MatrixHttpAgent(_AgentBase):
|
class MatrixHttpAgent(_AgentBase):
|
||||||
|
|
||||||
|
@ -109,113 +59,14 @@ class MatrixHttpAgent(_AgentBase):
|
||||||
parsed_URI.originForm)
|
parsed_URI.originForm)
|
||||||
|
|
||||||
|
|
||||||
class TwistedHttpClient(HttpClient):
|
class BaseHttpClient(object):
|
||||||
""" Wrapper around the twisted HTTP client api.
|
"""Base class for HTTP clients using twisted.
|
||||||
|
|
||||||
Attributes:
|
|
||||||
agent (twisted.web.client.Agent): The twisted Agent used to send the
|
|
||||||
requests.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.agent = MatrixHttpAgent(reactor)
|
self.agent = MatrixHttpAgent(reactor)
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def put_json(self, destination, path, data, on_send_callback=None):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"PUT",
|
|
||||||
path.encode("ascii"),
|
|
||||||
producer=_JsonProducer(data),
|
|
||||||
headers_dict={"Content-Type": ["application/json"]},
|
|
||||||
on_send_callback=on_send_callback,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("Getting resp body")
|
|
||||||
body = yield readBody(response)
|
|
||||||
logger.debug("Got resp body")
|
|
||||||
|
|
||||||
defer.returnValue((response.code, body))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def get_json(self, destination, path, args={}):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
logger.debug("get_json args: %s", args)
|
|
||||||
|
|
||||||
retry_on_dns_fail = True
|
|
||||||
if HttpClient.RETRY_DNS_LOOKUP_FAILURES in args:
|
|
||||||
# FIXME: This isn't ideal, but the interface exposed in get_json
|
|
||||||
# isn't comprehensive enough to give caller's any control over
|
|
||||||
# their connection mechanics.
|
|
||||||
retry_on_dns_fail = args.pop(HttpClient.RETRY_DNS_LOOKUP_FAILURES)
|
|
||||||
|
|
||||||
query_bytes = urllib.urlencode(args, True)
|
|
||||||
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"GET",
|
|
||||||
path.encode("ascii"),
|
|
||||||
query_bytes=query_bytes,
|
|
||||||
retry_on_dns_fail=retry_on_dns_fail
|
|
||||||
)
|
|
||||||
|
|
||||||
body = yield readBody(response)
|
|
||||||
|
|
||||||
defer.returnValue(json.loads(body))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def post_urlencoded_get_json(self, destination, path, args={}):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
logger.debug("post_urlencoded_get_json args: %s", args)
|
|
||||||
query_bytes = urllib.urlencode(args, True)
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"POST",
|
|
||||||
path.encode("ascii"),
|
|
||||||
producer=FileBodyProducer(StringIO(urllib.urlencode(args))),
|
|
||||||
headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
body = yield readBody(response)
|
|
||||||
|
|
||||||
defer.returnValue(json.loads(body))
|
|
||||||
|
|
||||||
# XXX FIXME : I'm so sorry.
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def post_urlencoded_get_raw(self, destination, path, accept_partial=False, args={}):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
query_bytes = urllib.urlencode(args, True)
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"POST",
|
|
||||||
path.encode("ascii"),
|
|
||||||
producer=FileBodyProducer(StringIO(urllib.urlencode(args))),
|
|
||||||
headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
body = yield readBody(response)
|
|
||||||
defer.returnValue(body)
|
|
||||||
except PartialDownloadError as e:
|
|
||||||
if accept_partial:
|
|
||||||
defer.returnValue(e.response)
|
|
||||||
else:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _create_request(self, destination, method, path_bytes, param_bytes=b"",
|
def _create_request(self, destination, method, path_bytes, param_bytes=b"",
|
||||||
query_bytes=b"", producer=None, headers_dict={},
|
query_bytes=b"", producer=None, headers_dict={},
|
||||||
|
@ -239,7 +90,6 @@ class TwistedHttpClient(HttpClient):
|
||||||
|
|
||||||
retries_left = 5
|
retries_left = 5
|
||||||
|
|
||||||
# TODO: setup and pass in an ssl_context to enable TLS
|
|
||||||
endpoint = self._getEndpoint(reactor, destination);
|
endpoint = self._getEndpoint(reactor, destination);
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -290,6 +140,85 @@ class TwistedHttpClient(HttpClient):
|
||||||
|
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixHttpClient(BaseHttpClient):
|
||||||
|
""" Wrapper around the twisted HTTP client api. Implements
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
agent (twisted.web.client.Agent): The twisted Agent used to send the
|
||||||
|
requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def put_json(self, destination, path, data, on_send_callback=None):
|
||||||
|
""" Sends the specifed json data using PUT
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): The remote server to send the HTTP request
|
||||||
|
to.
|
||||||
|
path (str): The HTTP path.
|
||||||
|
data (dict): A dict containing the data that will be used as
|
||||||
|
the request body. This will be encoded as JSON.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Succeeds when we get a 2xx HTTP response. The result
|
||||||
|
will be the decoded JSON body. On a 4xx or 5xx error response a
|
||||||
|
CodeMessageException is raised.
|
||||||
|
"""
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"PUT",
|
||||||
|
path.encode("ascii"),
|
||||||
|
producer=_JsonProducer(data),
|
||||||
|
headers_dict={"Content-Type": ["application/json"]},
|
||||||
|
on_send_callback=on_send_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug("Getting resp body")
|
||||||
|
body = yield readBody(response)
|
||||||
|
logger.debug("Got resp body")
|
||||||
|
|
||||||
|
defer.returnValue((response.code, body))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_json(self, destination, path, args={}, retry_on_dns_fail=True):
|
||||||
|
""" Get's some json from the given host homeserver and path
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): The remote server to send the HTTP request
|
||||||
|
to.
|
||||||
|
path (str): The HTTP path.
|
||||||
|
args (dict): A dictionary used to create query strings, defaults to
|
||||||
|
None.
|
||||||
|
**Note**: The value of each key is assumed to be an iterable
|
||||||
|
and *not* a string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Succeeds when we get *any* HTTP response.
|
||||||
|
|
||||||
|
The result of the deferred is a tuple of `(code, response)`,
|
||||||
|
where `response` is a dict representing the decoded JSON body.
|
||||||
|
"""
|
||||||
|
logger.debug("get_json args: %s", args)
|
||||||
|
|
||||||
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
|
||||||
|
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"GET",
|
||||||
|
path.encode("ascii"),
|
||||||
|
query_bytes=query_bytes,
|
||||||
|
retry_on_dns_fail=retry_on_dns_fail
|
||||||
|
)
|
||||||
|
|
||||||
|
body = yield readBody(response)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(body))
|
||||||
|
|
||||||
|
|
||||||
def _getEndpoint(self, reactor, destination):
|
def _getEndpoint(self, reactor, destination):
|
||||||
return matrix_endpoint(
|
return matrix_endpoint(
|
||||||
reactor, destination, timeout=10,
|
reactor, destination, timeout=10,
|
||||||
|
@ -297,10 +226,63 @@ class TwistedHttpClient(HttpClient):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PlainHttpClient(TwistedHttpClient):
|
class IdentityServerHttpClient(BaseHttpClient):
|
||||||
|
"""Separate HTTP client for talking to the Identity servers since they
|
||||||
|
don't use SRV records and talk x-www-form-urlencoded rather than JSON.
|
||||||
|
"""
|
||||||
|
def _getEndpoint(self, reactor, destination):
|
||||||
|
#TODO: This should be talking TLS
|
||||||
|
return matrix_endpoint(reactor, destination, timeout=10)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def post_urlencoded_get_json(self, destination, path, args={}):
|
||||||
|
logger.debug("post_urlencoded_get_json args: %s", args)
|
||||||
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"POST",
|
||||||
|
path.encode("ascii"),
|
||||||
|
producer=FileBodyProducer(StringIO(query_bytes)),
|
||||||
|
headers_dict={
|
||||||
|
"Content-Type": ["application/x-www-form-urlencoded"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
body = yield readBody(response)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(body))
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaServerHttpClient(MatrixHttpClient):
|
||||||
|
"""Separate HTTP client for talking to google's captcha servers"""
|
||||||
|
|
||||||
def _getEndpoint(self, reactor, destination):
|
def _getEndpoint(self, reactor, destination):
|
||||||
return matrix_endpoint(reactor, destination, timeout=10)
|
return matrix_endpoint(reactor, destination, timeout=10)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def post_urlencoded_get_raw(self, destination, path, accept_partial=False,
|
||||||
|
args={}):
|
||||||
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"POST",
|
||||||
|
path.encode("ascii"),
|
||||||
|
producer=FileBodyProducer(StringIO(query_bytes)),
|
||||||
|
headers_dict={
|
||||||
|
"Content-Type": ["application/x-www-form-urlencoded"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
body = yield readBody(response)
|
||||||
|
defer.returnValue(body)
|
||||||
|
except PartialDownloadError as e:
|
||||||
|
if accept_partial:
|
||||||
|
defer.returnValue(e.response)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
def _print_ex(e):
|
def _print_ex(e):
|
||||||
if hasattr(e, "reasons") and e.reasons:
|
if hasattr(e, "reasons") and e.reasons:
|
||||||
|
|
4
synctl
4
synctl
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SYNAPSE="synapse/app/homeserver.py"
|
SYNAPSE="python -m synapse.app.homeserver"
|
||||||
|
|
||||||
CONFIGFILE="homeserver.yaml"
|
CONFIGFILE="homeserver.yaml"
|
||||||
PIDFILE="homeserver.pid"
|
PIDFILE="homeserver.pid"
|
||||||
|
@ -14,7 +14,7 @@ case "$1" in
|
||||||
start)
|
start)
|
||||||
if [ ! -f "$CONFIGFILE" ]; then
|
if [ ! -f "$CONFIGFILE" ]; then
|
||||||
echo "No config file found"
|
echo "No config file found"
|
||||||
echo "To generate a config file, run 'python --generate-config'"
|
echo "To generate a config file, run '$SYNAPSE -c $CONFIGFILE --generate-config --server-name=<server name>'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ class FederationTestCase(unittest.TestCase):
|
||||||
response = yield self.federation.make_query(
|
response = yield self.federation.make_query(
|
||||||
destination="remote",
|
destination="remote",
|
||||||
query_type="a-question",
|
query_type="a-question",
|
||||||
args={"one": "1", "two": "2"}
|
args={"one": "1", "two": "2"},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals({"your": "response"}, response)
|
self.assertEquals({"your": "response"}, response)
|
||||||
|
@ -265,7 +265,8 @@ class FederationTestCase(unittest.TestCase):
|
||||||
self.mock_http_client.get_json.assert_called_with(
|
self.mock_http_client.get_json.assert_called_with(
|
||||||
destination="remote",
|
destination="remote",
|
||||||
path="/_matrix/federation/v1/query/a-question",
|
path="/_matrix/federation/v1/query/a-question",
|
||||||
args={"one": "1", "two": "2"}
|
args={"one": "1", "two": "2"},
|
||||||
|
retry_on_dns_fail=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
|
|
@ -20,7 +20,6 @@ from twisted.internet import defer
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.http.client import HttpClient
|
|
||||||
from synapse.handlers.directory import DirectoryHandler
|
from synapse.handlers.directory import DirectoryHandler
|
||||||
from synapse.storage.directory import RoomAliasMapping
|
from synapse.storage.directory import RoomAliasMapping
|
||||||
|
|
||||||
|
@ -95,8 +94,8 @@ class DirectoryTestCase(unittest.TestCase):
|
||||||
query_type="directory",
|
query_type="directory",
|
||||||
args={
|
args={
|
||||||
"room_alias": "#another:remote",
|
"room_alias": "#another:remote",
|
||||||
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
|
},
|
||||||
}
|
retry_on_dns_fail=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
|
Loading…
Reference in a new issue