mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Merge branch develop into server2server_signing
Conflicts: synapse/app/homeserver.py
This commit is contained in:
commit
984e207b59
83
README.rst
83
README.rst
@ -20,18 +20,21 @@ The overall architecture is::
|
||||
WARNING
|
||||
=======
|
||||
|
||||
**Synapse is currently in a state of rapid development, and not all features are yet functional.
|
||||
Critically, some security features are still in development, which means Synapse can *not*
|
||||
be considered secure or reliable at this point.** For instance:
|
||||
**Synapse is currently in a state of rapid development, and not all features
|
||||
are yet functional. Critically, some security features are still in
|
||||
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.**
|
||||
- **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
|
||||
|
||||
Despite this, we believe Synapse is more than useful as a way for experimenting and
|
||||
exploring Synapse, and the missing features will land shortly. **Until then, please do *NOT*
|
||||
use Synapse for any remotely important or secure communication.**
|
||||
Despite this, we believe Synapse is more than useful as a way for experimenting
|
||||
and exploring Synapse, and the missing features will land shortly. **Until
|
||||
then, please do *NOT* use Synapse for any remotely important or secure
|
||||
communication.**
|
||||
|
||||
|
||||
Quick Start
|
||||
@ -46,17 +49,21 @@ To get up and running:
|
||||
- To simply play with an **existing** homeserver you can
|
||||
just go straight to http://matrix.org/alpha.
|
||||
|
||||
- To run your own **private** homeserver on localhost:8008, install synapse with
|
||||
``python setup.py develop --user`` and then run ``./synctl start`` twice (once to
|
||||
generate a config; once to actually run) - you will find a webclient running at
|
||||
http://localhost:8008. Please use a recent Chrome, Safari or Firefox for now...
|
||||
- To run your own **private** homeserver on localhost:8008, generate a basic
|
||||
config file: ``./synctl start`` will give you instructions on how to do this.
|
||||
For this purpose, you can use 'localhost' or your hostname as a server name.
|
||||
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
|
||||
and participate in the global Matrix federation, you must expose port 8448 to the
|
||||
internet and edit homeserver.yaml to specify server_name (the public DNS entry for
|
||||
this server) and then run ``synctl start``. If you changed the server_name, you may
|
||||
need to move the old database (homeserver.db) out of the way first. Then come join
|
||||
``#matrix:matrix.org`` and say hi! :)
|
||||
- To run a **public** homeserver and let it exchange messages with other
|
||||
homeservers and participate in the global Matrix federation, you must expose
|
||||
port 8448 to the internet and edit homeserver.yaml to specify server_name
|
||||
(the public DNS entry for this server) and then run ``synctl start``. If you
|
||||
changed the server_name, you may need to move the old database
|
||||
(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.
|
||||
|
||||
@ -80,8 +87,8 @@ which handle:
|
||||
- Placing 1:1 VoIP and Video calls
|
||||
|
||||
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
|
||||
the entirely open Matrix ecosystem rather than using closed or proprietary
|
||||
and clients, letting developers build messaging and VoIP functionality on top
|
||||
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
|
||||
generation of fully open and interoperable messaging and VoIP apps for the
|
||||
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
|
||||
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
|
||||
control and own your own communications and history or use one hosted by someone
|
||||
else (e.g. matrix.org) - there is no single point of control or mandatory
|
||||
service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
|
||||
control and own your own communications and history or use one hosted by
|
||||
someone else (e.g. matrix.org) - there is no single point of control or
|
||||
mandatory service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
|
||||
|
||||
Synapse ships with two basic demo Matrix clients: webclient (a basic group chat
|
||||
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).
|
||||
|
||||
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
|
||||
with the APIs and the demo clients, and let us know your thoughts at
|
||||
homeserver, and join the existing Matrix chatrooms already out there,
|
||||
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.
|
||||
|
||||
Thanks for trying Matrix!
|
||||
@ -136,20 +143,20 @@ to install by making setup.py do so, in --user mode::
|
||||
$ python setup.py develop --user
|
||||
|
||||
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 pip install --upgrade setuptools
|
||||
|
||||
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
|
||||
too.
|
||||
manually by checking it out and running ``python setup.py develop --user`` on
|
||||
it too.
|
||||
|
||||
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
|
||||
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
|
||||
other versions installed by setuputils in, for example, ~/.local/lib).
|
||||
installing it. Installing PyNaCl using pip may also work (remember to remove
|
||||
any other versions installed by setuputils in, for example, ~/.local/lib).
|
||||
|
||||
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
||||
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
|
||||
IDs:
|
||||
|
||||
1) Use the machine's own hostname as available on public DNS in the form of its
|
||||
A or AAAA records. This is easier to set up initially, perhaps for testing,
|
||||
but lacks the flexibility of SRV.
|
||||
1) Use the machine's own hostname as available on public DNS in the form of
|
||||
its A or AAAA records. This is easier to set up initially, perhaps for
|
||||
testing, but lacks the flexibility of 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
|
||||
@ -247,7 +254,7 @@ http://localhost:8080. Simply run::
|
||||
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
|
||||
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
|
||||
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
|
||||
synapse sandbox running on localhost)
|
||||
required due to lack of SRV records (e.g. @matthew:localhost:8448 on an
|
||||
internal synapse sandbox running on localhost)
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
the job of publishing the end-to-end encryption public keys for Matrix users is
|
||||
also very security-sensitive for similar reasons.
|
||||
if it is too easy to sign up for Matrix accounts or harvest 3PID data.
|
||||
Meanwhile the job of publishing the end-to-end encryption public keys for
|
||||
Matrix users is also very security-sensitive for similar reasons.
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
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",
|
||||
@ -39,14 +39,15 @@ when accessing other APIs::
|
||||
"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 forget the ``access_token``.
|
||||
|
||||
Implementation note: The matrix specification does not enforce how users
|
||||
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,
|
||||
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
|
||||
-----
|
||||
|
@ -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
|
||||
==============
|
||||
|
||||
|
@ -48,6 +48,22 @@
|
||||
"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
|
||||
this user. They are case-insensitive.
|
||||
|
||||
.. TODO
|
||||
.. TODO-spec
|
||||
- Need to specify precise grammar for Matrix IDs
|
||||
|
||||
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 |
|
||||
+------------------+ +------------------+
|
||||
| ................................. |
|
||||
|______| Partially Shared State |_______|
|
||||
|______| Shared State |_______|
|
||||
| Room ID: !qporfwt:matrix.org |
|
||||
| Servers: matrix.org, domain.com |
|
||||
| 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
|
||||
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
|
||||
shared state in order to participate in a room. State is scoped to a single
|
||||
room, and federation ensures that all home servers have the information they
|
||||
need, even if that means the home server has to request more information from
|
||||
another home server before processing the event.
|
||||
to, and how to process the event. State is scoped to a single room, and
|
||||
federation ensures that all home servers have the information they need, even
|
||||
if that means the home server has to request more information from another home
|
||||
server before processing the event.
|
||||
|
||||
Room Aliases
|
||||
------------
|
||||
@ -191,7 +190,7 @@ Each room can also have multiple "Room Aliases", which looks like::
|
||||
#room_alias:domain
|
||||
|
||||
.. 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
|
||||
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
|
||||
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
|
||||
@ -215,10 +217,6 @@ requests.
|
||||
| #bike >> !4rguxf:matrix.org |
|
||||
|________________________________|
|
||||
|
||||
.. TODO kegan
|
||||
- show the actual API rather than pseudo-API?
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
greatly impacted.
|
||||
the Matrix ecosystem. However, without one clients will not be able to look up
|
||||
user IDs using 3PIDs.
|
||||
|
||||
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
|
||||
can be used in the next request to continue where the client left off.
|
||||
|
||||
.. TODO
|
||||
.. TODO-spec
|
||||
How do we filter the event stream?
|
||||
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
|
||||
@ -703,9 +701,6 @@ Rooms
|
||||
|
||||
Creation
|
||||
--------
|
||||
.. TODO kegan
|
||||
- TODO: Key for invite these users?
|
||||
|
||||
To create a room, a client has to use the |createRoom|_ API. There are various
|
||||
options which can be set when creating a room:
|
||||
|
||||
@ -719,7 +714,7 @@ options which can be set when creating a room:
|
||||
Description:
|
||||
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
|
||||
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``
|
||||
Type:
|
||||
@ -790,63 +785,98 @@ includes:
|
||||
- ``m.room.send_event_level`` : The power level required in order to send a
|
||||
message in this room.
|
||||
- ``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.
|
||||
|
||||
Modifying aliases
|
||||
-----------------
|
||||
Room aliases
|
||||
------------
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO kegan
|
||||
- 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] }
|
||||
- format when retrieving list of aliases. NOT complete list.
|
||||
- format for adding/removing aliases.
|
||||
Room aliases can be created by sending a ``PUT /directory/room/<room alias>``::
|
||||
|
||||
{
|
||||
"room_id": <room id>
|
||||
}
|
||||
|
||||
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
|
||||
-----------
|
||||
.. NOTE::
|
||||
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
|
||||
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
|
||||
default and specific users' power levels can be set. By default all users have
|
||||
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.
|
||||
default and specific users' power levels can be set::
|
||||
|
||||
{
|
||||
"<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
|
||||
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
|
||||
defined in the following state events:
|
||||
|
||||
- ``m.room.send_event_level`` defines the minimum level for sending non-state
|
||||
events. Defaults to 50.
|
||||
- ``m.room.add_state_level`` defines the minimum level for adding new state,
|
||||
rather than updating existing state. Defaults to 50.
|
||||
- ``m.room.ops_level`` defines the minimum levels to ban and kick other users.
|
||||
This defaults to a kick and ban levels of 50 each.
|
||||
- ``m.room.send_event_level`` defines the minimum ``level`` for sending
|
||||
non-state events. Defaults to 50.
|
||||
- ``m.room.add_state_level`` defines the minimum ``level`` for adding new
|
||||
state, rather than updating existing state. Defaults to 50.
|
||||
- ``m.room.ops_level`` defines the minimum ``ban_level`` and ``kick_level`` to
|
||||
ban and kick other users respectively. This defaults to a kick and ban levels
|
||||
of 50 each.
|
||||
|
||||
|
||||
Joining rooms
|
||||
-------------
|
||||
.. TODO kegan
|
||||
- TODO: What does the home server have to do to join a user to a room?
|
||||
.. TODO-doc 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
|
||||
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
|
||||
--------------
|
||||
.. TODO kegan
|
||||
- Can invite users to a room if the room config key TODO is set to TODO. Must have required power level.
|
||||
.. TODO-doc Invite-join dance
|
||||
- Outline invite join dance. What is it? Why is it required? How does it work?
|
||||
- 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
|
||||
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).
|
||||
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
|
||||
- TODO Room config free-to-join value explanation
|
||||
``public``
|
||||
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
|
||||
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
|
||||
-------------
|
||||
.. TODO kegan
|
||||
- TODO: Grace period before deletion?
|
||||
- TODO: Under what conditions should a room NOT be purged?
|
||||
.. TODO-spec - HS deleting rooms they are no longer a part of. Not implemented.
|
||||
- This is actually Very Tricky. If all clients a HS is serving leave a room,
|
||||
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
|
||||
@ -945,11 +981,7 @@ directly by sending the following request to
|
||||
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
|
||||
|initialSync|_ API. Be aware that leaving a room is not equivalent to have
|
||||
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.
|
||||
|initialSync|_ API.
|
||||
|
||||
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
|
||||
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;
|
||||
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
|
||||
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
|
||||
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
|
||||
- TODO: JSON response format for both types
|
||||
- TODO: when would you use global? when would you use scoped?
|
||||
The |initialSync|_ API contains the following keys:
|
||||
|
||||
``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
|
||||
-------------------------
|
||||
@ -1098,7 +1172,7 @@ There are several APIs provided to ``GET`` events for a room:
|
||||
Response format:
|
||||
``[ { state event }, { state event }, ... ]``
|
||||
Example:
|
||||
TODO
|
||||
TODO-doc
|
||||
|
||||
|
||||
|/rooms/<room_id>/members|_
|
||||
@ -1107,7 +1181,7 @@ There are several APIs provided to ``GET`` events for a room:
|
||||
Response format:
|
||||
``{ "start": "<token>", "end": "<token>", "chunk": [ { m.room.member event }, ... ] }``
|
||||
Example:
|
||||
TODO
|
||||
TODO-doc
|
||||
|
||||
|/rooms/<room_id>/messages|_
|
||||
Description:
|
||||
@ -1117,16 +1191,16 @@ There are several APIs provided to ``GET`` events for a room:
|
||||
Response format:
|
||||
``{ "start": "<token>", "end": "<token>" }``
|
||||
Example:
|
||||
TODO
|
||||
TODO-doc
|
||||
|
||||
|/rooms/<room_id>/initialSync|_
|
||||
Description:
|
||||
Get all relevant events for a room. This includes state events, paginated
|
||||
non-state events and presence events.
|
||||
Response format:
|
||||
`` { TODO } ``
|
||||
`` { TODO-doc } ``
|
||||
Example:
|
||||
TODO
|
||||
TODO-doc
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
===========
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO dave?
|
||||
- voip events?
|
||||
|
||||
This specification outlines several standard event types, all of which are
|
||||
prefixed with ``m.``
|
||||
|
||||
@ -1232,7 +1333,7 @@ prefixed with ``m.``
|
||||
Example:
|
||||
``{ "join_rule": "public" }``
|
||||
Description:
|
||||
TODO : Use docs/models/rooms.rst
|
||||
TODO-doc : Use docs/models/rooms.rst
|
||||
|
||||
``m.room.power_levels``
|
||||
Summary:
|
||||
@ -1284,7 +1385,7 @@ prefixed with ``m.``
|
||||
Type:
|
||||
State event
|
||||
JSON format:
|
||||
``{ "ban_level": <int>, "kick_level": <int> }``
|
||||
``{ "ban_level": <int>, "kick_level": <int>, "redact_level": <int> }``
|
||||
Example:
|
||||
``{ "ban_level": 5, "kick_level": 5 }``
|
||||
Description:
|
||||
@ -1303,10 +1404,32 @@ prefixed with ``m.``
|
||||
Example:
|
||||
``{ "aliases": ["#foo:example.com"] }``
|
||||
Description:
|
||||
A server `may` inform the room that it has added or removed an alias for
|
||||
the room. This is purely for informational purposes and may become stale.
|
||||
Clients `should` check that the room alias is still valid before using it.
|
||||
The ``state_key`` of the event is the homeserver which owns the room alias.
|
||||
This event is sent by a homeserver directly to inform of changes to the
|
||||
list of aliases it knows about for that room. As a special-case, 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``
|
||||
Summary:
|
||||
@ -1361,6 +1484,10 @@ prefixed with ``m.``
|
||||
|
||||
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
|
||||
of message being sent. Each type has their own required and optional keys, as
|
||||
outlined below:
|
||||
@ -1480,8 +1607,9 @@ the following:
|
||||
- ``offline`` : The user is not connected to an event stream.
|
||||
- ``free_for_chat`` : The user is generally willing to receive messages
|
||||
moreso than default.
|
||||
- ``hidden`` : TODO. Behaves as offline, but allows the user to see the
|
||||
client state anyway and generally interact with client features.
|
||||
- ``hidden`` : Behaves as offline, but allows the user to see the client
|
||||
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
|
||||
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
|
||||
message is generated/emitted, that the user was last seen active.
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
Transmission
|
||||
------------
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO:
|
||||
- Transmitted as an EDU.
|
||||
- Presence lists determine who to send to.
|
||||
- ``free_for_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
|
||||
|
||||
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
|
||||
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
|
||||
@ -1534,16 +1659,114 @@ user, either:
|
||||
In the latter case, this allows for clients to display some minimal sense of
|
||||
presence information in a user list for a room.
|
||||
|
||||
Typing notifications
|
||||
====================
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
Client API
|
||||
----------
|
||||
The client API for presence is on the following set of REST calls.
|
||||
|
||||
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
|
||||
=============
|
||||
@ -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
|
||||
replaces it.
|
||||
|
||||
|
||||
Profiles
|
||||
========
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO
|
||||
.. TODO-spec
|
||||
- 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
|
||||
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
|
||||
metadata fields that the user may wish to publish (email address, phone
|
||||
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
|
||||
@ -1712,9 +2029,10 @@ Identity
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO Dave
|
||||
.. TODO-doc Dave
|
||||
- 3PIDs and identity server, functions
|
||||
|
||||
|
||||
Federation
|
||||
==========
|
||||
|
||||
@ -1823,9 +2141,7 @@ is another list containing the EDUs. This key may be entirely absent if there
|
||||
are no EDUs to transfer.
|
||||
|
||||
(* 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
|
||||
other transaction IDs they should be aware of. This effectively acts as a push
|
||||
mechanism to encourage peers to continue to replicate content.)
|
||||
receiving an "empty" transaction.)
|
||||
|
||||
PDUs and EDUs
|
||||
-------------
|
||||
@ -1884,10 +2200,9 @@ All PDUs have:
|
||||
The maximum depth of the previous PDUs plus one.
|
||||
|
||||
|
||||
.. TODO paul
|
||||
[[TODO(paul): Update this structure so that 'pdu_id' is a two-element
|
||||
[origin,ref] pair like the prev_pdus are]]
|
||||
|
||||
.. TODO-spec paul
|
||||
- Update this structure so that 'pdu_id' is a two-element [origin,ref] pair
|
||||
like the prev_pdus are
|
||||
|
||||
For state updates:
|
||||
|
||||
@ -1909,7 +2224,7 @@ For state updates:
|
||||
Description:
|
||||
The asserted power level of the user performing the update.
|
||||
|
||||
``min_update``
|
||||
``required_power_level``
|
||||
Type:
|
||||
Integer
|
||||
Description:
|
||||
@ -1927,7 +2242,7 @@ For state updates:
|
||||
Description:
|
||||
The PDU id of the update this replaces.
|
||||
|
||||
``user``
|
||||
``user_id``
|
||||
Type:
|
||||
String
|
||||
Description:
|
||||
@ -1967,18 +2282,10 @@ keys exist to support this:
|
||||
|
||||
{...,
|
||||
"is_state":true,
|
||||
"state_key":TODO
|
||||
"power_level":TODO
|
||||
"prev_state_id":TODO
|
||||
"prev_state_origin":TODO}
|
||||
|
||||
.. 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.]]
|
||||
"state_key":TODO-doc
|
||||
"power_level":TODO-doc
|
||||
"prev_state_id":TODO-doc
|
||||
"prev_state_origin":TODO-doc}
|
||||
|
||||
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
|
||||
@ -1993,7 +2300,7 @@ destination home server names, and the actual nested content.
|
||||
|
||||
|
||||
Protocol URLs
|
||||
=============
|
||||
-------------
|
||||
.. WARNING::
|
||||
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/
|
||||
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 destination name will be set to that of the receiving server itself. Each
|
||||
@ -2068,7 +2375,7 @@ Backfilling
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO
|
||||
.. TODO-doc
|
||||
- What it is, when is it used, how is it done
|
||||
|
||||
SRV Records
|
||||
@ -2076,15 +2383,48 @@ SRV Records
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO
|
||||
.. TODO-doc
|
||||
- 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
|
||||
========
|
||||
|
||||
.. NOTE::
|
||||
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
|
||||
------------
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
homeserver come up with their own idea, causing totally unpredictable performance over
|
||||
federated rooms?
|
||||
- crypto (s-s auth)
|
||||
- E2E
|
||||
- Lawful intercept + Key Escrow
|
||||
TODO Mark
|
||||
|
||||
|
||||
Policy Servers
|
||||
==============
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO
|
||||
We should mention them in the Architecture section at least...
|
||||
.. TODO-spec
|
||||
We should mention them in the Architecture section at least: how they fit
|
||||
into the picture.
|
||||
|
||||
Enforcing policies
|
||||
------------------
|
||||
|
||||
|
||||
Content repository
|
||||
==================
|
||||
.. NOTE::
|
||||
This section is a work in progress.
|
||||
|
||||
.. TODO
|
||||
.. TODO-spec
|
||||
- path to upload
|
||||
- format for thumbnail paths, mention what it is protecting against.
|
||||
- content size limit and associated M_ERROR.
|
||||
|
||||
|
||||
Address book repository
|
||||
=======================
|
||||
.. NOTE::
|
||||
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.
|
||||
- processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of
|
||||
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",
|
||||
type: "POST",
|
||||
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",
|
||||
success: function(data) {
|
||||
onLoggedIn(data);
|
||||
|
@ -14,7 +14,7 @@ $('.register').live('click', function() {
|
||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
||||
type: "POST",
|
||||
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",
|
||||
success: function(data) {
|
||||
showLoggedIn(data);
|
||||
|
@ -25,8 +25,8 @@ from twisted.web.static import File
|
||||
from twisted.web.server import Site
|
||||
from synapse.http.server import JsonResource, RootRedirect
|
||||
from synapse.http.content_repository import ContentRepoResource
|
||||
from synapse.http.client import TwistedHttpClient
|
||||
from synapse.http.server_key_resource import LocalKey
|
||||
from synapse.http.client import MatrixHttpClient
|
||||
from synapse.api.urls import (
|
||||
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
||||
SERVER_KEY_PREFIX,
|
||||
@ -49,7 +49,7 @@ logger = logging.getLogger(__name__)
|
||||
class SynapseHomeServer(HomeServer):
|
||||
|
||||
def build_http_client(self):
|
||||
return TwistedHttpClient(self)
|
||||
return MatrixHttpClient(self)
|
||||
|
||||
def build_resource_for_client(self):
|
||||
return JsonResource()
|
||||
|
@ -123,6 +123,8 @@ class Config(object):
|
||||
# style mode markers into the file, to hint to people that
|
||||
# this is a YAML file.
|
||||
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)
|
||||
|
||||
return cls(args)
|
||||
|
@ -163,7 +163,8 @@ class ReplicationLayer(object):
|
||||
return defer.succeed(None)
|
||||
|
||||
@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
|
||||
and arguments.
|
||||
|
||||
@ -178,7 +179,9 @@ class ReplicationLayer(object):
|
||||
a Deferred which will eventually yield a JSON object from the
|
||||
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
|
||||
@log_function
|
||||
|
@ -195,13 +195,14 @@ class TransportLayer(object):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
@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
|
||||
|
||||
response = yield self.client.get_json(
|
||||
destination=destination,
|
||||
path=path,
|
||||
args=args
|
||||
args=args,
|
||||
retry_on_dns_fail=retry_on_dns_fail,
|
||||
)
|
||||
|
||||
defer.returnValue(response)
|
||||
|
@ -18,7 +18,6 @@ from twisted.internet import defer
|
||||
from ._base import BaseHandler
|
||||
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.http.client import HttpClient
|
||||
from synapse.api.events.room import RoomAliasesEvent
|
||||
|
||||
import logging
|
||||
@ -98,8 +97,8 @@ class DirectoryHandler(BaseHandler):
|
||||
query_type="directory",
|
||||
args={
|
||||
"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:
|
||||
|
@ -17,7 +17,7 @@ from twisted.internet import defer
|
||||
|
||||
from ._base import BaseHandler
|
||||
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
|
||||
import synapse.util.emailutils as emailutils
|
||||
|
||||
@ -97,7 +97,7 @@ class LoginHandler(BaseHandler):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _query_email(self, email):
|
||||
httpCli = PlainHttpClient(self.hs)
|
||||
httpCli = IdentityServerHttpClient(self.hs)
|
||||
data = yield httpCli.get_json(
|
||||
'matrix.org:8090', # TODO FIXME This should be configurable.
|
||||
"/_matrix/identity/api/v1/lookup?medium=email&address=" +
|
||||
|
@ -22,7 +22,8 @@ from synapse.api.errors import (
|
||||
)
|
||||
from ._base import BaseHandler
|
||||
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 bcrypt
|
||||
@ -154,7 +155,9 @@ class RegistrationHandler(BaseHandler):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
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!
|
||||
trustedIdServers = ['matrix.org:8090']
|
||||
if not creds['idServer'] in trustedIdServers:
|
||||
@ -173,7 +176,7 @@ class RegistrationHandler(BaseHandler):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _bind_threepid(self, creds, mxid):
|
||||
httpCli = PlainHttpClient(self.hs)
|
||||
httpCli = IdentityServerHttpClient(self.hs)
|
||||
data = yield httpCli.post_urlencoded_get_json(
|
||||
creds['idServer'],
|
||||
"/_matrix/identity/api/v1/3pid/bind",
|
||||
@ -203,7 +206,9 @@ class RegistrationHandler(BaseHandler):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
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(
|
||||
"www.google.com:80",
|
||||
"/recaptcha/api/verify",
|
||||
|
@ -35,56 +35,6 @@ import urllib
|
||||
|
||||
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):
|
||||
|
||||
@ -109,113 +59,14 @@ class MatrixHttpAgent(_AgentBase):
|
||||
parsed_URI.originForm)
|
||||
|
||||
|
||||
class TwistedHttpClient(HttpClient):
|
||||
""" Wrapper around the twisted HTTP client api.
|
||||
|
||||
Attributes:
|
||||
agent (twisted.web.client.Agent): The twisted Agent used to send the
|
||||
requests.
|
||||
class BaseHttpClient(object):
|
||||
"""Base class for HTTP clients using twisted.
|
||||
"""
|
||||
|
||||
def __init__(self, hs):
|
||||
self.agent = MatrixHttpAgent(reactor)
|
||||
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
|
||||
def _create_request(self, destination, method, path_bytes, param_bytes=b"",
|
||||
query_bytes=b"", producer=None, headers_dict={},
|
||||
@ -239,7 +90,6 @@ class TwistedHttpClient(HttpClient):
|
||||
|
||||
retries_left = 5
|
||||
|
||||
# TODO: setup and pass in an ssl_context to enable TLS
|
||||
endpoint = self._getEndpoint(reactor, destination);
|
||||
|
||||
while True:
|
||||
@ -290,6 +140,85 @@ class TwistedHttpClient(HttpClient):
|
||||
|
||||
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):
|
||||
return matrix_endpoint(
|
||||
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):
|
||||
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):
|
||||
if hasattr(e, "reasons") and e.reasons:
|
||||
|
4
synctl
4
synctl
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SYNAPSE="synapse/app/homeserver.py"
|
||||
SYNAPSE="python -m synapse.app.homeserver"
|
||||
|
||||
CONFIGFILE="homeserver.yaml"
|
||||
PIDFILE="homeserver.pid"
|
||||
@ -14,7 +14,7 @@ case "$1" in
|
||||
start)
|
||||
if [ ! -f "$CONFIGFILE" ]; then
|
||||
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
|
||||
fi
|
||||
|
||||
|
@ -257,7 +257,7 @@ class FederationTestCase(unittest.TestCase):
|
||||
response = yield self.federation.make_query(
|
||||
destination="remote",
|
||||
query_type="a-question",
|
||||
args={"one": "1", "two": "2"}
|
||||
args={"one": "1", "two": "2"},
|
||||
)
|
||||
|
||||
self.assertEquals({"your": "response"}, response)
|
||||
@ -265,7 +265,8 @@ class FederationTestCase(unittest.TestCase):
|
||||
self.mock_http_client.get_json.assert_called_with(
|
||||
destination="remote",
|
||||
path="/_matrix/federation/v1/query/a-question",
|
||||
args={"one": "1", "two": "2"}
|
||||
args={"one": "1", "two": "2"},
|
||||
retry_on_dns_fail=True,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
|
@ -20,7 +20,6 @@ from twisted.internet import defer
|
||||
from mock import Mock
|
||||
|
||||
from synapse.server import HomeServer
|
||||
from synapse.http.client import HttpClient
|
||||
from synapse.handlers.directory import DirectoryHandler
|
||||
from synapse.storage.directory import RoomAliasMapping
|
||||
|
||||
@ -95,8 +94,8 @@ class DirectoryTestCase(unittest.TestCase):
|
||||
query_type="directory",
|
||||
args={
|
||||
"room_alias": "#another:remote",
|
||||
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
|
||||
}
|
||||
},
|
||||
retry_on_dns_fail=False,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
|
Loading…
Reference in New Issue
Block a user