mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2024-10-01 11:49:51 -04:00
Merge branch develop into server2server_signing
Conflicts: synapse/app/homeserver.py
This commit is contained in:
commit
984e207b59
113
README.rst
113
README.rst
@ -4,11 +4,11 @@ Introduction
|
|||||||
Matrix is an ambitious new ecosystem for open federated Instant Messaging and
|
Matrix is an ambitious new ecosystem for open federated Instant Messaging and
|
||||||
VoIP. The basics you need to know to get up and running are:
|
VoIP. The basics you need to know to get up and running are:
|
||||||
|
|
||||||
- Chatrooms are distributed and do not exist on any single server. Rooms
|
- Chatrooms are distributed and do not exist on any single server. Rooms
|
||||||
can be found using aliases like ``#matrix:matrix.org`` or
|
can be found using aliases like ``#matrix:matrix.org`` or
|
||||||
``#test:localhost:8008`` or they can be ephemeral.
|
``#test:localhost:8008`` or they can be ephemeral.
|
||||||
|
|
||||||
- Matrix user IDs look like ``@matthew:matrix.org`` (although in the future
|
- Matrix user IDs look like ``@matthew:matrix.org`` (although in the future
|
||||||
you will normally refer to yourself and others using a 3PID: email
|
you will normally refer to yourself and others using a 3PID: email
|
||||||
address, phone number, etc rather than manipulating Matrix user IDs)
|
address, phone number, etc rather than manipulating Matrix user IDs)
|
||||||
|
|
||||||
@ -20,43 +20,50 @@ The overall architecture is::
|
|||||||
WARNING
|
WARNING
|
||||||
=======
|
=======
|
||||||
|
|
||||||
**Synapse is currently in a state of rapid development, and not all features are yet functional.
|
**Synapse is currently in a state of rapid development, and not all features
|
||||||
Critically, some security features are still in development, which means Synapse can *not*
|
are yet functional. Critically, some security features are still in
|
||||||
be considered secure or reliable at this point.** For instance:
|
development, which means Synapse can *not* be considered secure or reliable at
|
||||||
|
this point.** For instance:
|
||||||
|
|
||||||
- **SSL Certificates used by server-server federation are not yet validated.**
|
- **SSL Certificates used by server-server federation are not yet validated.**
|
||||||
- **Room permissions are not yet enforced on traffic received via federation.**
|
- **Room permissions are not yet enforced on traffic received via federation.**
|
||||||
- **Homeservers do not yet cryptographically sign their events to avoid tampering**
|
- **Homeservers do not yet cryptographically sign their events to avoid
|
||||||
|
tampering**
|
||||||
- Default configuration provides open signup to the service from the internet
|
- Default configuration provides open signup to the service from the internet
|
||||||
|
|
||||||
Despite this, we believe Synapse is more than useful as a way for experimenting and
|
Despite this, we believe Synapse is more than useful as a way for experimenting
|
||||||
exploring Synapse, and the missing features will land shortly. **Until then, please do *NOT*
|
and exploring Synapse, and the missing features will land shortly. **Until
|
||||||
use Synapse for any remotely important or secure communication.**
|
then, please do *NOT* use Synapse for any remotely important or secure
|
||||||
|
communication.**
|
||||||
|
|
||||||
|
|
||||||
Quick Start
|
Quick Start
|
||||||
===========
|
===========
|
||||||
|
|
||||||
System requirements:
|
System requirements:
|
||||||
- POSIX-compliant system (tested on Linux & OSX)
|
- POSIX-compliant system (tested on Linux & OSX)
|
||||||
- Python 2.7
|
- Python 2.7
|
||||||
|
|
||||||
To get up and running:
|
To get up and running:
|
||||||
|
|
||||||
- To simply play with an **existing** homeserver you can
|
- To simply play with an **existing** homeserver you can
|
||||||
just go straight to http://matrix.org/alpha.
|
just go straight to http://matrix.org/alpha.
|
||||||
|
|
||||||
- To run your own **private** homeserver on localhost:8008, install synapse with
|
- To run your own **private** homeserver on localhost:8008, generate a basic
|
||||||
``python setup.py develop --user`` and then run ``./synctl start`` twice (once to
|
config file: ``./synctl start`` will give you instructions on how to do this.
|
||||||
generate a config; once to actually run) - you will find a webclient running at
|
For this purpose, you can use 'localhost' or your hostname as a server name.
|
||||||
http://localhost:8008. Please use a recent Chrome, Safari or Firefox for now...
|
Once you've done so, running ``./synctl start`` again will start your private
|
||||||
|
home sserver. You will find a webclient running at http://localhost:8008.
|
||||||
|
Please use a recent Chrome or Firefox for now (or Safari if you don't need
|
||||||
|
VoIP support).
|
||||||
|
|
||||||
- To run a **public** homeserver and let it exchange messages with other homeservers
|
- To run a **public** homeserver and let it exchange messages with other
|
||||||
and participate in the global Matrix federation, you must expose port 8448 to the
|
homeservers and participate in the global Matrix federation, you must expose
|
||||||
internet and edit homeserver.yaml to specify server_name (the public DNS entry for
|
port 8448 to the internet and edit homeserver.yaml to specify server_name
|
||||||
this server) and then run ``synctl start``. If you changed the server_name, you may
|
(the public DNS entry for this server) and then run ``synctl start``. If you
|
||||||
need to move the old database (homeserver.db) out of the way first. Then come join
|
changed the server_name, you may need to move the old database
|
||||||
``#matrix:matrix.org`` and say hi! :)
|
(homeserver.db) out of the way first. Then come join ``#matrix:matrix.org``
|
||||||
|
and say hi! :)
|
||||||
|
|
||||||
For more detailed setup instructions, please see further down this document.
|
For more detailed setup instructions, please see further down this document.
|
||||||
|
|
||||||
@ -67,21 +74,21 @@ About Matrix
|
|||||||
Matrix specifies a set of pragmatic RESTful HTTP JSON APIs as an open standard,
|
Matrix specifies a set of pragmatic RESTful HTTP JSON APIs as an open standard,
|
||||||
which handle:
|
which handle:
|
||||||
|
|
||||||
- Creating and managing fully distributed chat rooms with no
|
- Creating and managing fully distributed chat rooms with no
|
||||||
single points of control or failure
|
single points of control or failure
|
||||||
- Eventually-consistent cryptographically secure[1] synchronisation of room
|
- Eventually-consistent cryptographically secure[1] synchronisation of room
|
||||||
state across a global open network of federated servers and services
|
state across a global open network of federated servers and services
|
||||||
- Sending and receiving extensible messages in a room with (optional)
|
- Sending and receiving extensible messages in a room with (optional)
|
||||||
end-to-end encryption[2]
|
end-to-end encryption[2]
|
||||||
- Inviting, joining, leaving, kicking, banning room members
|
- Inviting, joining, leaving, kicking, banning room members
|
||||||
- Managing user accounts (registration, login, logout)
|
- Managing user accounts (registration, login, logout)
|
||||||
- Using 3rd Party IDs (3PIDs) such as email addresses, phone numbers,
|
- Using 3rd Party IDs (3PIDs) such as email addresses, phone numbers,
|
||||||
Facebook accounts to authenticate, identify and discover users on Matrix.
|
Facebook accounts to authenticate, identify and discover users on Matrix.
|
||||||
- Placing 1:1 VoIP and Video calls
|
- Placing 1:1 VoIP and Video calls
|
||||||
|
|
||||||
These APIs are intended to be implemented on a wide range of servers, services
|
These APIs are intended to be implemented on a wide range of servers, services
|
||||||
and clients, letting developers build messaging and VoIP functionality on top of
|
and clients, letting developers build messaging and VoIP functionality on top
|
||||||
the entirely open Matrix ecosystem rather than using closed or proprietary
|
of the entirely open Matrix ecosystem rather than using closed or proprietary
|
||||||
solutions. The hope is for Matrix to act as the building blocks for a new
|
solutions. The hope is for Matrix to act as the building blocks for a new
|
||||||
generation of fully open and interoperable messaging and VoIP apps for the
|
generation of fully open and interoperable messaging and VoIP apps for the
|
||||||
internet.
|
internet.
|
||||||
@ -96,17 +103,17 @@ In Matrix, every user runs one or more Matrix clients, which connect through to
|
|||||||
a Matrix homeserver which stores all their personal chat history and user
|
a Matrix homeserver which stores all their personal chat history and user
|
||||||
account information - much as a mail client connects through to an IMAP/SMTP
|
account information - much as a mail client connects through to an IMAP/SMTP
|
||||||
server. Just like email, you can either run your own Matrix homeserver and
|
server. Just like email, you can either run your own Matrix homeserver and
|
||||||
control and own your own communications and history or use one hosted by someone
|
control and own your own communications and history or use one hosted by
|
||||||
else (e.g. matrix.org) - there is no single point of control or mandatory
|
someone else (e.g. matrix.org) - there is no single point of control or
|
||||||
service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
|
mandatory service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
|
||||||
|
|
||||||
Synapse ships with two basic demo Matrix clients: webclient (a basic group chat
|
Synapse ships with two basic demo Matrix clients: webclient (a basic group chat
|
||||||
web client demo implemented in AngularJS) and cmdclient (a basic Python
|
web client demo implemented in AngularJS) and cmdclient (a basic Python
|
||||||
command line utility which lets you easily see what the JSON APIs are up to).
|
command line utility which lets you easily see what the JSON APIs are up to).
|
||||||
|
|
||||||
We'd like to invite you to take a look at the Matrix spec, try to run a
|
We'd like to invite you to take a look at the Matrix spec, try to run a
|
||||||
homeserver, and join the existing Matrix chatrooms already out there, experiment
|
homeserver, and join the existing Matrix chatrooms already out there,
|
||||||
with the APIs and the demo clients, and let us know your thoughts at
|
experiment with the APIs and the demo clients, and let us know your thoughts at
|
||||||
https://github.com/matrix-org/synapse/issues or at matrix@matrix.org.
|
https://github.com/matrix-org/synapse/issues or at matrix@matrix.org.
|
||||||
|
|
||||||
Thanks for trying Matrix!
|
Thanks for trying Matrix!
|
||||||
@ -122,11 +129,11 @@ Homeserver Installation
|
|||||||
First, the dependencies need to be installed. Start by installing
|
First, the dependencies need to be installed. Start by installing
|
||||||
'python2.7-dev' and the various tools of the compiler toolchain.
|
'python2.7-dev' and the various tools of the compiler toolchain.
|
||||||
|
|
||||||
Installing prerequisites on Ubuntu::
|
Installing prerequisites on Ubuntu::
|
||||||
|
|
||||||
$ sudo apt-get install build-essential python2.7-dev libffi-dev
|
$ sudo apt-get install build-essential python2.7-dev libffi-dev
|
||||||
|
|
||||||
Installing prerequisites on Mac OS X::
|
Installing prerequisites on Mac OS X::
|
||||||
|
|
||||||
$ xcode-select --install
|
$ xcode-select --install
|
||||||
|
|
||||||
@ -136,20 +143,20 @@ to install by making setup.py do so, in --user mode::
|
|||||||
$ python setup.py develop --user
|
$ python setup.py develop --user
|
||||||
|
|
||||||
You'll need a version of setuptools new enough to know about git, so you
|
You'll need a version of setuptools new enough to know about git, so you
|
||||||
may need to also run:
|
may need to also run::
|
||||||
|
|
||||||
$ sudo apt-get install python-pip
|
$ sudo apt-get install python-pip
|
||||||
$ sudo pip install --upgrade setuptools
|
$ sudo pip install --upgrade setuptools
|
||||||
|
|
||||||
If you don't have access to github, then you may need to install ``syutil``
|
If you don't have access to github, then you may need to install ``syutil``
|
||||||
manually by checking it out and running ``python setup.py develop --user`` on it
|
manually by checking it out and running ``python setup.py develop --user`` on
|
||||||
too.
|
it too.
|
||||||
|
|
||||||
If you get errors about ``sodium.h`` being missing, you may also need to
|
If you get errors about ``sodium.h`` being missing, you may also need to
|
||||||
manually install a newer PyNaCl via pip as setuptools installs an old one. Or
|
manually install a newer PyNaCl via pip as setuptools installs an old one. Or
|
||||||
you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
|
you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
|
||||||
installing it. Installing PyNaCl using pip may also work (remember to remove any
|
installing it. Installing PyNaCl using pip may also work (remember to remove
|
||||||
other versions installed by setuputils in, for example, ~/.local/lib).
|
any other versions installed by setuputils in, for example, ~/.local/lib).
|
||||||
|
|
||||||
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
||||||
you will need to ``export CFLAGS=-Qunused-arguments``.
|
you will need to ``export CFLAGS=-Qunused-arguments``.
|
||||||
@ -185,11 +192,11 @@ be publicly visible on the internet, and they will need to know its host name.
|
|||||||
You have two choices here, which will influence the form of your Matrix user
|
You have two choices here, which will influence the form of your Matrix user
|
||||||
IDs:
|
IDs:
|
||||||
|
|
||||||
1) Use the machine's own hostname as available on public DNS in the form of its
|
1) Use the machine's own hostname as available on public DNS in the form of
|
||||||
A or AAAA records. This is easier to set up initially, perhaps for testing,
|
its A or AAAA records. This is easier to set up initially, perhaps for
|
||||||
but lacks the flexibility of SRV.
|
testing, but lacks the flexibility of SRV.
|
||||||
|
|
||||||
2) Set up a SRV record for your domain name. This requires you create a SRV
|
2) Set up a SRV record for your domain name. This requires you create a SRV
|
||||||
record in DNS, but gives the flexibility to run the server on your own
|
record in DNS, but gives the flexibility to run the server on your own
|
||||||
choice of TCP port, on a machine that might not be the same name as the
|
choice of TCP port, on a machine that might not be the same name as the
|
||||||
domain name.
|
domain name.
|
||||||
@ -247,7 +254,7 @@ http://localhost:8080. Simply run::
|
|||||||
Running The Demo Web Client
|
Running The Demo Web Client
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
The homeserver runs a web client by default at http://localhost:8080.
|
The homeserver runs a web client by default at https://localhost:8448/.
|
||||||
|
|
||||||
If this is the first time you have used the client from that browser (it uses
|
If this is the first time you have used the client from that browser (it uses
|
||||||
HTML5 local storage to remember its config), you will need to log in to your
|
HTML5 local storage to remember its config), you will need to log in to your
|
||||||
@ -267,8 +274,8 @@ account. Your name will take the form of::
|
|||||||
|
|
||||||
Specify your desired localpart in the topmost box of the "Register for an
|
Specify your desired localpart in the topmost box of the "Register for an
|
||||||
account" form, and click the "Register" button. Hostnames can contain ports if
|
account" form, and click the "Register" button. Hostnames can contain ports if
|
||||||
required due to lack of SRV records (e.g. @matthew:localhost:8080 on an internal
|
required due to lack of SRV records (e.g. @matthew:localhost:8448 on an
|
||||||
synapse sandbox running on localhost)
|
internal synapse sandbox running on localhost)
|
||||||
|
|
||||||
|
|
||||||
Logging In To An Existing Account
|
Logging In To An Existing Account
|
||||||
@ -283,9 +290,9 @@ Identity Servers
|
|||||||
|
|
||||||
The job of authenticating 3PIDs and tracking which 3PIDs are associated with a
|
The job of authenticating 3PIDs and tracking which 3PIDs are associated with a
|
||||||
given Matrix user is very security-sensitive, as there is obvious risk of spam
|
given Matrix user is very security-sensitive, as there is obvious risk of spam
|
||||||
if it is too easy to sign up for Matrix accounts or harvest 3PID data. Meanwhile
|
if it is too easy to sign up for Matrix accounts or harvest 3PID data.
|
||||||
the job of publishing the end-to-end encryption public keys for Matrix users is
|
Meanwhile the job of publishing the end-to-end encryption public keys for
|
||||||
also very security-sensitive for similar reasons.
|
Matrix users is also very security-sensitive for similar reasons.
|
||||||
|
|
||||||
Therefore the role of managing trusted identity in the Matrix ecosystem is
|
Therefore the role of managing trusted identity in the Matrix ecosystem is
|
||||||
farmed out to a cluster of known trusted ecosystem partners, who run 'Matrix
|
farmed out to a cluster of known trusted ecosystem partners, who run 'Matrix
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
Broad-sweeping stuff which would be nice to have
|
|
||||||
================================================
|
|
||||||
|
|
||||||
- Additional SQL backends beyond sqlite
|
|
||||||
- homeserver implementation in go
|
|
||||||
- homeserver implementation in node.js
|
|
||||||
- client SDKs
|
|
||||||
- libpurple library
|
|
||||||
- irssi plugin?
|
|
@ -31,7 +31,7 @@ Registration
|
|||||||
The aim of registration is to get a user ID and access token which you will need
|
The aim of registration is to get a user ID and access token which you will need
|
||||||
when accessing other APIs::
|
when accessing other APIs::
|
||||||
|
|
||||||
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/register"
|
curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register"
|
||||||
|
|
||||||
{
|
{
|
||||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
||||||
@ -39,14 +39,15 @@ when accessing other APIs::
|
|||||||
"user_id": "@example:localhost"
|
"user_id": "@example:localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
NB: If a ``user_id`` is not specified, one will be randomly generated for you.
|
NB: If a ``user`` is not specified, one will be randomly generated for you.
|
||||||
If you do not specify a ``password``, you will be unable to login to the account
|
If you do not specify a ``password``, you will be unable to login to the account
|
||||||
if you forget the ``access_token``.
|
if you forget the ``access_token``.
|
||||||
|
|
||||||
Implementation note: The matrix specification does not enforce how users
|
Implementation note: The matrix specification does not enforce how users
|
||||||
register with a server. It just specifies the URL path and absolute minimum
|
register with a server. It just specifies the URL path and absolute minimum
|
||||||
keys. The reference home server uses a username/password to authenticate user,
|
keys. The reference home server uses a username/password to authenticate user,
|
||||||
but other home servers may use different methods.
|
but other home servers may use different methods. This is why you need to
|
||||||
|
specify the ``type`` of method.
|
||||||
|
|
||||||
Login
|
Login
|
||||||
-----
|
-----
|
||||||
|
@ -1,103 +1,3 @@
|
|||||||
========
|
|
||||||
Presence
|
|
||||||
========
|
|
||||||
|
|
||||||
A description of presence information and visibility between users.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Each user has the concept of Presence information. This encodes a sense of the
|
|
||||||
"availability" of that user, suitable for display on other user's clients.
|
|
||||||
|
|
||||||
|
|
||||||
Presence Information
|
|
||||||
====================
|
|
||||||
|
|
||||||
The basic piece of presence information is an enumeration of a small set of
|
|
||||||
state; such as "free to chat", "online", "busy", or "offline". The default state
|
|
||||||
unless the user changes it is "online". Lower states suggest some amount of
|
|
||||||
decreased availability from normal, which might have some client-side effect
|
|
||||||
like muting notification sounds and suggests to other users not to bother them
|
|
||||||
unless it is urgent. Equally, the "free to chat" state exists to let the user
|
|
||||||
announce their general willingness to receive messages moreso than default.
|
|
||||||
|
|
||||||
Home servers should also allow a user to set their state as "hidden" - a state
|
|
||||||
which behaves as offline, but allows the user to see the client state anyway and
|
|
||||||
generally interact with client features such as reading message history or
|
|
||||||
accessing contacts in the address book.
|
|
||||||
|
|
||||||
This basic state field applies to the user as a whole, regardless of how many
|
|
||||||
client devices they have connected. The home server should synchronise this
|
|
||||||
status choice among multiple devices to ensure the user gets a consistent
|
|
||||||
experience.
|
|
||||||
|
|
||||||
Idle Time
|
|
||||||
---------
|
|
||||||
|
|
||||||
As well as the basic state field, the presence information can also show a sense
|
|
||||||
of an "idle timer". This should be maintained individually by the user's
|
|
||||||
clients, and the homeserver can take the highest reported time as that to
|
|
||||||
report. Likely this should be presented in fairly coarse granularity; possibly
|
|
||||||
being limited to letting the home server automatically switch from a "free to
|
|
||||||
chat" or "online" mode into "idle".
|
|
||||||
|
|
||||||
When a user is offline, the Home Server can still report when the user was last
|
|
||||||
seen online, again perhaps in a somewhat coarse manner.
|
|
||||||
|
|
||||||
Device Type
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Client devices that may limit the user experience somewhat (such as "mobile"
|
|
||||||
devices with limited ability to type on a real keyboard or read large amounts of
|
|
||||||
text) should report this to the home server, as this is also useful information
|
|
||||||
to report as "presence" if the user cannot be expected to provide a good typed
|
|
||||||
response to messages.
|
|
||||||
|
|
||||||
|
|
||||||
Presence List
|
|
||||||
=============
|
|
||||||
|
|
||||||
Each user's home server stores a "presence list" for that user. This stores a
|
|
||||||
list of other user IDs the user has chosen to add to it (remembering any ACL
|
|
||||||
Pointer if appropriate).
|
|
||||||
|
|
||||||
To be added to a contact list, the user being added must grant permission. Once
|
|
||||||
granted, both user's HS(es) store this information, as it allows the user who
|
|
||||||
has added the contact some more abilities; see below. Since such subscriptions
|
|
||||||
are likely to be bidirectional, HSes may wish to automatically accept requests
|
|
||||||
when a reverse subscription already exists.
|
|
||||||
|
|
||||||
As a convenience, presence lists should support the ability to collect users
|
|
||||||
into groups, which could allow things like inviting the entire group to a new
|
|
||||||
("ad-hoc") chat room, or easy interaction with the profile information ACL
|
|
||||||
implementation of the HS.
|
|
||||||
|
|
||||||
|
|
||||||
Presence and Permissions
|
|
||||||
========================
|
|
||||||
|
|
||||||
For a viewing user to be allowed to see the presence information of a target
|
|
||||||
user, either
|
|
||||||
|
|
||||||
* The target user has allowed the viewing user to add them to their presence
|
|
||||||
list, or
|
|
||||||
|
|
||||||
* The two users share at least one room in common
|
|
||||||
|
|
||||||
In the latter case, this allows for clients to display some minimal sense of
|
|
||||||
presence information in a user list for a room.
|
|
||||||
|
|
||||||
Home servers can also use the user's choice of presence state as a signal for
|
|
||||||
how to handle new private one-to-one chat message requests. For example, it
|
|
||||||
might decide:
|
|
||||||
|
|
||||||
"free to chat": accept anything
|
|
||||||
"online": accept from anyone in my addres book list
|
|
||||||
"busy": accept from anyone in this "important people" group in my address
|
|
||||||
book list
|
|
||||||
|
|
||||||
|
|
||||||
API Efficiency
|
API Efficiency
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -48,6 +48,22 @@
|
|||||||
"paramType": "body"
|
"paramType": "body"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "DELETE",
|
||||||
|
"summary": "Removes a mapping of room alias to room ID.",
|
||||||
|
"notes": "Only privileged users can perform this action.",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "remove_room_alias",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomAlias",
|
||||||
|
"description": "The room alias to remove.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
53
docs/definitions.rst
Normal file
53
docs/definitions.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
Definitions
|
||||||
|
===========
|
||||||
|
|
||||||
|
# *Event* -- A JSON object that represents a piece of information to be
|
||||||
|
distributed to the the room. The object includes a payload and metadata,
|
||||||
|
including a `type` used to indicate what the payload is for and how to process
|
||||||
|
them. It also includes one or more references to previous events.
|
||||||
|
|
||||||
|
# *Event graph* -- Events and their references to previous events form a
|
||||||
|
directed acyclic graph. All events must be a descendant of the first event in a
|
||||||
|
room, except for a few special circumstances.
|
||||||
|
|
||||||
|
# *State event* -- A state event is an event that has a non-null string valued
|
||||||
|
`state_key` field. It may also include a `prev_state` key referencing exactly
|
||||||
|
one state event with the same type and state key, in the same event graph.
|
||||||
|
|
||||||
|
# *State tree* -- A state tree is a tree formed by a collection of state events
|
||||||
|
that have the same type and state key (all in the same event graph.
|
||||||
|
|
||||||
|
# *State resolution algorithm* -- An algorithm that takes a state tree as input
|
||||||
|
and selects a single leaf node.
|
||||||
|
|
||||||
|
# *Current state event* -- The leaf node of a given state tree that has been
|
||||||
|
selected by the state resolution algorithm.
|
||||||
|
|
||||||
|
# *Room state* / *state dictionary* / *current state* -- A mapping of the pair
|
||||||
|
(event type, state key) to the current state event for that pair.
|
||||||
|
|
||||||
|
# *Room* -- An event graph and its associated state dictionary. An event is in
|
||||||
|
the room if it is part of the event graph.
|
||||||
|
|
||||||
|
# *Topological ordering* -- The partial ordering that can be extracted from the
|
||||||
|
event graph due to it being a DAG.
|
||||||
|
|
||||||
|
(The state definitions are purposely slightly ill-defined, since if we allow
|
||||||
|
deleting events we might end up with multiple state trees for a given event
|
||||||
|
type and state key pair.)
|
||||||
|
|
||||||
|
Federation specific
|
||||||
|
-------------------
|
||||||
|
# *(Persistent data unit) PDU* -- An encoding of an event for distribution of
|
||||||
|
the server to server protocol.
|
||||||
|
|
||||||
|
# *(Ephemeral data unit) EDU* -- A piece of information that is sent between
|
||||||
|
servers and doesn't encode an event.
|
||||||
|
|
||||||
|
Client specific
|
||||||
|
---------------
|
||||||
|
# *Child events* -- Events that reference a single event in the same room
|
||||||
|
independently of the event graph.
|
||||||
|
|
||||||
|
# *Collapsed events* -- Events that have all child events that reference it
|
||||||
|
included in the JSON object.
|
30
docs/specification-NOTHAVE.rst
Normal file
30
docs/specification-NOTHAVE.rst
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
Matrix Specification NOTHAVEs
|
||||||
|
=============================
|
||||||
|
|
||||||
|
This document contains sections of the main specification that have been
|
||||||
|
temporarily removed, because they specify intentions or aspirations that have
|
||||||
|
in no way yet been implemented. Rather than outright-deleting them, they have
|
||||||
|
been moved here so as to stand as an initial version for such time as they
|
||||||
|
become extant.
|
||||||
|
|
||||||
|
|
||||||
|
Presence
|
||||||
|
========
|
||||||
|
|
||||||
|
Idle Time
|
||||||
|
---------
|
||||||
|
As well as the basic ``presence`` field, the presence information can also show
|
||||||
|
a sense of an "idle timer". This should be maintained individually by the
|
||||||
|
user's clients, and the home server can take the highest reported time as that
|
||||||
|
to report. When a user is offline, the home server can still report when the
|
||||||
|
user was last seen online.
|
||||||
|
|
||||||
|
Device Type
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Client devices that may limit the user experience somewhat (such as "mobile"
|
||||||
|
devices with limited ability to type on a real keyboard or read large amounts of
|
||||||
|
text) should report this to the home server, as this is also useful information
|
||||||
|
to report as "presence" if the user cannot be expected to provide a good typed
|
||||||
|
response to messages.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
51
docs/state_resolution.rst
Normal file
51
docs/state_resolution.rst
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
State Resolution
|
||||||
|
================
|
||||||
|
This section describes why we need state resolution and how it works.
|
||||||
|
|
||||||
|
|
||||||
|
Motivation
|
||||||
|
-----------
|
||||||
|
We want to be able to associate some shared state with rooms, e.g. a room name
|
||||||
|
or members list. This is done by having a current state dictionary that maps
|
||||||
|
from the pair event type and state key to an event.
|
||||||
|
|
||||||
|
However, since the servers involved in the room are distributed we need to be
|
||||||
|
able to handle the case when two (or more) servers try and update the state at
|
||||||
|
the same time. This is done via the state resolution algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
State Tree
|
||||||
|
------------
|
||||||
|
State events contain a reference to the state it is trying to replace. These
|
||||||
|
relations form a tree where the current state is one of the leaf nodes.
|
||||||
|
|
||||||
|
Note that state events are events, and so are part of the PDU graph. Thus we
|
||||||
|
can be sure that (modulo the internet being particularly broken) we will see
|
||||||
|
all state events eventually.
|
||||||
|
|
||||||
|
|
||||||
|
Algorithm requirements
|
||||||
|
----------------------
|
||||||
|
We want the algorithm to have the following properties:
|
||||||
|
- Since we aren't guaranteed what order we receive state events in, except that
|
||||||
|
we see parents before children, the state resolution algorithm must not depend
|
||||||
|
on the order and must always come to the same result.
|
||||||
|
- If we receive a state event whose parent is the current state, then the
|
||||||
|
algorithm will select it.
|
||||||
|
- The algorithm does not depend on internal state, ensuring all servers should
|
||||||
|
come to the same decision.
|
||||||
|
|
||||||
|
These three properties mean it is enough to keep track of the current state and
|
||||||
|
compare it with any new proposed state, rather than having to keep track of all
|
||||||
|
the leafs of the tree and recomputing across the entire state tree.
|
||||||
|
|
||||||
|
|
||||||
|
Current Implementation
|
||||||
|
----------------------
|
||||||
|
The current implementation works as follows: Upon receipt of a newly proposed
|
||||||
|
state change we first find the common ancestor. Then we take the maximum
|
||||||
|
across each branch of the users' power levels, if one is higher then it is
|
||||||
|
selected as the current state. Otherwise, we check if one chain is longer than
|
||||||
|
the other, if so we choose that one. If that also fails, then we concatenate
|
||||||
|
all the pdu ids and take a SHA1 hash and compare them to select a common
|
||||||
|
ancestor.
|
@ -110,7 +110,7 @@ $('.register').live('click', function() {
|
|||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user_id: user, password: password }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
onLoggedIn(data);
|
onLoggedIn(data);
|
||||||
|
@ -14,7 +14,7 @@ $('.register').live('click', function() {
|
|||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user_id: user, password: password }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
showLoggedIn(data);
|
showLoggedIn(data);
|
||||||
|
@ -25,8 +25,8 @@ from twisted.web.static import File
|
|||||||
from twisted.web.server import Site
|
from twisted.web.server import Site
|
||||||
from synapse.http.server import JsonResource, RootRedirect
|
from synapse.http.server import JsonResource, RootRedirect
|
||||||
from synapse.http.content_repository import ContentRepoResource
|
from synapse.http.content_repository import ContentRepoResource
|
||||||
from synapse.http.client import TwistedHttpClient
|
|
||||||
from synapse.http.server_key_resource import LocalKey
|
from synapse.http.server_key_resource import LocalKey
|
||||||
|
from synapse.http.client import MatrixHttpClient
|
||||||
from synapse.api.urls import (
|
from synapse.api.urls import (
|
||||||
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
||||||
SERVER_KEY_PREFIX,
|
SERVER_KEY_PREFIX,
|
||||||
@ -49,7 +49,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class SynapseHomeServer(HomeServer):
|
class SynapseHomeServer(HomeServer):
|
||||||
|
|
||||||
def build_http_client(self):
|
def build_http_client(self):
|
||||||
return TwistedHttpClient(self)
|
return MatrixHttpClient(self)
|
||||||
|
|
||||||
def build_resource_for_client(self):
|
def build_resource_for_client(self):
|
||||||
return JsonResource()
|
return JsonResource()
|
||||||
|
@ -123,6 +123,8 @@ class Config(object):
|
|||||||
# style mode markers into the file, to hint to people that
|
# style mode markers into the file, to hint to people that
|
||||||
# this is a YAML file.
|
# this is a YAML file.
|
||||||
yaml.dump(config, config_file, default_flow_style=False)
|
yaml.dump(config, config_file, default_flow_style=False)
|
||||||
|
print "A config file has been generated in %s for server name '%s') with corresponding SSL keys and self-signed certificates. Please review this file and customise it to your needs." % (config_args.config_path, config['server_name'])
|
||||||
|
print "If this server name is incorrect, you will need to regenerate the SSL certificates"
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
return cls(args)
|
return cls(args)
|
||||||
|
@ -163,7 +163,8 @@ class ReplicationLayer(object):
|
|||||||
return defer.succeed(None)
|
return defer.succeed(None)
|
||||||
|
|
||||||
@log_function
|
@log_function
|
||||||
def make_query(self, destination, query_type, args):
|
def make_query(self, destination, query_type, args,
|
||||||
|
retry_on_dns_fail=True):
|
||||||
"""Sends a federation Query to a remote homeserver of the given type
|
"""Sends a federation Query to a remote homeserver of the given type
|
||||||
and arguments.
|
and arguments.
|
||||||
|
|
||||||
@ -178,7 +179,9 @@ class ReplicationLayer(object):
|
|||||||
a Deferred which will eventually yield a JSON object from the
|
a Deferred which will eventually yield a JSON object from the
|
||||||
response
|
response
|
||||||
"""
|
"""
|
||||||
return self.transport_layer.make_query(destination, query_type, args)
|
return self.transport_layer.make_query(
|
||||||
|
destination, query_type, args, retry_on_dns_fail=retry_on_dns_fail
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
|
@ -195,13 +195,14 @@ class TransportLayer(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def make_query(self, destination, query_type, args):
|
def make_query(self, destination, query_type, args, retry_on_dns_fail):
|
||||||
path = PREFIX + "/query/%s" % query_type
|
path = PREFIX + "/query/%s" % query_type
|
||||||
|
|
||||||
response = yield self.client.get_json(
|
response = yield self.client.get_json(
|
||||||
destination=destination,
|
destination=destination,
|
||||||
path=path,
|
path=path,
|
||||||
args=args
|
args=args,
|
||||||
|
retry_on_dns_fail=retry_on_dns_fail,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
@ -18,7 +18,6 @@ from twisted.internet import defer
|
|||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.http.client import HttpClient
|
|
||||||
from synapse.api.events.room import RoomAliasesEvent
|
from synapse.api.events.room import RoomAliasesEvent
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -98,8 +97,8 @@ class DirectoryHandler(BaseHandler):
|
|||||||
query_type="directory",
|
query_type="directory",
|
||||||
args={
|
args={
|
||||||
"room_alias": room_alias.to_string(),
|
"room_alias": room_alias.to_string(),
|
||||||
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
|
},
|
||||||
}
|
retry_on_dns_fail=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result and "room_id" in result and "servers" in result:
|
if result and "room_id" in result and "servers" in result:
|
||||||
|
@ -17,7 +17,7 @@ from twisted.internet import defer
|
|||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
from synapse.api.errors import LoginError, Codes
|
from synapse.api.errors import LoginError, Codes
|
||||||
from synapse.http.client import PlainHttpClient
|
from synapse.http.client import IdentityServerHttpClient
|
||||||
from synapse.util.emailutils import EmailException
|
from synapse.util.emailutils import EmailException
|
||||||
import synapse.util.emailutils as emailutils
|
import synapse.util.emailutils as emailutils
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ class LoginHandler(BaseHandler):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _query_email(self, email):
|
def _query_email(self, email):
|
||||||
httpCli = PlainHttpClient(self.hs)
|
httpCli = IdentityServerHttpClient(self.hs)
|
||||||
data = yield httpCli.get_json(
|
data = yield httpCli.get_json(
|
||||||
'matrix.org:8090', # TODO FIXME This should be configurable.
|
'matrix.org:8090', # TODO FIXME This should be configurable.
|
||||||
"/_matrix/identity/api/v1/lookup?medium=email&address=" +
|
"/_matrix/identity/api/v1/lookup?medium=email&address=" +
|
||||||
|
@ -22,7 +22,8 @@ from synapse.api.errors import (
|
|||||||
)
|
)
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
import synapse.util.stringutils as stringutils
|
import synapse.util.stringutils as stringutils
|
||||||
from synapse.http.client import PlainHttpClient
|
from synapse.http.client import IdentityServerHttpClient
|
||||||
|
from synapse.http.client import CaptchaServerHttpClient
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import bcrypt
|
import bcrypt
|
||||||
@ -154,7 +155,9 @@ class RegistrationHandler(BaseHandler):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _threepid_from_creds(self, creds):
|
def _threepid_from_creds(self, creds):
|
||||||
httpCli = PlainHttpClient(self.hs)
|
# TODO: get this from the homeserver rather than creating a new one for
|
||||||
|
# each request
|
||||||
|
httpCli = IdentityServerHttpClient(self.hs)
|
||||||
# XXX: make this configurable!
|
# XXX: make this configurable!
|
||||||
trustedIdServers = ['matrix.org:8090']
|
trustedIdServers = ['matrix.org:8090']
|
||||||
if not creds['idServer'] in trustedIdServers:
|
if not creds['idServer'] in trustedIdServers:
|
||||||
@ -173,7 +176,7 @@ class RegistrationHandler(BaseHandler):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _bind_threepid(self, creds, mxid):
|
def _bind_threepid(self, creds, mxid):
|
||||||
httpCli = PlainHttpClient(self.hs)
|
httpCli = IdentityServerHttpClient(self.hs)
|
||||||
data = yield httpCli.post_urlencoded_get_json(
|
data = yield httpCli.post_urlencoded_get_json(
|
||||||
creds['idServer'],
|
creds['idServer'],
|
||||||
"/_matrix/identity/api/v1/3pid/bind",
|
"/_matrix/identity/api/v1/3pid/bind",
|
||||||
@ -203,7 +206,9 @@ class RegistrationHandler(BaseHandler):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _submit_captcha(self, ip_addr, private_key, challenge, response):
|
def _submit_captcha(self, ip_addr, private_key, challenge, response):
|
||||||
client = PlainHttpClient(self.hs)
|
# TODO: get this from the homeserver rather than creating a new one for
|
||||||
|
# each request
|
||||||
|
client = CaptchaServerHttpClient(self.hs)
|
||||||
data = yield client.post_urlencoded_get_raw(
|
data = yield client.post_urlencoded_get_raw(
|
||||||
"www.google.com:80",
|
"www.google.com:80",
|
||||||
"/recaptcha/api/verify",
|
"/recaptcha/api/verify",
|
||||||
|
@ -35,56 +35,6 @@ import urllib
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# FIXME: SURELY these should be killed?!
|
|
||||||
_destination_mappings = {
|
|
||||||
"red": "localhost:8080",
|
|
||||||
"blue": "localhost:8081",
|
|
||||||
"green": "localhost:8082",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class HttpClient(object):
|
|
||||||
""" Interface for talking json over http
|
|
||||||
"""
|
|
||||||
RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
|
|
||||||
|
|
||||||
def put_json(self, destination, path, data):
|
|
||||||
""" Sends the specifed json data using PUT
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): The remote server to send the HTTP request
|
|
||||||
to.
|
|
||||||
path (str): The HTTP path.
|
|
||||||
data (dict): A dict containing the data that will be used as
|
|
||||||
the request body. This will be encoded as JSON.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Succeeds when we get a 2xx HTTP response. The result
|
|
||||||
will be the decoded JSON body. On a 4xx or 5xx error response a
|
|
||||||
CodeMessageException is raised.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_json(self, destination, path, args=None):
|
|
||||||
""" Get's some json from the given host homeserver and path
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): The remote server to send the HTTP request
|
|
||||||
to.
|
|
||||||
path (str): The HTTP path.
|
|
||||||
args (dict): A dictionary used to create query strings, defaults to
|
|
||||||
None.
|
|
||||||
**Note**: The value of each key is assumed to be an iterable
|
|
||||||
and *not* a string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Succeeds when we get *any* HTTP response.
|
|
||||||
|
|
||||||
The result of the deferred is a tuple of `(code, response)`,
|
|
||||||
where `response` is a dict representing the decoded JSON body.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MatrixHttpAgent(_AgentBase):
|
class MatrixHttpAgent(_AgentBase):
|
||||||
|
|
||||||
@ -109,113 +59,14 @@ class MatrixHttpAgent(_AgentBase):
|
|||||||
parsed_URI.originForm)
|
parsed_URI.originForm)
|
||||||
|
|
||||||
|
|
||||||
class TwistedHttpClient(HttpClient):
|
class BaseHttpClient(object):
|
||||||
""" Wrapper around the twisted HTTP client api.
|
"""Base class for HTTP clients using twisted.
|
||||||
|
|
||||||
Attributes:
|
|
||||||
agent (twisted.web.client.Agent): The twisted Agent used to send the
|
|
||||||
requests.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.agent = MatrixHttpAgent(reactor)
|
self.agent = MatrixHttpAgent(reactor)
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def put_json(self, destination, path, data, on_send_callback=None):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"PUT",
|
|
||||||
path.encode("ascii"),
|
|
||||||
producer=_JsonProducer(data),
|
|
||||||
headers_dict={"Content-Type": ["application/json"]},
|
|
||||||
on_send_callback=on_send_callback,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("Getting resp body")
|
|
||||||
body = yield readBody(response)
|
|
||||||
logger.debug("Got resp body")
|
|
||||||
|
|
||||||
defer.returnValue((response.code, body))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def get_json(self, destination, path, args={}):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
logger.debug("get_json args: %s", args)
|
|
||||||
|
|
||||||
retry_on_dns_fail = True
|
|
||||||
if HttpClient.RETRY_DNS_LOOKUP_FAILURES in args:
|
|
||||||
# FIXME: This isn't ideal, but the interface exposed in get_json
|
|
||||||
# isn't comprehensive enough to give caller's any control over
|
|
||||||
# their connection mechanics.
|
|
||||||
retry_on_dns_fail = args.pop(HttpClient.RETRY_DNS_LOOKUP_FAILURES)
|
|
||||||
|
|
||||||
query_bytes = urllib.urlencode(args, True)
|
|
||||||
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"GET",
|
|
||||||
path.encode("ascii"),
|
|
||||||
query_bytes=query_bytes,
|
|
||||||
retry_on_dns_fail=retry_on_dns_fail
|
|
||||||
)
|
|
||||||
|
|
||||||
body = yield readBody(response)
|
|
||||||
|
|
||||||
defer.returnValue(json.loads(body))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def post_urlencoded_get_json(self, destination, path, args={}):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
logger.debug("post_urlencoded_get_json args: %s", args)
|
|
||||||
query_bytes = urllib.urlencode(args, True)
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"POST",
|
|
||||||
path.encode("ascii"),
|
|
||||||
producer=FileBodyProducer(StringIO(urllib.urlencode(args))),
|
|
||||||
headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
body = yield readBody(response)
|
|
||||||
|
|
||||||
defer.returnValue(json.loads(body))
|
|
||||||
|
|
||||||
# XXX FIXME : I'm so sorry.
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def post_urlencoded_get_raw(self, destination, path, accept_partial=False, args={}):
|
|
||||||
if destination in _destination_mappings:
|
|
||||||
destination = _destination_mappings[destination]
|
|
||||||
|
|
||||||
query_bytes = urllib.urlencode(args, True)
|
|
||||||
|
|
||||||
response = yield self._create_request(
|
|
||||||
destination.encode("ascii"),
|
|
||||||
"POST",
|
|
||||||
path.encode("ascii"),
|
|
||||||
producer=FileBodyProducer(StringIO(urllib.urlencode(args))),
|
|
||||||
headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
body = yield readBody(response)
|
|
||||||
defer.returnValue(body)
|
|
||||||
except PartialDownloadError as e:
|
|
||||||
if accept_partial:
|
|
||||||
defer.returnValue(e.response)
|
|
||||||
else:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _create_request(self, destination, method, path_bytes, param_bytes=b"",
|
def _create_request(self, destination, method, path_bytes, param_bytes=b"",
|
||||||
query_bytes=b"", producer=None, headers_dict={},
|
query_bytes=b"", producer=None, headers_dict={},
|
||||||
@ -239,7 +90,6 @@ class TwistedHttpClient(HttpClient):
|
|||||||
|
|
||||||
retries_left = 5
|
retries_left = 5
|
||||||
|
|
||||||
# TODO: setup and pass in an ssl_context to enable TLS
|
|
||||||
endpoint = self._getEndpoint(reactor, destination);
|
endpoint = self._getEndpoint(reactor, destination);
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -290,6 +140,85 @@ class TwistedHttpClient(HttpClient):
|
|||||||
|
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixHttpClient(BaseHttpClient):
|
||||||
|
""" Wrapper around the twisted HTTP client api. Implements
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
agent (twisted.web.client.Agent): The twisted Agent used to send the
|
||||||
|
requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def put_json(self, destination, path, data, on_send_callback=None):
|
||||||
|
""" Sends the specifed json data using PUT
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): The remote server to send the HTTP request
|
||||||
|
to.
|
||||||
|
path (str): The HTTP path.
|
||||||
|
data (dict): A dict containing the data that will be used as
|
||||||
|
the request body. This will be encoded as JSON.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Succeeds when we get a 2xx HTTP response. The result
|
||||||
|
will be the decoded JSON body. On a 4xx or 5xx error response a
|
||||||
|
CodeMessageException is raised.
|
||||||
|
"""
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"PUT",
|
||||||
|
path.encode("ascii"),
|
||||||
|
producer=_JsonProducer(data),
|
||||||
|
headers_dict={"Content-Type": ["application/json"]},
|
||||||
|
on_send_callback=on_send_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug("Getting resp body")
|
||||||
|
body = yield readBody(response)
|
||||||
|
logger.debug("Got resp body")
|
||||||
|
|
||||||
|
defer.returnValue((response.code, body))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_json(self, destination, path, args={}, retry_on_dns_fail=True):
|
||||||
|
""" Get's some json from the given host homeserver and path
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): The remote server to send the HTTP request
|
||||||
|
to.
|
||||||
|
path (str): The HTTP path.
|
||||||
|
args (dict): A dictionary used to create query strings, defaults to
|
||||||
|
None.
|
||||||
|
**Note**: The value of each key is assumed to be an iterable
|
||||||
|
and *not* a string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Succeeds when we get *any* HTTP response.
|
||||||
|
|
||||||
|
The result of the deferred is a tuple of `(code, response)`,
|
||||||
|
where `response` is a dict representing the decoded JSON body.
|
||||||
|
"""
|
||||||
|
logger.debug("get_json args: %s", args)
|
||||||
|
|
||||||
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
|
||||||
|
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"GET",
|
||||||
|
path.encode("ascii"),
|
||||||
|
query_bytes=query_bytes,
|
||||||
|
retry_on_dns_fail=retry_on_dns_fail
|
||||||
|
)
|
||||||
|
|
||||||
|
body = yield readBody(response)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(body))
|
||||||
|
|
||||||
|
|
||||||
def _getEndpoint(self, reactor, destination):
|
def _getEndpoint(self, reactor, destination):
|
||||||
return matrix_endpoint(
|
return matrix_endpoint(
|
||||||
reactor, destination, timeout=10,
|
reactor, destination, timeout=10,
|
||||||
@ -297,10 +226,63 @@ class TwistedHttpClient(HttpClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PlainHttpClient(TwistedHttpClient):
|
class IdentityServerHttpClient(BaseHttpClient):
|
||||||
|
"""Separate HTTP client for talking to the Identity servers since they
|
||||||
|
don't use SRV records and talk x-www-form-urlencoded rather than JSON.
|
||||||
|
"""
|
||||||
|
def _getEndpoint(self, reactor, destination):
|
||||||
|
#TODO: This should be talking TLS
|
||||||
|
return matrix_endpoint(reactor, destination, timeout=10)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def post_urlencoded_get_json(self, destination, path, args={}):
|
||||||
|
logger.debug("post_urlencoded_get_json args: %s", args)
|
||||||
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"POST",
|
||||||
|
path.encode("ascii"),
|
||||||
|
producer=FileBodyProducer(StringIO(query_bytes)),
|
||||||
|
headers_dict={
|
||||||
|
"Content-Type": ["application/x-www-form-urlencoded"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
body = yield readBody(response)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(body))
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaServerHttpClient(MatrixHttpClient):
|
||||||
|
"""Separate HTTP client for talking to google's captcha servers"""
|
||||||
|
|
||||||
def _getEndpoint(self, reactor, destination):
|
def _getEndpoint(self, reactor, destination):
|
||||||
return matrix_endpoint(reactor, destination, timeout=10)
|
return matrix_endpoint(reactor, destination, timeout=10)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def post_urlencoded_get_raw(self, destination, path, accept_partial=False,
|
||||||
|
args={}):
|
||||||
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
|
||||||
|
response = yield self._create_request(
|
||||||
|
destination.encode("ascii"),
|
||||||
|
"POST",
|
||||||
|
path.encode("ascii"),
|
||||||
|
producer=FileBodyProducer(StringIO(query_bytes)),
|
||||||
|
headers_dict={
|
||||||
|
"Content-Type": ["application/x-www-form-urlencoded"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
body = yield readBody(response)
|
||||||
|
defer.returnValue(body)
|
||||||
|
except PartialDownloadError as e:
|
||||||
|
if accept_partial:
|
||||||
|
defer.returnValue(e.response)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
def _print_ex(e):
|
def _print_ex(e):
|
||||||
if hasattr(e, "reasons") and e.reasons:
|
if hasattr(e, "reasons") and e.reasons:
|
||||||
|
4
synctl
4
synctl
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SYNAPSE="synapse/app/homeserver.py"
|
SYNAPSE="python -m synapse.app.homeserver"
|
||||||
|
|
||||||
CONFIGFILE="homeserver.yaml"
|
CONFIGFILE="homeserver.yaml"
|
||||||
PIDFILE="homeserver.pid"
|
PIDFILE="homeserver.pid"
|
||||||
@ -14,7 +14,7 @@ case "$1" in
|
|||||||
start)
|
start)
|
||||||
if [ ! -f "$CONFIGFILE" ]; then
|
if [ ! -f "$CONFIGFILE" ]; then
|
||||||
echo "No config file found"
|
echo "No config file found"
|
||||||
echo "To generate a config file, run 'python --generate-config'"
|
echo "To generate a config file, run '$SYNAPSE -c $CONFIGFILE --generate-config --server-name=<server name>'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
response = yield self.federation.make_query(
|
response = yield self.federation.make_query(
|
||||||
destination="remote",
|
destination="remote",
|
||||||
query_type="a-question",
|
query_type="a-question",
|
||||||
args={"one": "1", "two": "2"}
|
args={"one": "1", "two": "2"},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals({"your": "response"}, response)
|
self.assertEquals({"your": "response"}, response)
|
||||||
@ -265,7 +265,8 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
self.mock_http_client.get_json.assert_called_with(
|
self.mock_http_client.get_json.assert_called_with(
|
||||||
destination="remote",
|
destination="remote",
|
||||||
path="/_matrix/federation/v1/query/a-question",
|
path="/_matrix/federation/v1/query/a-question",
|
||||||
args={"one": "1", "two": "2"}
|
args={"one": "1", "two": "2"},
|
||||||
|
retry_on_dns_fail=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -20,7 +20,6 @@ from twisted.internet import defer
|
|||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.http.client import HttpClient
|
|
||||||
from synapse.handlers.directory import DirectoryHandler
|
from synapse.handlers.directory import DirectoryHandler
|
||||||
from synapse.storage.directory import RoomAliasMapping
|
from synapse.storage.directory import RoomAliasMapping
|
||||||
|
|
||||||
@ -95,8 +94,8 @@ class DirectoryTestCase(unittest.TestCase):
|
|||||||
query_type="directory",
|
query_type="directory",
|
||||||
args={
|
args={
|
||||||
"room_alias": "#another:remote",
|
"room_alias": "#another:remote",
|
||||||
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
|
},
|
||||||
}
|
retry_on_dns_fail=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
Loading…
Reference in New Issue
Block a user