Merge branch develop into server2server_signing

Conflicts:
	synapse/app/homeserver.py
This commit is contained in:
Mark Haines 2014-10-13 10:58:36 +01:00
commit 984e207b59
22 changed files with 911 additions and 527 deletions

View File

@ -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

View File

@ -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?

View File

@ -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
-----

View File

@ -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
==============

View File

@ -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
View 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.

View 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.

View File

@ -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
View 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.

View File

@ -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);

View File

@ -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);

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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=" +

View File

@ -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",

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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