Merge branch 'release-v0.2.1'
This commit is contained in:
commit
0538a4098d
17
CHANGES.rst
17
CHANGES.rst
|
@ -1,3 +1,20 @@
|
||||||
|
Changes in synapse 0.2.1 (2014-09-03)
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
Homeserver:
|
||||||
|
* Added support for signing up with a third party id.
|
||||||
|
* Add synctl scripts.
|
||||||
|
* Added rate limiting.
|
||||||
|
* Add option to change the external address the content repo uses.
|
||||||
|
* Presence bug fixes.
|
||||||
|
|
||||||
|
Webclient:
|
||||||
|
* Added support for signing up with a third party id.
|
||||||
|
* Added support for banning and kicking users.
|
||||||
|
* Added support for displaying and setting ops.
|
||||||
|
* Added support for room names.
|
||||||
|
* Fix bugs with room membership event display.
|
||||||
|
|
||||||
Changes in synapse 0.2.0 (2014-09-02)
|
Changes in synapse 0.2.0 (2014-09-02)
|
||||||
=====================================
|
=====================================
|
||||||
This update changes many configuration options, updates the
|
This update changes many configuration options, updates the
|
||||||
|
|
26
README.rst
26
README.rst
|
@ -2,11 +2,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[1]. 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 names like ``#matrix:matrix.org`` or
|
can be found using names like ``#matrix:matrix.org`` or
|
||||||
``#test:localhost:8080`` 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
|
||||||
|
@ -14,8 +14,8 @@ VoIP[1]. The basics you need to know to get up and running are:
|
||||||
|
|
||||||
The overall architecture is::
|
The overall architecture is::
|
||||||
|
|
||||||
client <----> homeserver <=================> homeserver <-----> client
|
client <----> homeserver <=====================> homeserver <----> client
|
||||||
e.g. matrix.org:8080 e.g. mydomain.net:8080
|
https://matrix.org/_matrix https://mydomain.net/_matrix
|
||||||
|
|
||||||
Quick Start
|
Quick Start
|
||||||
===========
|
===========
|
||||||
|
@ -25,22 +25,20 @@ 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:8080, install synapse
|
- To run your own **private** homeserver on localhost:8008, install synapse
|
||||||
with ``python setup.py develop --user`` and then run one with
|
with ``python setup.py develop --user`` and then run one with
|
||||||
``python synapse/app/homeserver.py`` - you will find a webclient running
|
``python synapse/app/homeserver.py`` - you will find a webclient running
|
||||||
at http://localhost:8080 (use a recent Chrome, Safari or Firefox for now,
|
at http://localhost:8008 (use a recent Chrome, Safari or Firefox for now,
|
||||||
please...)
|
please...)
|
||||||
|
|
||||||
- To make the homeserver **public** and let it exchange messages with
|
- To make the homeserver **public** and let it exchange messages with
|
||||||
other homeservers and participate in the overall Matrix federation, open
|
other homeservers and participate in the overall Matrix federation, open
|
||||||
up port 8080 and run ``python synapse/app/homeserver.py --host
|
up port 8448 and run ``python synapse/app/homeserver.py --host
|
||||||
machine.my.domain.name``. Then come join ``#matrix:matrix.org`` and
|
machine.my.domain.name``. Then come join ``#matrix:matrix.org`` and
|
||||||
say hi! :)
|
say hi! :)
|
||||||
|
|
||||||
For more detailed setup instructions, please see further down this document.
|
For more detailed setup instructions, please see further down this document.
|
||||||
|
|
||||||
[1] VoIP currently in development
|
|
||||||
|
|
||||||
|
|
||||||
About Matrix
|
About Matrix
|
||||||
============
|
============
|
||||||
|
@ -50,15 +48,15 @@ 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[2] 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[3]
|
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 (in development)
|
- 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 of
|
||||||
|
@ -92,9 +90,9 @@ https://github.com/matrix-org/synapse/issues or at matrix@matrix.org.
|
||||||
|
|
||||||
Thanks for trying Matrix!
|
Thanks for trying Matrix!
|
||||||
|
|
||||||
[2] Cryptographic signing of messages isn't turned on yet
|
[1] Cryptographic signing of messages isn't turned on yet
|
||||||
|
|
||||||
[3] End-to-end encryption is currently in development
|
[2] End-to-end encryption is currently in development
|
||||||
|
|
||||||
|
|
||||||
Homeserver Installation
|
Homeserver Installation
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
Upgrading to v0.2.0
|
Upgrading to v0.2.0
|
||||||
===================
|
===================
|
||||||
|
|
||||||
To upgrade the database schema, run::
|
|
||||||
|
|
||||||
./database-prepare-for-0.2.0.sh "<database>.db"
|
|
||||||
|
|
||||||
|
|
||||||
The home server now requires setting up of SSL config before it can run. To
|
The home server now requires setting up of SSL config before it can run. To
|
||||||
automatically generate default config use::
|
automatically generate default config use::
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -88,6 +88,8 @@ class SynapseCmd(cmd.Cmd):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _domain(self):
|
def _domain(self):
|
||||||
|
if "user" not in self.config or not self.config["user"]:
|
||||||
|
return None
|
||||||
return self.config["user"].split(":")[1]
|
return self.config["user"].split(":")[1]
|
||||||
|
|
||||||
def do_config(self, line):
|
def do_config(self, line):
|
||||||
|
@ -191,10 +193,12 @@ class SynapseCmd(cmd.Cmd):
|
||||||
p = getpass.getpass("Enter your password: ")
|
p = getpass.getpass("Enter your password: ")
|
||||||
user = args["user_id"]
|
user = args["user_id"]
|
||||||
if self._is_on("complete_usernames") and not user.startswith("@"):
|
if self._is_on("complete_usernames") and not user.startswith("@"):
|
||||||
user = "@" + user + ":" + self._domain()
|
domain = self._domain()
|
||||||
|
if domain:
|
||||||
|
user = "@" + user + ":" + domain
|
||||||
|
|
||||||
reactor.callFromThread(self._do_login, user, p)
|
reactor.callFromThread(self._do_login, user, p)
|
||||||
print " got %s " % p
|
#print " got %s " % p
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print e
|
print e
|
||||||
|
|
||||||
|
@ -312,7 +316,7 @@ class SynapseCmd(cmd.Cmd):
|
||||||
try:
|
try:
|
||||||
args = self._parse(line, ["roomname"], force_keys=True)
|
args = self._parse(line, ["roomname"], force_keys=True)
|
||||||
path = "/join/%s" % urllib.quote(args["roomname"])
|
path = "/join/%s" % urllib.quote(args["roomname"])
|
||||||
reactor.callFromThread(self._run_and_pprint, "PUT", path, {})
|
reactor.callFromThread(self._run_and_pprint, "POST", path, {})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print e
|
print e
|
||||||
|
|
||||||
|
@ -700,7 +704,7 @@ def main(server_url, identity_server_url, username, token, config_path):
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser("Starts a synapse client.")
|
parser = argparse.ArgumentParser("Starts a synapse client.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s", "--server", dest="server", default="http://localhost:8080",
|
"-s", "--server", dest="server", default="http://localhost:8008",
|
||||||
help="The URL of the home server to talk to.")
|
help="The URL of the home server to talk to.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-i", "--identity-server", dest="identityserver", default="http://localhost:8090",
|
"-i", "--identity-server", dest="identityserver", default="http://localhost:8090",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# This is will prepare a synapse database for running with v0.2.0 of synapse.
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cp "$1" "$1.bak"
|
|
||||||
|
|
||||||
sqlite3 "$1" < "synapse/storage/schema/im.sql"
|
|
||||||
sqlite3 "$1" <<< "PRAGMA user_version = 2;"
|
|
|
@ -2,6 +2,20 @@
|
||||||
Matrix Client-Server API
|
Matrix Client-Server API
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
This specification is old. Please see /docs/specification.rst instead.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The following specification outlines how a client can send and receive data from
|
The following specification outlines how a client can send and receive data from
|
||||||
a home server.
|
a home server.
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
TODO(kegan): Tweak joinalias API keys/path? Event stream historical > live needs
|
.. TODO kegan
|
||||||
a token (currently doesn't). im/sync responses include outdated event formats
|
Room config (specifically: message history,
|
||||||
(room membership change messages). Room config (specifically: message history,
|
public rooms). /register seems super simplistic compared to /login, maybe it
|
||||||
public rooms). /register seems super simplistic compared to /login, maybe it
|
would be better if /register used the same technique as /login? /register should
|
||||||
would be better if /register used the same technique as /login? /register should
|
be "user" not "user_id".
|
||||||
be "user" not "user_id".
|
|
||||||
|
|
||||||
|
|
||||||
How to use the client-server API
|
How to use the client-server API
|
||||||
|
@ -15,7 +14,7 @@ implementation, there may be variations in relation to registering/logging in
|
||||||
which are not covered in extensive detail in this guide.
|
which are not covered in extensive detail in this guide.
|
||||||
|
|
||||||
If you haven't already, get a home server up and running on
|
If you haven't already, get a home server up and running on
|
||||||
``http://localhost:8080``.
|
``http://localhost:8008``.
|
||||||
|
|
||||||
|
|
||||||
Accounts
|
Accounts
|
||||||
|
@ -23,14 +22,16 @@ Accounts
|
||||||
Before you can send and receive messages, you must **register** for an account.
|
Before you can send and receive messages, you must **register** for an account.
|
||||||
If you already have an account, you must **login** into it.
|
If you already have an account, you must **login** into it.
|
||||||
|
|
||||||
**Try out the fiddle: http://jsfiddle.net/jrf1h02d/**
|
`Try out the fiddle`__
|
||||||
|
|
||||||
|
.. __: http://jsfiddle.net/4q2jyxng/
|
||||||
|
|
||||||
Registration
|
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:8080/_matrix/client/api/v1/register"
|
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/register"
|
||||||
|
|
||||||
{
|
{
|
||||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
||||||
|
@ -51,13 +52,17 @@ Login
|
||||||
-----
|
-----
|
||||||
The aim when logging in is to get an access token for your existing user ID::
|
The aim when logging in is to get an access token for your existing user ID::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/_matrix/client/api/v1/login"
|
curl -XGET "http://localhost:8008/_matrix/client/api/v1/login"
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "m.login.password"
|
"flows": [
|
||||||
|
{
|
||||||
|
"type": "m.login.password"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8080/_matrix/client/api/v1/login"
|
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
|
||||||
|
|
||||||
{
|
{
|
||||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
|
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
|
||||||
|
@ -80,14 +85,16 @@ Communicating
|
||||||
In order to communicate with another user, you must **create a room** with that
|
In order to communicate with another user, you must **create a room** with that
|
||||||
user and **send a message** to that room.
|
user and **send a message** to that room.
|
||||||
|
|
||||||
**Try out the fiddle: http://jsfiddle.net/jnwqcshc/**
|
`Try out the fiddle`__
|
||||||
|
|
||||||
|
.. __: http://jsfiddle.net/zL3zto9g/
|
||||||
|
|
||||||
Creating a room
|
Creating a room
|
||||||
---------------
|
---------------
|
||||||
If you want to send a message to someone, you have to be in a room with them. To
|
If you want to send a message to someone, you have to be in a room with them. To
|
||||||
create a room::
|
create a room::
|
||||||
|
|
||||||
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8080/_matrix/client/api/v1/rooms?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
{
|
{
|
||||||
"room_alias": "#tutorial:localhost",
|
"room_alias": "#tutorial:localhost",
|
||||||
|
@ -98,20 +105,27 @@ The "room alias" is a human-readable string which can be shared with other users
|
||||||
so they can join a room, rather than the room ID which is a randomly generated
|
so they can join a room, rather than the room ID which is a randomly generated
|
||||||
string. You can have multiple room aliases per room.
|
string. You can have multiple room aliases per room.
|
||||||
|
|
||||||
TODO(kegan): How to add/remove aliases from an existing room.
|
.. TODO(kegan)
|
||||||
|
How to add/remove aliases from an existing room.
|
||||||
|
|
||||||
|
|
||||||
Sending messages
|
Sending messages
|
||||||
----------------
|
----------------
|
||||||
You can now send messages to this room::
|
You can now send messages to this room::
|
||||||
|
|
||||||
curl -XPUT -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8080/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/messages/%40example%3Alocalhost/msgid1?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
|
{
|
||||||
|
"event_id": "YUwRidLecu"
|
||||||
|
}
|
||||||
|
|
||||||
|
The event ID returned is a unique ID which identifies this message.
|
||||||
|
|
||||||
NB: There are no limitations to the types of messages which can be exchanged.
|
NB: There are no limitations to the types of messages which can be exchanged.
|
||||||
The only requirement is that ``"msgtype"`` is specified.
|
The only requirement is that ``"msgtype"`` is specified. The Matrix
|
||||||
|
specification outlines the following standard types: ``m.text``, ``m.image``,
|
||||||
NB: Depending on the room config, users who join the room may be able to see
|
``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for
|
||||||
message history from before they joined.
|
more information on these types.
|
||||||
|
|
||||||
Users and rooms
|
Users and rooms
|
||||||
===============
|
===============
|
||||||
|
@ -121,33 +135,34 @@ these rules may specify if you require an **invitation** from someone already in
|
||||||
the room in order to **join the room**. In addition, you may also be able to
|
the room in order to **join the room**. In addition, you may also be able to
|
||||||
join a room **via a room alias** if one was set up.
|
join a room **via a room alias** if one was set up.
|
||||||
|
|
||||||
**Try out the fiddle: http://jsfiddle.net/og1xokcr/**
|
`Try out the fiddle`__
|
||||||
|
|
||||||
|
.. __: http://jsfiddle.net/7fhotf1b/
|
||||||
|
|
||||||
Inviting a user to a room
|
Inviting a user to a room
|
||||||
-------------------------
|
-------------------------
|
||||||
You can directly invite a user to a room like so::
|
You can directly invite a user to a room like so::
|
||||||
|
|
||||||
curl -XPUT -d '{"membership":"invite"}' "http://localhost:8080/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
This informs ``@myfriend:localhost`` of the room ID
|
This informs ``@myfriend:localhost`` of the room ID
|
||||||
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
|
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
|
||||||
|
|
||||||
Joining a room via an invite
|
Joining a room via an invite
|
||||||
----------------------------
|
----------------------------
|
||||||
If you receive an invite, you can join the room by changing the membership to
|
If you receive an invite, you can join the room::
|
||||||
join::
|
|
||||||
|
|
||||||
curl -XPUT -d '{"membership":"join"}' "http://localhost:8080/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
NB: Only the person invited (``@myfriend:localhost``) can change the membership
|
NB: Only the person invited (``@myfriend:localhost``) can change the membership
|
||||||
state to ``"join"``.
|
state to ``"join"``. Repeatedly joining a room does nothing.
|
||||||
|
|
||||||
Joining a room via an alias
|
Joining a room via an alias
|
||||||
---------------------------
|
---------------------------
|
||||||
Alternatively, if you know the room alias for this room and the room config
|
Alternatively, if you know the room alias for this room and the room config
|
||||||
allows it, you can directly join a room via the alias::
|
allows it, you can directly join a room via the alias::
|
||||||
|
|
||||||
curl -XPUT -d '{}' "http://localhost:8080/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
{
|
{
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
||||||
|
@ -166,128 +181,444 @@ An event is some interesting piece of data that a client may be interested in.
|
||||||
It can be a message in a room, a room invite, etc. There are many different ways
|
It can be a message in a room, a room invite, etc. There are many different ways
|
||||||
of getting events, depending on what the client already knows.
|
of getting events, depending on what the client already knows.
|
||||||
|
|
||||||
**Try out the fiddle: http://jsfiddle.net/5uk4dqe2/**
|
`Try out the fiddle`__
|
||||||
|
|
||||||
|
.. __: http://jsfiddle.net/vw11mg37/
|
||||||
|
|
||||||
Getting all state
|
Getting all state
|
||||||
-----------------
|
-----------------
|
||||||
If the client doesn't know any information on the rooms the user is
|
If the client doesn't know any information on the rooms the user is
|
||||||
invited/joined on, they can get all the user's state for all rooms::
|
invited/joined on, they can get all the user's state for all rooms::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/_matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
[
|
{
|
||||||
{
|
"end": "s39_18_0",
|
||||||
"membership": "join",
|
"presence": [
|
||||||
"messages": {
|
{
|
||||||
"chunk": [
|
"content": {
|
||||||
{
|
"last_active_ago": 1061436,
|
||||||
"content": {
|
"user_id": "@example:localhost"
|
||||||
"body": "@example:localhost joined the room.",
|
},
|
||||||
"hsob_ts": 1408444664249,
|
"type": "m.presence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rooms": [
|
||||||
|
{
|
||||||
|
"membership": "join",
|
||||||
|
"messages": {
|
||||||
|
"chunk": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"@example:localhost": 10,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"event_id": "wAumPSTsWF",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"join_rule": "public"
|
||||||
|
},
|
||||||
|
"event_id": "jrLVqKHKiI",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.join_rules",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"level": 10
|
||||||
|
},
|
||||||
|
"event_id": "WpmTgsNWUZ",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.add_state_level",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"level": 0
|
||||||
|
},
|
||||||
|
"event_id": "qUMBJyKsTQ",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.send_event_level",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"ban_level": 5,
|
||||||
|
"kick_level": 5
|
||||||
|
},
|
||||||
|
"event_id": "YAaDmKvoUW",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.ops_levels",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "RJbPMtCutf",
|
||||||
"membership": "join",
|
"membership": "join",
|
||||||
"membership_source": "@example:localhost",
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
"membership_target": "@example:localhost",
|
"state_key": "@example:localhost",
|
||||||
"msgtype": "m.text"
|
"ts": 1409665586730,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
},
|
},
|
||||||
"event_id": "lZjmmlrEvo",
|
{
|
||||||
"msg_id": "m1408444664249",
|
"content": {
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
|
"body": "hello",
|
||||||
"type": "m.room.message",
|
"hsob_ts": 1409665660439,
|
||||||
"user_id": "_homeserver_"
|
"msgtype": "m.text"
|
||||||
},
|
},
|
||||||
|
"event_id": "YUwRidLecu",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"ts": 1409665660439,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"membership": "invite"
|
||||||
|
},
|
||||||
|
"event_id": "YjNuBKnPsb",
|
||||||
|
"membership": "invite",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@myfriend:localhost",
|
||||||
|
"ts": 1409666426819,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join",
|
||||||
|
"prev": "join"
|
||||||
|
},
|
||||||
|
"event_id": "KWwdDjNZnm",
|
||||||
|
"membership": "join",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"ts": 1409666551582,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "JFLVteSvQc",
|
||||||
|
"membership": "join",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"ts": 1409666587265,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": "s39_18_0",
|
||||||
|
"start": "t1-11_18_0"
|
||||||
|
},
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state": [
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"body": "hello",
|
"creator": "@example:localhost"
|
||||||
"hsob_ts": 1408445405672,
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
},
|
||||||
"event_id": "BiBJqamISg",
|
"event_id": "dMUoqVTZca",
|
||||||
"msg_id": "msgid1",
|
"required_power_level": 10,
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
"type": "m.room.message",
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.create",
|
||||||
"user_id": "@example:localhost"
|
"user_id": "@example:localhost"
|
||||||
},
|
},
|
||||||
[...]
|
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"body": "@myfriend:localhost joined the room.",
|
"@example:localhost": 10,
|
||||||
"hsob_ts": 1408446501661,
|
"default": 0
|
||||||
"membership": "join",
|
|
||||||
"membership_source": "@myfriend:localhost",
|
|
||||||
"membership_target": "@myfriend:localhost",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
},
|
||||||
"event_id": "IMmXbOzFAa",
|
"event_id": "wAumPSTsWF",
|
||||||
"msg_id": "m1408446501661",
|
"required_power_level": 10,
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
"type": "m.room.message",
|
"state_key": "",
|
||||||
"user_id": "_homeserver_"
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"join_rule": "public"
|
||||||
|
},
|
||||||
|
"event_id": "jrLVqKHKiI",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.join_rules",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"level": 10
|
||||||
|
},
|
||||||
|
"event_id": "WpmTgsNWUZ",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.add_state_level",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"level": 0
|
||||||
|
},
|
||||||
|
"event_id": "qUMBJyKsTQ",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.send_event_level",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"ban_level": 5,
|
||||||
|
"kick_level": 5
|
||||||
|
},
|
||||||
|
"event_id": "YAaDmKvoUW",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.ops_levels",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"membership": "invite"
|
||||||
|
},
|
||||||
|
"event_id": "YjNuBKnPsb",
|
||||||
|
"membership": "invite",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@myfriend:localhost",
|
||||||
|
"ts": 1409666426819,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "JFLVteSvQc",
|
||||||
|
"membership": "join",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"ts": 1409666587265,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"end": "20",
|
}
|
||||||
"start": "0"
|
]
|
||||||
},
|
}
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
This returns all the room IDs of rooms the user is invited/joined on, as well as
|
This returns all the room information the user is invited/joined on, as well as
|
||||||
all of the messages and feedback for these rooms. This can be a LOT of data. You
|
all of the presences relevant for these rooms. This can be a LOT of data. You
|
||||||
may just want the most recent message for each room. This can be achieved by
|
may just want the most recent event for each room. This can be achieved by
|
||||||
applying pagination stream parameters to this request::
|
applying query parameters to ``limit`` this request::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/_matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END&to=START&limit=1"
|
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
[
|
{
|
||||||
{
|
"end": "s39_18_0",
|
||||||
"membership": "join",
|
"presence": [
|
||||||
"messages": {
|
{
|
||||||
"chunk": [
|
"content": {
|
||||||
|
"last_active_ago": 1279484,
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
"type": "m.presence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rooms": [
|
||||||
|
{
|
||||||
|
"membership": "join",
|
||||||
|
"messages": {
|
||||||
|
"chunk": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "JFLVteSvQc",
|
||||||
|
"membership": "join",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"ts": 1409666587265,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": "s39_18_0",
|
||||||
|
"start": "t10-30_18_0"
|
||||||
|
},
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state": [
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"body": "@myfriend:localhost joined the room.",
|
"creator": "@example:localhost"
|
||||||
"hsob_ts": 1408446501661,
|
|
||||||
"membership": "join",
|
|
||||||
"membership_source": "@myfriend:localhost",
|
|
||||||
"membership_target": "@myfriend:localhost",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
},
|
||||||
"event_id": "IMmXbOzFAa",
|
"event_id": "dMUoqVTZca",
|
||||||
"msg_id": "m1408446501661",
|
"required_power_level": 10,
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
"type": "m.room.message",
|
"state_key": "",
|
||||||
"user_id": "_homeserver_"
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.create",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"@example:localhost": 10,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"event_id": "wAumPSTsWF",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"join_rule": "public"
|
||||||
|
},
|
||||||
|
"event_id": "jrLVqKHKiI",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.join_rules",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"level": 10
|
||||||
|
},
|
||||||
|
"event_id": "WpmTgsNWUZ",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.add_state_level",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"level": 0
|
||||||
|
},
|
||||||
|
"event_id": "qUMBJyKsTQ",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.send_event_level",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"ban_level": 5,
|
||||||
|
"kick_level": 5
|
||||||
|
},
|
||||||
|
"event_id": "YAaDmKvoUW",
|
||||||
|
"required_power_level": 10,
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"ts": 1409665585188,
|
||||||
|
"type": "m.room.ops_levels",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"membership": "invite"
|
||||||
|
},
|
||||||
|
"event_id": "YjNuBKnPsb",
|
||||||
|
"membership": "invite",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@myfriend:localhost",
|
||||||
|
"ts": 1409666426819,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "JFLVteSvQc",
|
||||||
|
"membership": "join",
|
||||||
|
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"ts": 1409666587265,
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@example:localhost"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"end": "20",
|
}
|
||||||
"start": "21"
|
]
|
||||||
},
|
}
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Getting live state
|
Getting live state
|
||||||
------------------
|
------------------
|
||||||
Once you know which rooms the client has previously interacted with, you need to
|
Once you know which rooms the client has previously interacted with, you need to
|
||||||
listen for incoming events. This can be done like so::
|
listen for incoming events. This can be done like so::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/_matrix/client/api/v1/events?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END"
|
curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
|
||||||
|
|
||||||
{
|
{
|
||||||
"chunk": [],
|
"chunk": [],
|
||||||
"end": "215",
|
"end": "s39_18_0",
|
||||||
"start": "215"
|
"start": "s39_18_0"
|
||||||
}
|
}
|
||||||
|
|
||||||
This will block waiting for an incoming event, timing out after several seconds.
|
This will block waiting for an incoming event, timing out after several seconds.
|
||||||
Even if there are no new events (as in the example above), there will be some
|
Even if there are no new events (as in the example above), there will be some
|
||||||
pagination stream response keys. The client should make subsequent requests
|
pagination stream response keys. The client should make subsequent requests
|
||||||
using the value of the ``"end"`` key (in this case ``215``) as the ``from``
|
using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from``
|
||||||
query parameter. This value should be stored so when the client reopens your app
|
query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access
|
||||||
after a period of inactivity, you can resume from where you got up to in the
|
_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the
|
||||||
event stream. If it has been a long period of inactivity, there may be LOTS of
|
client reopens your app after a period of inactivity, you can resume from where
|
||||||
events waiting for the user. In this case, you may wish to get all state instead
|
you got up to in the event stream. If it has been a long period of inactivity,
|
||||||
and then resume getting live state from a newer end token.
|
there may be LOTS of events waiting for the user. In this case, you may wish to
|
||||||
|
get all state instead and then resume getting live state from a newer end token.
|
||||||
|
|
||||||
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
|
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
|
||||||
in milliseconds. A timeout of 0 will not block.
|
in milliseconds. A timeout of 0 will not block.
|
||||||
|
@ -300,4 +631,6 @@ creating and joining rooms, sending messages, getting member lists and getting
|
||||||
historical messages for a room. This covers most functionality of a messaging
|
historical messages for a room. This covers most functionality of a messaging
|
||||||
application.
|
application.
|
||||||
|
|
||||||
**Try out the fiddle: http://jsfiddle.net/L8r3o1wr/**
|
`Try out the fiddle`__
|
||||||
|
|
||||||
|
.. __: http://jsfiddle.net/uztL3yme/
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"resourcePath": "/directory",
|
"resourcePath": "/directory",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"summary": "Get the room ID corresponding to this room alias.",
|
"summary": "Get the room ID corresponding to this room alias.",
|
||||||
|
"notes": "Volatile: This API is likely to change.",
|
||||||
"type": "DirectoryResponse",
|
"type": "DirectoryResponse",
|
||||||
"nickname": "get_room_id_for_alias",
|
"nickname": "get_room_id_for_alias",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"summary": "Create a new mapping from room alias to room ID.",
|
"summary": "Create a new mapping from room alias to room ID.",
|
||||||
|
"notes": "Volatile: This API is likely to change.",
|
||||||
"type": "void",
|
"type": "void",
|
||||||
"nickname": "add_room_alias",
|
"nickname": "add_room_alias",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"resourcePath": "/events",
|
"resourcePath": "/events",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"nickname": "get_login_info",
|
"nickname": "get_login_info",
|
||||||
"notes": "All login stages MUST be mentioned if there is >1 login type.",
|
"notes": "All login stages MUST be mentioned if there is >1 login type.",
|
||||||
"summary": "Get the login mechanism to use when logging in.",
|
"summary": "Get the login mechanism to use when logging in.",
|
||||||
"type": "LoginInfo"
|
"type": "LoginFlows"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
@ -40,17 +40,31 @@
|
||||||
"path": "/login"
|
"path": "/login"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"models": {
|
"models": {
|
||||||
|
"LoginFlows": {
|
||||||
|
"id": "LoginFlows",
|
||||||
|
"properties": {
|
||||||
|
"flows": {
|
||||||
|
"description": "A list of valid login flows.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "LoginInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"LoginInfo": {
|
"LoginInfo": {
|
||||||
"id": "LoginInfo",
|
"id": "LoginInfo",
|
||||||
"properties": {
|
"properties": {
|
||||||
"stages": {
|
"stages": {
|
||||||
"description": "Multi-stage login only: An array of all the login types required to login.",
|
"description": "Multi-stage login only: An array of all the login types required to login.",
|
||||||
"format": "string",
|
"items": {
|
||||||
|
"$ref": "string"
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -65,6 +79,10 @@
|
||||||
"access_token": {
|
"access_token": {
|
||||||
"description": "The access token for this user's login if this is the final stage of the login process.",
|
"description": "The access token for this user's login if this is the final stage of the login process.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"description": "The user's fully-qualified user ID.",
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"next": {
|
"next": {
|
||||||
"description": "Multi-stage login only: The next login type to submit.",
|
"description": "Multi-stage login only: The next login type to submit.",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"resourcePath": "/presence",
|
"resourcePath": "/presence",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
"PresenceUpdate": {
|
"PresenceUpdate": {
|
||||||
"id": "PresenceUpdate",
|
"id": "PresenceUpdate",
|
||||||
"properties": {
|
"properties": {
|
||||||
"state": {
|
"presence": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Enum: The presence state.",
|
"description": "Enum: The presence state.",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
@ -128,10 +128,10 @@
|
||||||
"Presence": {
|
"Presence": {
|
||||||
"id": "Presence",
|
"id": "Presence",
|
||||||
"properties": {
|
"properties": {
|
||||||
"mtime_age": {
|
"last_active_ago": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
"description": "The last time this user's presence state changed, in milliseconds."
|
"description": "The last time this user performed an action on their home server."
|
||||||
},
|
},
|
||||||
"user_id": {
|
"user_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"resourcePath": "/profile",
|
"resourcePath": "/profile",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
"path": "/register"
|
"path": "/register"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -52,6 +52,10 @@
|
||||||
"user_id": {
|
"user_id": {
|
||||||
"description": "The fully-qualified user ID.",
|
"description": "The fully-qualified user ID.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"home_server": {
|
||||||
|
"description": "The name of the home server.",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
"basePath": "http://localhost:8008/_matrix/client/api/v1",
|
||||||
"resourcePath": "/rooms",
|
"resourcePath": "/rooms",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -180,6 +180,59 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/rooms/{roomId}/state/m.room.name",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Set the name of this room.",
|
||||||
|
"notes": "Set the name of this room.",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "set_room_name",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"description": "The name contents",
|
||||||
|
"required": true,
|
||||||
|
"type": "RoomName",
|
||||||
|
"paramType": "body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room to set the name of.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"summary": "Get the room's name.",
|
||||||
|
"notes": "",
|
||||||
|
"type": "RoomName",
|
||||||
|
"nickname": "get_room_name",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room to get the name of.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responseMessages": [
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"message": "Name not found."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/send/m.room.message.feedback",
|
"path": "/rooms/{roomId}/send/m.room.message.feedback",
|
||||||
"operations": [
|
"operations": [
|
||||||
|
@ -267,6 +320,12 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"required": true,
|
||||||
|
"type": "JoinRequest",
|
||||||
|
"paramType": "body"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -291,6 +350,12 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"required": true,
|
||||||
|
"type": "LeaveRequest",
|
||||||
|
"paramType": "body"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -424,10 +489,10 @@
|
||||||
"path": "/join/{roomAliasOrId}",
|
"path": "/join/{roomAliasOrId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Join a room via a room alias or room ID.",
|
"summary": "Join a room via a room alias or room ID.",
|
||||||
"notes": "Join a room via a room alias or room ID.",
|
"notes": "Join a room via a room alias or room ID.",
|
||||||
"type": "RoomInfo",
|
"type": "JoinRoomInfo",
|
||||||
"nickname": "join",
|
"nickname": "join",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -574,7 +639,7 @@
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"summary": "Get a list of all the current state events for this room.",
|
"summary": "Get a list of all the current state events for this room.",
|
||||||
"notes": "Get a list of all the current state events for this room.",
|
"notes": "NOT YET IMPLEMENTED.",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "Event"
|
"$ref": "Event"
|
||||||
|
@ -598,7 +663,7 @@
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"summary": "Get all the current information for this room, including messages and state events.",
|
"summary": "Get all the current information for this room, including messages and state events.",
|
||||||
"notes": "Get all the current information for this room, including messages and state events.",
|
"notes": "NOT YET IMPLEMENTED.",
|
||||||
"type": "InitialSyncRoomData",
|
"type": "InitialSyncRoomData",
|
||||||
"nickname": "get_room_sync_data",
|
"nickname": "get_room_sync_data",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -624,6 +689,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"RoomName": {
|
||||||
|
"id": "RoomName",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The human-readable name for the room. Can contain spaces."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Message": {
|
"Message": {
|
||||||
"id": "Message",
|
"id": "Message",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -640,6 +714,16 @@
|
||||||
"Feedback": {
|
"Feedback": {
|
||||||
"id": "Feedback",
|
"id": "Feedback",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"target_event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The event ID being acknowledged.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The type of feedback. Either 'delivered' or 'read'.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Member": {
|
"Member": {
|
||||||
|
@ -652,7 +736,7 @@
|
||||||
"invite",
|
"invite",
|
||||||
"join",
|
"join",
|
||||||
"leave",
|
"leave",
|
||||||
"knock"
|
"ban"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -672,6 +756,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"JoinRoomInfo": {
|
||||||
|
"id": "JoinRoomInfo",
|
||||||
|
"properties": {
|
||||||
|
"room_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The room ID joined, if joined via a room alias only.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"RoomConfig": {
|
"RoomConfig": {
|
||||||
"id": "RoomConfig",
|
"id": "RoomConfig",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -830,6 +924,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"JoinRequest": {
|
||||||
|
"id": "JoinRequest",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"LeaveRequest": {
|
||||||
|
"id": "LeaveRequest",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
"BanRequest": {
|
"BanRequest": {
|
||||||
"id": "BanRequest",
|
"id": "BanRequest",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Scope
|
|
||||||
-----
|
|
||||||
|
|
||||||
This document considers threats specific to the server to server federation
|
|
||||||
synapse protocol.
|
|
||||||
|
|
||||||
|
|
||||||
Attacker
|
|
||||||
--------
|
|
||||||
|
|
||||||
It is assumed that the attacker can see and manipulate all network traffic
|
|
||||||
between any of the servers and may be in control of one or more homeservers
|
|
||||||
participating in the federation protocol.
|
|
||||||
|
|
||||||
Threat Model
|
|
||||||
============
|
|
||||||
|
|
||||||
Denial of Service
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The attacker could attempt to prevent delivery of messages to or from the
|
|
||||||
victim in order to:
|
|
||||||
|
|
||||||
* Disrupt service or marketing campaign of a commercial competitor.
|
|
||||||
* Censor a discussion or censor a participant in a discussion.
|
|
||||||
* Perform general vandalism.
|
|
||||||
|
|
||||||
Threat: Resource Exhaustion
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could cause the victims server to exhaust a particular resource
|
|
||||||
(e.g. open TCP connections, CPU, memory, disk storage)
|
|
||||||
|
|
||||||
Threat: Unrecoverable Consistency Violations
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could send messages which created an unrecoverable "split-brain"
|
|
||||||
state in the cluster such that the victim's servers could no longer dervive a
|
|
||||||
consistent view of the chatroom state.
|
|
||||||
|
|
||||||
Threat: Bad History
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could convince the victim to accept invalid messages which the
|
|
||||||
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.
|
|
||||||
|
|
||||||
Threat: Block Network Traffic
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could try to firewall traffic between the victim's server and some
|
|
||||||
or all of the other servers in the chatroom.
|
|
||||||
|
|
||||||
Threat: High Volume of Messages
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could send large volumes of messages to a chatroom with the victim
|
|
||||||
making the chatroom unusable.
|
|
||||||
|
|
||||||
Threat: Banning users without necessary authorisation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could attempt to ban a user from a chatroom with the necessary
|
|
||||||
authorisation.
|
|
||||||
|
|
||||||
Spoofing
|
|
||||||
--------
|
|
||||||
|
|
||||||
An attacker could try to send a message claiming to be from the victim without
|
|
||||||
the victim having sent the message in order to:
|
|
||||||
|
|
||||||
* Impersonate the victim while performing illict activity.
|
|
||||||
* Obtain privileges of the victim.
|
|
||||||
|
|
||||||
Threat: Altering Message Contents
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could try to alter the contents of an existing message from the
|
|
||||||
victim.
|
|
||||||
|
|
||||||
Threat: Fake Message "origin" Field
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could try to send a new message purporting to be from the victim
|
|
||||||
with a phony "origin" field.
|
|
||||||
|
|
||||||
Spamming
|
|
||||||
--------
|
|
||||||
|
|
||||||
The attacker could try to send a high volume of solicicted or unsolicted
|
|
||||||
messages to the victim in order to:
|
|
||||||
|
|
||||||
* Find victims for scams.
|
|
||||||
* Market unwanted products.
|
|
||||||
|
|
||||||
Threat: Unsoliticted Messages
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could try to send messages to victims who do not wish to receive
|
|
||||||
them.
|
|
||||||
|
|
||||||
Threat: Abusive Messages
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could send abusive or threatening messages to the victim
|
|
||||||
|
|
||||||
Spying
|
|
||||||
------
|
|
||||||
|
|
||||||
The attacker could try to access message contents or metadata for messages sent
|
|
||||||
by the victim or to the victim that were not intended to reach the attacker in
|
|
||||||
order to:
|
|
||||||
|
|
||||||
* Gain sensitive personal or commercial information.
|
|
||||||
* Impersonate the victim using credentials contained in the messages.
|
|
||||||
(e.g. password reset messages)
|
|
||||||
* Discover who the victim was talking to and when.
|
|
||||||
|
|
||||||
Threat: Disclosure during Transmission
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could try to expose the message contents or metadata during
|
|
||||||
transmission between the servers.
|
|
||||||
|
|
||||||
Threat: Disclosure to Servers Outside Chatroom
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could try to convince servers within a chatroom to send messages to
|
|
||||||
a server it controls that was not authorised to be within the chatroom.
|
|
||||||
|
|
||||||
Threat: Disclosure to Servers Within Chatroom
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
An attacker could take control of a server within a chatroom to expose message
|
|
||||||
contents or metadata for messages in that room.
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8080</p>
|
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8008</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="loginForm">
|
<form class="loginForm">
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
<input type="text" id="userLogin" placeholder="Username"></input>
|
||||||
|
|
|
@ -10,7 +10,7 @@ $('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
|
@ -25,7 +25,7 @@ $('.login').live('click', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
var getCurrentRoomList = function() {
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
var rooms = data.rooms;
|
var rooms = data.rooms;
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
|
@ -44,7 +44,7 @@ $('.createRoom').live('click', function() {
|
||||||
data.room_alias_name = roomAlias;
|
data.room_alias_name = roomAlias;
|
||||||
}
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
|
@ -79,7 +79,7 @@ $('.sendMessage').live('click', function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<p>This event stream demo requires a home server to be running on http://localhost:8080</p>
|
<p>This event stream demo requires a home server to be running on http://localhost:8008</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="loginForm">
|
<form class="loginForm">
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
<input type="text" id="userLogin" placeholder="Username"></input>
|
||||||
|
|
|
@ -7,7 +7,7 @@ var eventStreamInfo = {
|
||||||
var roomInfo = [];
|
var roomInfo = [];
|
||||||
|
|
||||||
var longpollEventStream = function() {
|
var longpollEventStream = function() {
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
url = url.replace("$from", eventStreamInfo.from);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ $('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
|
@ -65,7 +65,7 @@ $('.login').live('click', function() {
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
var getCurrentRoomList = function() {
|
||||||
$("#roomId").val("");
|
$("#roomId").val("");
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
var rooms = data.rooms;
|
var rooms = data.rooms;
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
|
@ -98,7 +98,7 @@ var sendMessage = function(roomId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="signUp">
|
<div class="signUp">
|
||||||
<p>Matrix example application: Requires a local home server running at http://localhost:8080</p>
|
<p>Matrix example application: Requires a local home server running at http://localhost:8008</p>
|
||||||
<form class="registrationForm">
|
<form class="registrationForm">
|
||||||
<p>No account? Register:</p>
|
<p>No account? Register:</p>
|
||||||
<input type="text" id="userReg" placeholder="Username"></input>
|
<input type="text" id="userReg" placeholder="Username"></input>
|
||||||
|
|
|
@ -10,7 +10,7 @@ var viewingRoomId;
|
||||||
|
|
||||||
// ************** Event Streaming **************
|
// ************** Event Streaming **************
|
||||||
var longpollEventStream = function() {
|
var longpollEventStream = function() {
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
url = url.replace("$from", eventStreamInfo.from);
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ $('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
|
@ -107,7 +107,7 @@ $('.register').live('click', function() {
|
||||||
var user = $("#userReg").val();
|
var user = $("#userReg").val();
|
||||||
var password = $("#passwordReg").val();
|
var password = $("#passwordReg").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_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_id: user, password: password }),
|
||||||
|
@ -134,7 +134,7 @@ $('.createRoom').live('click', function() {
|
||||||
data.room_alias_name = roomAlias;
|
data.room_alias_name = roomAlias;
|
||||||
}
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
|
@ -155,7 +155,7 @@ $('.createRoom').live('click', function() {
|
||||||
|
|
||||||
// ************** Getting current state **************
|
// ************** Getting current state **************
|
||||||
var getCurrentRoomList = function() {
|
var getCurrentRoomList = function() {
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
var rooms = data.rooms;
|
var rooms = data.rooms;
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
|
@ -181,7 +181,7 @@ var loadRoomContent = function(roomId) {
|
||||||
|
|
||||||
var getMessages = function(roomId) {
|
var getMessages = function(roomId) {
|
||||||
$("#messages").empty();
|
$("#messages").empty();
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/" +
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
|
||||||
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
|
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=data.chunk.length-1; i>=0; --i) {
|
for (var i=data.chunk.length-1; i>=0; --i) {
|
||||||
|
@ -193,7 +193,7 @@ var getMessages = function(roomId) {
|
||||||
var getMemberList = function(roomId) {
|
var getMemberList = function(roomId) {
|
||||||
$("#members").empty();
|
$("#members").empty();
|
||||||
memberInfo = [];
|
memberInfo = [];
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/" +
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
|
||||||
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
|
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=0; i<data.chunk.length; ++i) {
|
for (var i=0; i<data.chunk.length; ++i) {
|
||||||
|
@ -216,7 +216,7 @@ $('.sendMessage').live('click', function() {
|
||||||
var sendMessage = function(roomId, body) {
|
var sendMessage = function(roomId, body) {
|
||||||
var msgId = $.now();
|
var msgId = $.now();
|
||||||
|
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ var setRooms = function(roomList) {
|
||||||
var membership = $(this).find('td:eq(1)').text();
|
var membership = $(this).find('td:eq(1)').text();
|
||||||
if (membership !== "join") {
|
if (membership !== "join") {
|
||||||
console.log("Joining room " + roomId);
|
console.log("Joining room " + roomId);
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -290,6 +290,9 @@ var addMessage = function(data) {
|
||||||
|
|
||||||
var msg = data.content.body;
|
var msg = data.content.body;
|
||||||
if (data.type === "m.room.member") {
|
if (data.type === "m.room.member") {
|
||||||
|
if (data.content.membership === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data.content.membership === "invite") {
|
if (data.content.membership === "invite") {
|
||||||
msg = "<em>invited " + data.state_key + " to the room</em>";
|
msg = "<em>invited " + data.state_key + " to the room</em>";
|
||||||
}
|
}
|
||||||
|
@ -299,10 +302,13 @@ var addMessage = function(data) {
|
||||||
else if (data.content.membership === "leave") {
|
else if (data.content.membership === "leave") {
|
||||||
msg = "<em>left the room</em>";
|
msg = "<em>left the room</em>";
|
||||||
}
|
}
|
||||||
else {
|
else if (data.content.membership === "ban") {
|
||||||
msg = "<em>" + data.content.membership + "</em>";
|
msg = "<em>was banned from the room</em>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (msg === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var row = "<tr>" +
|
var row = "<tr>" +
|
||||||
"<td>"+data.user_id+"</td>" +
|
"<td>"+data.user_id+"</td>" +
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<p>This registration/login demo requires a home server to be running on http://localhost:8080</p>
|
<p>This registration/login demo requires a home server to be running on http://localhost:8008</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="registrationForm">
|
<form class="registrationForm">
|
||||||
<input type="text" id="user" placeholder="Username"></input>
|
<input type="text" id="user" placeholder="Username"></input>
|
||||||
|
|
|
@ -11,7 +11,7 @@ $('.register').live('click', function() {
|
||||||
var user = $("#user").val();
|
var user = $("#user").val();
|
||||||
var password = $("#password").val();
|
var password = $("#password").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_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_id: user, password: password }),
|
||||||
|
@ -27,7 +27,7 @@ $('.register').live('click', function() {
|
||||||
|
|
||||||
var login = function(user, password) {
|
var login = function(user, password) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
|
@ -44,7 +44,7 @@ var login = function(user, password) {
|
||||||
$('.login').live('click', function() {
|
$('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.getJSON("http://localhost:8080/_matrix/client/api/v1/login", function(data) {
|
$.getJSON("http://localhost:8008/_matrix/client/api/v1/login", function(data) {
|
||||||
if (data.flows[0].type !== "m.login.password") {
|
if (data.flows[0].type !== "m.login.password") {
|
||||||
alert("I don't know how to login with this type: " + data.type);
|
alert("I don't know how to login with this type: " + data.type);
|
||||||
return;
|
return;
|
||||||
|
@ -60,7 +60,7 @@ $('.logout').live('click', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.testToken').live('click', function() {
|
$('.testToken').live('click', function() {
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
|
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<p>This room membership demo requires a home server to be running on http://localhost:8080</p>
|
<p>This room membership demo requires a home server to be running on http://localhost:8008</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="loginForm">
|
<form class="loginForm">
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
<input type="text" id="userLogin" placeholder="Username"></input>
|
||||||
|
|
|
@ -18,7 +18,7 @@ $('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
|
@ -39,7 +39,7 @@ var getCurrentRoomList = function() {
|
||||||
// solution but that is out of scope of this fiddle.
|
// solution but that is out of scope of this fiddle.
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
$("#rooms").find("tr:gt(0)").remove();
|
||||||
|
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
var rooms = data.rooms;
|
var rooms = data.rooms;
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
|
@ -53,7 +53,7 @@ var getCurrentRoomList = function() {
|
||||||
$('.createRoom').live('click', function() {
|
$('.createRoom').live('click', function() {
|
||||||
var data = {};
|
var data = {};
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
|
@ -87,7 +87,7 @@ $('.changeMembership').live('click', function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
|
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
url = url.replace("$membership", membership);
|
url = url.replace("$membership", membership);
|
||||||
|
@ -117,7 +117,7 @@ $('.changeMembership').live('click', function() {
|
||||||
|
|
||||||
$('.joinAlias').live('click', function() {
|
$('.joinAlias').live('click', function() {
|
||||||
var roomAlias = $("#roomAlias").val();
|
var roomAlias = $("#roomAlias").val();
|
||||||
var url = "http://localhost:8080/_matrix/client/api/v1/join/$roomalias?access_token=$token";
|
var url = "http://localhost:8008/_matrix/client/api/v1/join/$roomalias?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
|
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|
|
@ -0,0 +1,510 @@
|
||||||
|
/*
|
||||||
|
* basic.css
|
||||||
|
* ~~~~~~~~~
|
||||||
|
*
|
||||||
|
* Sphinx stylesheet -- basic theme.
|
||||||
|
*
|
||||||
|
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||||
|
* :license: BSD, see LICENSE for details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* -- main layout ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
div.clearer {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- relbar ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
div.related {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related h3 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0 0 10px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related li {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related li.right {
|
||||||
|
float: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- sidebar --------------------------------------------------------------- */
|
||||||
|
|
||||||
|
div.sphinxsidebarwrapper {
|
||||||
|
padding: 10px 5px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar {
|
||||||
|
float: left;
|
||||||
|
width: 230px;
|
||||||
|
margin-left: -100%;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul ul,
|
||||||
|
div.sphinxsidebar ul.want-points {
|
||||||
|
margin-left: 20px;
|
||||||
|
list-style: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul ul {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar form {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar input {
|
||||||
|
border: 1px solid #98dbcc;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- search page ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
ul.search {
|
||||||
|
margin: 10px 0 0 20px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search li {
|
||||||
|
padding: 5px 0 5px 20px;
|
||||||
|
background-image: url(file.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search li a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search li div.context {
|
||||||
|
color: #888;
|
||||||
|
margin: 2px 0 0 30px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.keywordmatches li.goodmatch a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- index page ------------------------------------------------------------ */
|
||||||
|
|
||||||
|
table.contentstable {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.contentstable p.biglink {
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.biglink {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.linkdescr {
|
||||||
|
font-style: italic;
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- general index --------------------------------------------------------- */
|
||||||
|
|
||||||
|
table.indextable {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.indextable td {
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.indextable dl, table.indextable dd {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.indextable tr.pcap {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.indextable tr.cap {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.toggler {
|
||||||
|
margin-right: 3px;
|
||||||
|
margin-top: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.modindex-jumpbox {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
margin: 1em 0 1em 0;
|
||||||
|
padding: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.genindex-jumpbox {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
margin: 1em 0 1em 0;
|
||||||
|
padding: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- general body styles --------------------------------------------------- */
|
||||||
|
|
||||||
|
a.headerlink {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover > a.headerlink,
|
||||||
|
h2:hover > a.headerlink,
|
||||||
|
h3:hover > a.headerlink,
|
||||||
|
h4:hover > a.headerlink,
|
||||||
|
h5:hover > a.headerlink,
|
||||||
|
h6:hover > a.headerlink,
|
||||||
|
dt:hover > a.headerlink {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document p.caption {
|
||||||
|
text-align: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-list ul {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rubric {
|
||||||
|
margin-top: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
clear: both;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- sidebars -------------------------------------------------------------- */
|
||||||
|
|
||||||
|
div.sidebar {
|
||||||
|
margin: 0 0 0.5em 1em;
|
||||||
|
border: 1px solid #ddb;
|
||||||
|
padding: 7px 7px 0 7px;
|
||||||
|
background-color: #ffe;
|
||||||
|
width: 40%;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sidebar-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- topics ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 7px 7px 0 7px;
|
||||||
|
margin: 10px 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.topic-title {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- admonitions ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
div.admonition {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition dt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition dl {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title {
|
||||||
|
margin: 0px 10px 5px 0px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document p.centered {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- tables ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
table.docutils {
|
||||||
|
border: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.docutils td, table.docutils th {
|
||||||
|
padding: 1px 8px 1px 5px;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list td, table.field-list th {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote td, table.footnote th {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.citation {
|
||||||
|
border-left: solid 1px gray;
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.citation td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- other body styles ----------------------------------------------------- */
|
||||||
|
|
||||||
|
ol.arabic {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.loweralpha {
|
||||||
|
list-style: lower-alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.upperalpha {
|
||||||
|
list-style: upper-alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.lowerroman {
|
||||||
|
list-style: lower-roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.upperroman {
|
||||||
|
list-style: upper-roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd p {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd ul, dd table {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt:target, .highlighted {
|
||||||
|
background-color: #fbe54e;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl.glossary dt {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-list ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-list p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refcount {
|
||||||
|
color: #060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optional {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.versionmodified {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-message {
|
||||||
|
background-color: #fda;
|
||||||
|
padding: 5px;
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footnote:target {
|
||||||
|
background-color: #ffa
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-block {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-block .line-block {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guilabel, .menuselection {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accelerator {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.classifier {
|
||||||
|
font-style: oblique;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- code displays --------------------------------------------------------- */
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.linenos pre {
|
||||||
|
padding: 5px 0px;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.highlighttable {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.highlighttable td {
|
||||||
|
padding: 0 0.5em 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descname {
|
||||||
|
background-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descclassname {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.xref, a tt {
|
||||||
|
background-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewcode-link {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewcode-back {
|
||||||
|
float: right;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.viewcode-block:target {
|
||||||
|
margin: -1px -10px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- math display ---------------------------------------------------------- */
|
||||||
|
|
||||||
|
img.math {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document div.math p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.eqno {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- printout stylesheet --------------------------------------------------- */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
div.document,
|
||||||
|
div.documentwrapper,
|
||||||
|
div.bodywrapper {
|
||||||
|
margin: 0 !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar,
|
||||||
|
div.related,
|
||||||
|
div.footer,
|
||||||
|
#top-link {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/perl -pi
|
#!/usr/bin/perl -pi
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
$copyright = <<EOT;
|
$copyright = <<EOT;
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
MATRIXDOTORG=$HOME/workspace/matrix.org
|
||||||
|
|
||||||
|
rst2html-2.7.py --stylesheet=basic.css,nature.css ../docs/specification.rst > $MATRIXDOTORG/docs/spec/index.html
|
||||||
|
rst2html-2.7.py --stylesheet=basic.css,nature.css ../docs/client-server/howto.rst > $MATRIXDOTORG/docs/howtos/client-server.html
|
||||||
|
|
||||||
|
perl -pi -e 's#<head>#<head><link rel="stylesheet" href="/site.css">#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
|
||||||
|
|
||||||
|
perl -pi -e 's#<body>#<body><div id="header"><div id="headerContent"> </div></div><div id="page"><div id="wrapper"><div style="text-align: center; padding: 40px;"><img src="/matrix.png" width="305" height="130" alt="[matrix]"/></div>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
|
||||||
|
|
||||||
|
perl -pi -e 's#</body>#</div></div><div id="footer"><div id="footerContent">© 2014 Matrix.org</div></div></body>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
|
||||||
|
|
||||||
|
scp -r $MATRIXDOTORG/docs matrix@ldc-prd-matrix-001:/sites/matrix-beta
|
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
* nature.css_t
|
||||||
|
* ~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* Sphinx stylesheet -- nature theme.
|
||||||
|
*
|
||||||
|
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||||
|
* :license: BSD, see LICENSE for details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* -- page layout ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 100%;
|
||||||
|
/*background-color: #111;*/
|
||||||
|
color: #555;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.documentwrapper {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bodywrapper {
|
||||||
|
margin: 0 0 0 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #B1B4B6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
div.document {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
div.document {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #3E4349;
|
||||||
|
padding: 0 30px 30px 30px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer {
|
||||||
|
color: #555;
|
||||||
|
width: 100%;
|
||||||
|
padding: 13px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer a {
|
||||||
|
color: #444;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related {
|
||||||
|
background-color: #6BA81E;
|
||||||
|
line-height: 32px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0px 1px 0 #444;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related a {
|
||||||
|
color: #E2F3CC;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar {
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebarwrapper{
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3,
|
||||||
|
div.sphinxsidebar h4 {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #222;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #ddd;
|
||||||
|
text-shadow: 1px 1px 0 white
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h4{
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3 a {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.sphinxsidebar p {
|
||||||
|
color: #888;
|
||||||
|
padding: 5px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar p.topless {
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul {
|
||||||
|
margin: 10px 20px;
|
||||||
|
padding: 0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar a {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar input {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar input[type=text]{
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- body styles ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #005B81;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #E32E00;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document h1,
|
||||||
|
div.document h2,
|
||||||
|
div.document h3,
|
||||||
|
div.document h4,
|
||||||
|
div.document h5,
|
||||||
|
div.document h6 {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #BED4EB;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #212224;
|
||||||
|
margin: 30px 0px 10px 0px;
|
||||||
|
padding: 5px 0 5px 10px;
|
||||||
|
text-shadow: 0px 1px 0 white
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
|
||||||
|
div.document h2 { font-size: 150%; background-color: #C8D5E3; }
|
||||||
|
div.document h3 { font-size: 120%; background-color: #D8DEE3; }
|
||||||
|
div.document h4 { font-size: 110%; background-color: #D8DEE3; }
|
||||||
|
div.document h5 { font-size: 100%; background-color: #D8DEE3; }
|
||||||
|
div.document h6 { font-size: 100%; background-color: #D8DEE3; }
|
||||||
|
|
||||||
|
a.headerlink {
|
||||||
|
color: #c60f0f;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: 0 4px 0 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.headerlink:hover {
|
||||||
|
background-color: #c60f0f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document p, div.document dd, div.document li {
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p.admonition-title + p {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.highlight{
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.note {
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.seealso {
|
||||||
|
background-color: #ffc;
|
||||||
|
border: 1px solid #ff6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.warning {
|
||||||
|
background-color: #ffe4e4;
|
||||||
|
border: 1px solid #f66;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title:after {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: White;
|
||||||
|
color: #222;
|
||||||
|
line-height: 1.2em;
|
||||||
|
border: 1px solid #C6C9CB;
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin: 1.5em 0 1.5em 0;
|
||||||
|
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
|
||||||
|
-moz-box-shadow: 1px 1px 1px #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
background-color: #ecf0f3;
|
||||||
|
color: #222;
|
||||||
|
/* padding: 1px 2px; */
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewcode-back {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.viewcode-block:target {
|
||||||
|
background-color: #f4debf;
|
||||||
|
border-top: 1px solid #ac9;
|
||||||
|
border-bottom: 1px solid #ac9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li dd {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li dl {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li dl dd {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd ul {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li dd ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -16,4 +16,4 @@
|
||||||
""" This is a reference implementation of a synapse home server.
|
""" This is a reference implementation of a synapse home server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.2.0"
|
__version__ = "0.2.1"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -28,6 +28,7 @@ class Codes(object):
|
||||||
UNKNOWN = "M_UNKNOWN"
|
UNKNOWN = "M_UNKNOWN"
|
||||||
NOT_FOUND = "M_NOT_FOUND"
|
NOT_FOUND = "M_NOT_FOUND"
|
||||||
UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||||
|
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||||
|
|
||||||
|
|
||||||
class CodeMessageException(Exception):
|
class CodeMessageException(Exception):
|
||||||
|
@ -38,11 +39,15 @@ class CodeMessageException(Exception):
|
||||||
super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
|
super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
|
||||||
self.code = code
|
self.code = code
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
self.response_code_message = None
|
||||||
|
|
||||||
|
def error_dict(self):
|
||||||
|
return cs_error(self.msg)
|
||||||
|
|
||||||
|
|
||||||
class SynapseError(CodeMessageException):
|
class SynapseError(CodeMessageException):
|
||||||
"""A base error which can be caught for all synapse events."""
|
"""A base error which can be caught for all synapse events."""
|
||||||
def __init__(self, code, msg, errcode=""):
|
def __init__(self, code, msg, errcode=Codes.UNKNOWN):
|
||||||
"""Constructs a synapse error.
|
"""Constructs a synapse error.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -53,6 +58,11 @@ class SynapseError(CodeMessageException):
|
||||||
super(SynapseError, self).__init__(code, msg)
|
super(SynapseError, self).__init__(code, msg)
|
||||||
self.errcode = errcode
|
self.errcode = errcode
|
||||||
|
|
||||||
|
def error_dict(self):
|
||||||
|
return cs_error(
|
||||||
|
self.msg,
|
||||||
|
self.errcode,
|
||||||
|
)
|
||||||
|
|
||||||
class RoomError(SynapseError):
|
class RoomError(SynapseError):
|
||||||
"""An error raised when a room event fails."""
|
"""An error raised when a room event fails."""
|
||||||
|
@ -91,13 +101,26 @@ class StoreError(SynapseError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def cs_exception(exception):
|
class LimitExceededError(SynapseError):
|
||||||
if isinstance(exception, SynapseError):
|
"""A client has sent too many requests and is being throttled.
|
||||||
|
"""
|
||||||
|
def __init__(self, code=429, msg="Too Many Requests", retry_after_ms=None,
|
||||||
|
errcode=Codes.LIMIT_EXCEEDED):
|
||||||
|
super(LimitExceededError, self).__init__(code, msg, errcode)
|
||||||
|
self.retry_after_ms = retry_after_ms
|
||||||
|
self.response_code_message = "Too Many Requests"
|
||||||
|
|
||||||
|
def error_dict(self):
|
||||||
return cs_error(
|
return cs_error(
|
||||||
exception.msg,
|
self.msg,
|
||||||
Codes.UNKNOWN if not exception.errcode else exception.errcode)
|
self.errcode,
|
||||||
elif isinstance(exception, CodeMessageException):
|
retry_after_ms=self.retry_after_ms,
|
||||||
return cs_error(exception.msg)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cs_exception(exception):
|
||||||
|
if isinstance(exception, CodeMessageException):
|
||||||
|
return exception.error_dict()
|
||||||
else:
|
else:
|
||||||
logging.error("Unknown exception type: %s", type(exception))
|
logging.error("Unknown exception type: %s", type(exception))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -103,8 +103,7 @@ class FeedbackEvent(SynapseEvent):
|
||||||
def get_content_template(self):
|
def get_content_template(self):
|
||||||
return {
|
return {
|
||||||
"type": u"string",
|
"type": u"string",
|
||||||
"target_event_id": u"string",
|
"target_event_id": u"string"
|
||||||
"msg_sender_id": u"string"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
|
||||||
|
class Ratelimiter(object):
|
||||||
|
"""
|
||||||
|
Ratelimit message sending by user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.message_counts = collections.OrderedDict()
|
||||||
|
|
||||||
|
def send_message(self, user_id, time_now_s, msg_rate_hz, burst_count):
|
||||||
|
"""Can the user send a message?
|
||||||
|
Args:
|
||||||
|
user_id: The user sending a message.
|
||||||
|
time_now_s: The time now.
|
||||||
|
msg_rate_hz: The long term number of messages a user can send in a
|
||||||
|
second.
|
||||||
|
burst_count: How many messages the user can send before being
|
||||||
|
limited.
|
||||||
|
Returns:
|
||||||
|
A pair of a bool indicating if they can send a message now and a
|
||||||
|
time in seconds of when they can next send a message.
|
||||||
|
"""
|
||||||
|
self.prune_message_counts(time_now_s)
|
||||||
|
message_count, time_start, _ignored = self.message_counts.pop(
|
||||||
|
user_id, (0., time_now_s, None),
|
||||||
|
)
|
||||||
|
time_delta = time_now_s - time_start
|
||||||
|
sent_count = message_count - time_delta * msg_rate_hz
|
||||||
|
if sent_count < 0:
|
||||||
|
allowed = True
|
||||||
|
time_start = time_now_s
|
||||||
|
message_count = 1.
|
||||||
|
elif sent_count > burst_count - 1.:
|
||||||
|
allowed = False
|
||||||
|
else:
|
||||||
|
allowed = True
|
||||||
|
message_count += 1
|
||||||
|
|
||||||
|
self.message_counts[user_id] = (
|
||||||
|
message_count, time_start, msg_rate_hz
|
||||||
|
)
|
||||||
|
|
||||||
|
if msg_rate_hz > 0:
|
||||||
|
time_allowed = (
|
||||||
|
time_start + (message_count - burst_count + 1) / msg_rate_hz
|
||||||
|
)
|
||||||
|
if time_allowed < time_now_s:
|
||||||
|
time_allowed = time_now_s
|
||||||
|
else:
|
||||||
|
time_allowed = -1
|
||||||
|
|
||||||
|
return allowed, time_allowed
|
||||||
|
|
||||||
|
def prune_message_counts(self, time_now_s):
|
||||||
|
for user_id in self.message_counts.keys():
|
||||||
|
message_count, time_start, msg_rate_hz = (
|
||||||
|
self.message_counts[user_id]
|
||||||
|
)
|
||||||
|
time_delta = time_now_s - time_start
|
||||||
|
if message_count - time_delta * msg_rate_hz > 0:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
del self.message_counts[user_id]
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -23,7 +23,8 @@ from twisted.enterprise import adbapi
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
from twisted.web.static import File
|
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, ContentRepoResource
|
from synapse.http.server import JsonResource, RootRedirect
|
||||||
|
from synapse.http.content_repository import ContentRepoResource
|
||||||
from synapse.http.client import TwistedHttpClient
|
from synapse.http.client import TwistedHttpClient
|
||||||
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
|
||||||
|
@ -74,7 +75,9 @@ class SynapseHomeServer(HomeServer):
|
||||||
return File("webclient") # TODO configurable?
|
return File("webclient") # TODO configurable?
|
||||||
|
|
||||||
def build_resource_for_content_repo(self):
|
def build_resource_for_content_repo(self):
|
||||||
return ContentRepoResource(self, self.upload_dir, self.auth)
|
return ContentRepoResource(
|
||||||
|
self, self.upload_dir, self.auth, self.content_addr
|
||||||
|
)
|
||||||
|
|
||||||
def build_db_pool(self):
|
def build_db_pool(self):
|
||||||
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
|
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
|
||||||
|
@ -90,20 +93,28 @@ class SynapseHomeServer(HomeServer):
|
||||||
if row and row[0]:
|
if row and row[0]:
|
||||||
user_version = row[0]
|
user_version = row[0]
|
||||||
|
|
||||||
if user_version < SCHEMA_VERSION:
|
if user_version > SCHEMA_VERSION:
|
||||||
# TODO(paul): add some kind of intelligent fixup here
|
raise ValueError("Cannot use this database as it is too " +
|
||||||
raise ValueError("Cannot use this database as the " +
|
"new for the server to understand"
|
||||||
"schema version (%d) does not match (%d)" %
|
|
||||||
(user_version, SCHEMA_VERSION)
|
|
||||||
)
|
)
|
||||||
|
elif user_version < SCHEMA_VERSION:
|
||||||
|
logging.info("Upgrading database from version %d",
|
||||||
|
user_version
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run every version since after the current version.
|
||||||
|
for v in range(user_version + 1, SCHEMA_VERSION + 1):
|
||||||
|
sql_script = read_schema("delta/v%d" % (v))
|
||||||
|
c.executescript(sql_script)
|
||||||
|
|
||||||
|
db_conn.commit()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for sql_loc in SCHEMAS:
|
for sql_loc in SCHEMAS:
|
||||||
sql_script = read_schema(sql_loc)
|
sql_script = read_schema(sql_loc)
|
||||||
|
|
||||||
c.executescript(sql_script)
|
c.executescript(sql_script)
|
||||||
db_conn.commit()
|
db_conn.commit()
|
||||||
|
|
||||||
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
|
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
@ -247,6 +258,8 @@ def setup():
|
||||||
upload_dir=os.path.abspath("uploads"),
|
upload_dir=os.path.abspath("uploads"),
|
||||||
db_name=config.database_path,
|
db_name=config.database_path,
|
||||||
tls_context_factory=tls_context_factory,
|
tls_context_factory=tls_context_factory,
|
||||||
|
config=config,
|
||||||
|
content_addr=config.content_addr,
|
||||||
)
|
)
|
||||||
|
|
||||||
hs.register_servlets()
|
hs.register_servlets()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -13,8 +13,6 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
import ConfigParser as configparser
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
@ -121,6 +119,9 @@ class Config(object):
|
||||||
and value is not None):
|
and value is not None):
|
||||||
config[key] = value
|
config[key] = value
|
||||||
with open(config_args.config_path, "w") as config_file:
|
with open(config_args.config_path, "w") as config_file:
|
||||||
|
# TODO(paul) it would be lovely if we wrote out vim- and emacs-
|
||||||
|
# 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)
|
yaml.dump(config, config_file, default_flow_style=False)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -17,8 +17,11 @@ from .tls import TlsConfig
|
||||||
from .server import ServerConfig
|
from .server import ServerConfig
|
||||||
from .logger import LoggingConfig
|
from .logger import LoggingConfig
|
||||||
from .database import DatabaseConfig
|
from .database import DatabaseConfig
|
||||||
|
from .ratelimiting import RatelimitConfig
|
||||||
|
from .repository import ContentRepositoryConfig
|
||||||
|
|
||||||
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig):
|
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
||||||
|
RatelimitConfig, ContentRepositoryConfig):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if __name__=='__main__':
|
if __name__=='__main__':
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import Config
|
||||||
|
|
||||||
|
class RatelimitConfig(Config):
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
super(RatelimitConfig, self).__init__(args)
|
||||||
|
self.rc_messages_per_second = args.rc_messages_per_second
|
||||||
|
self.rc_message_burst_count = args.rc_message_burst_count
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_arguments(cls, parser):
|
||||||
|
super(RatelimitConfig, cls).add_arguments(parser)
|
||||||
|
rc_group = parser.add_argument_group("ratelimiting")
|
||||||
|
rc_group.add_argument(
|
||||||
|
"--rc-messages-per-second", type=float, default=0.2,
|
||||||
|
help="number of messages a client can send per second"
|
||||||
|
)
|
||||||
|
rc_group.add_argument(
|
||||||
|
"--rc-message-burst-count", type=float, default=10,
|
||||||
|
help="number of message a client can send before being throttled"
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 matrix.org
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import Config
|
||||||
|
import os
|
||||||
|
|
||||||
|
class ContentRepositoryConfig(Config):
|
||||||
|
def __init__(self, args):
|
||||||
|
super(ContentRepositoryConfig, self).__init__(args)
|
||||||
|
self.max_upload_size = self.parse_size(args.max_upload_size)
|
||||||
|
|
||||||
|
def parse_size(self, string):
|
||||||
|
sizes = {"K": 1024, "M": 1024 * 1024}
|
||||||
|
size = 1
|
||||||
|
suffix = string[-1]
|
||||||
|
if suffix in sizes:
|
||||||
|
string = string[:-1]
|
||||||
|
size = sizes[suffix]
|
||||||
|
return int(string) * size
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_arguments(cls, parser):
|
||||||
|
super(ContentRepositoryConfig, cls).add_arguments(parser)
|
||||||
|
db_group = parser.add_argument_group("content_repository")
|
||||||
|
db_group.add_argument(
|
||||||
|
"--max-upload-size", default="1M"
|
||||||
|
)
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -32,6 +32,14 @@ class ServerConfig(Config):
|
||||||
self.webclient = True
|
self.webclient = True
|
||||||
self.manhole = args.manhole
|
self.manhole = args.manhole
|
||||||
|
|
||||||
|
if not args.content_addr:
|
||||||
|
host = args.server_name
|
||||||
|
if ':' not in host:
|
||||||
|
host = "%s:%d" % (host, args.bind_port)
|
||||||
|
args.content_addr = "https://%s" % (host,)
|
||||||
|
|
||||||
|
self.content_addr = args.content_addr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_arguments(cls, parser):
|
def add_arguments(cls, parser):
|
||||||
super(ServerConfig, cls).add_arguments(parser)
|
super(ServerConfig, cls).add_arguments(parser)
|
||||||
|
@ -50,13 +58,16 @@ class ServerConfig(Config):
|
||||||
help="Local interface to listen on")
|
help="Local interface to listen on")
|
||||||
server_group.add_argument("-D", "--daemonize", action='store_true',
|
server_group.add_argument("-D", "--daemonize", action='store_true',
|
||||||
help="Daemonize the home server")
|
help="Daemonize the home server")
|
||||||
server_group.add_argument('--pid-file', default="hs.pid",
|
server_group.add_argument('--pid-file', default="homeserver.pid",
|
||||||
help="When running as a daemon, the file to"
|
help="When running as a daemon, the file to"
|
||||||
" store the pid in")
|
" store the pid in")
|
||||||
server_group.add_argument("--manhole", metavar="PORT", dest="manhole",
|
server_group.add_argument("--manhole", metavar="PORT", dest="manhole",
|
||||||
type=int,
|
type=int,
|
||||||
help="Turn on the twisted telnet manhole"
|
help="Turn on the twisted telnet manhole"
|
||||||
" service on the given port.")
|
" service on the given port.")
|
||||||
|
server_group.add_argument("--content-addr", default=None,
|
||||||
|
help="The host and scheme to use for the "
|
||||||
|
"content repository")
|
||||||
|
|
||||||
def read_signing_key(self, signing_key_path):
|
def read_signing_key(self, signing_key_path):
|
||||||
signing_key_base64 = self.read_file(signing_key_path, "signing_key")
|
signing_key_base64 = self.read_file(signing_key_path, "signing_key")
|
||||||
|
@ -77,3 +88,4 @@ class ServerConfig(Config):
|
||||||
with open(args.signing_key_path, "w") as signing_key_file:
|
with open(args.signing_key_path, "w") as signing_key_file:
|
||||||
key = nacl.signing.SigningKey.generate()
|
key = nacl.signing.SigningKey.generate()
|
||||||
signing_key_file.write(encode_base64(key.encode()))
|
signing_key_file.write(encode_base64(key.encode()))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,18 @@
|
||||||
from twisted.internet import reactor, ssl
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from twisted.internet import ssl
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL
|
||||||
from twisted.internet._sslverify import _OpenSSLECCurve, _defaultCurveName
|
from twisted.internet._sslverify import _OpenSSLECCurve, _defaultCurveName
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -628,7 +628,6 @@ class _TransactionQueue(object):
|
||||||
|
|
||||||
for deferred in deferreds:
|
for deferred in deferreds:
|
||||||
deferred.errback(e)
|
deferred.errback(e)
|
||||||
yield deferred
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# We want to be *very* sure we delete this after we stop processing
|
# We want to be *very* sure we delete this after we stop processing
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from synapse.api.errors import LimitExceededError
|
||||||
|
|
||||||
class BaseHandler(object):
|
class BaseHandler(object):
|
||||||
|
|
||||||
|
@ -25,8 +26,22 @@ class BaseHandler(object):
|
||||||
self.room_lock = hs.get_room_lock_manager()
|
self.room_lock = hs.get_room_lock_manager()
|
||||||
self.state_handler = hs.get_state_handler()
|
self.state_handler = hs.get_state_handler()
|
||||||
self.distributor = hs.get_distributor()
|
self.distributor = hs.get_distributor()
|
||||||
|
self.ratelimiter = hs.get_ratelimiter()
|
||||||
|
self.clock = hs.get_clock()
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
|
|
||||||
|
def ratelimit(self, user_id):
|
||||||
|
time_now = self.clock.time()
|
||||||
|
allowed, time_allowed = self.ratelimiter.send_message(
|
||||||
|
user_id, time_now,
|
||||||
|
msg_rate_hz=self.hs.config.rc_messages_per_second,
|
||||||
|
burst_count=self.hs.config.rc_message_burst_count,
|
||||||
|
)
|
||||||
|
if not allowed:
|
||||||
|
raise LimitExceededError(
|
||||||
|
retry_after_ms=int(1000*(time_allowed - time_now)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseRoomHandler(BaseHandler):
|
class BaseRoomHandler(BaseHandler):
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +18,7 @@ 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
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ class DirectoryHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_association(self, room_alias, room_id, servers):
|
def create_association(self, room_alias, room_id, servers=None):
|
||||||
# TODO(erikj): Do auth.
|
# TODO(erikj): Do auth.
|
||||||
|
|
||||||
if not room_alias.is_mine:
|
if not room_alias.is_mine:
|
||||||
|
@ -47,6 +48,12 @@ class DirectoryHandler(BaseHandler):
|
||||||
|
|
||||||
# TODO(erikj): Check if there is a current association.
|
# TODO(erikj): Check if there is a current association.
|
||||||
|
|
||||||
|
if not servers:
|
||||||
|
servers = yield self.store.get_joined_hosts_for_room(room_id)
|
||||||
|
|
||||||
|
if not servers:
|
||||||
|
raise SynapseError(400, "Failed to get server list")
|
||||||
|
|
||||||
yield self.store.create_room_alias_association(
|
yield self.store.create_room_alias_association(
|
||||||
room_alias,
|
room_alias,
|
||||||
room_id,
|
room_id,
|
||||||
|
@ -68,7 +75,10 @@ class DirectoryHandler(BaseHandler):
|
||||||
result = yield self.federation.make_query(
|
result = yield self.federation.make_query(
|
||||||
destination=room_alias.domain,
|
destination=room_alias.domain,
|
||||||
query_type="directory",
|
query_type="directory",
|
||||||
args={"room_alias": room_alias.to_string()},
|
args={
|
||||||
|
"room_alias": room_alias.to_string(),
|
||||||
|
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if result and "room_id" in result and "servers" in result:
|
if result and "room_id" in result and "servers" in result:
|
||||||
|
@ -79,6 +89,9 @@ class DirectoryHandler(BaseHandler):
|
||||||
defer.returnValue({})
|
defer.returnValue({})
|
||||||
return
|
return
|
||||||
|
|
||||||
|
extra_servers = yield self.store.get_joined_hosts_for_room(room_id)
|
||||||
|
servers = list(set(extra_servers) | set(servers))
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
"servers": servers,
|
"servers": servers,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -126,5 +126,7 @@ class EventHandler(BaseHandler):
|
||||||
defer.returnValue(None)
|
defer.returnValue(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
yield self.auth.check(event, raises=True)
|
if hasattr(event, "room_id"):
|
||||||
|
yield self.auth.check_joined_room(event.room_id, user.to_string())
|
||||||
|
|
||||||
defer.returnValue(event)
|
defer.returnValue(event)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -21,8 +21,9 @@ from synapse.api.events.room import InviteJoinEvent, RoomMemberEvent
|
||||||
from synapse.api.constants import Membership
|
from synapse.api.constants import Membership
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.federation.pdu_codec import PduCodec
|
from synapse.federation.pdu_codec import PduCodec
|
||||||
|
from synapse.api.errors import SynapseError
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer, reactor
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
yield self.hs.get_handlers().room_member_handler.change_membership(
|
yield self.hs.get_handlers().room_member_handler.change_membership(
|
||||||
new_event,
|
new_event,
|
||||||
do_auth=True
|
do_auth=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -231,7 +232,12 @@ class FederationHandler(BaseHandler):
|
||||||
# TODO (erikj): Time out here.
|
# TODO (erikj): Time out here.
|
||||||
d = defer.Deferred()
|
d = defer.Deferred()
|
||||||
self.waiting_for_join_list.setdefault((joinee, room_id), []).append(d)
|
self.waiting_for_join_list.setdefault((joinee, room_id), []).append(d)
|
||||||
yield d
|
reactor.callLater(10, d.cancel)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield d
|
||||||
|
except defer.CancelledError:
|
||||||
|
raise SynapseError(500, "Unable to join remote room")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield self.store.store_room(
|
yield self.store.store_room(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -76,6 +76,8 @@ class MessageHandler(BaseRoomHandler):
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError if something went wrong.
|
SynapseError if something went wrong.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.ratelimit(event.user_id)
|
||||||
# TODO(paul): Why does 'event' not have a 'user' object?
|
# TODO(paul): Why does 'event' not have a 'user' object?
|
||||||
user = self.hs.parse_userid(event.user_id)
|
user = self.hs.parse_userid(event.user_id)
|
||||||
assert user.is_mine, "User must be our own: %s" % (user,)
|
assert user.is_mine, "User must be our own: %s" % (user,)
|
||||||
|
@ -140,7 +142,12 @@ class MessageHandler(BaseRoomHandler):
|
||||||
SynapseError if something went wrong.
|
SynapseError if something went wrong.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
snapshot = yield self.store.snapshot_room(event.room_id, event.user_id)
|
snapshot = yield self.store.snapshot_room(
|
||||||
|
event.room_id,
|
||||||
|
event.user_id,
|
||||||
|
state_type=event.type,
|
||||||
|
state_key=event.state_key,
|
||||||
|
)
|
||||||
|
|
||||||
yield self.auth.check(event, snapshot, raises=True)
|
yield self.auth.check(event, snapshot, raises=True)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -155,19 +155,18 @@ class PresenceHandler(BaseHandler):
|
||||||
if observer_user == observed_user:
|
if observer_user == observed_user:
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
allowed_by_subscription = yield self.store.is_presence_visible(
|
if (yield self.store.user_rooms_intersect(
|
||||||
observed_localpart=observed_user.localpart,
|
[u.to_string() for u in observer_user, observed_user]
|
||||||
observer_userid=observer_user.to_string(),
|
)):
|
||||||
)
|
|
||||||
|
|
||||||
if allowed_by_subscription:
|
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
share_room = yield self.store.do_users_share_a_room(
|
if (yield self.store.is_presence_visible(
|
||||||
[observer_user, observed_user]
|
observed_localpart=observed_user.localpart,
|
||||||
)
|
observer_userid=observer_user.to_string(),
|
||||||
|
)):
|
||||||
|
defer.returnValue(True)
|
||||||
|
|
||||||
defer.returnValue(share_room)
|
defer.returnValue(False)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_state(self, target_user, auth_user):
|
def get_state(self, target_user, auth_user):
|
||||||
|
@ -181,7 +180,7 @@ class PresenceHandler(BaseHandler):
|
||||||
state = yield self.store.get_presence_state(target_user.localpart)
|
state = yield self.store.get_presence_state(target_user.localpart)
|
||||||
if "mtime" in state:
|
if "mtime" in state:
|
||||||
del state["mtime"]
|
del state["mtime"]
|
||||||
state["presence"] = state["state"]
|
state["presence"] = state.pop("state")
|
||||||
|
|
||||||
if target_user in self._user_cachemap:
|
if target_user in self._user_cachemap:
|
||||||
state["last_active"] = (
|
state["last_active"] = (
|
||||||
|
@ -208,21 +207,17 @@ class PresenceHandler(BaseHandler):
|
||||||
raise SynapseError(400, "User is not hosted on this Home Server")
|
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||||
|
|
||||||
if target_user != auth_user:
|
if target_user != auth_user:
|
||||||
raise AuthError(400, "Cannot set another user's displayname")
|
raise AuthError(400, "Cannot set another user's presence")
|
||||||
|
|
||||||
if "status_msg" not in state:
|
if "status_msg" not in state:
|
||||||
state["status_msg"] = None
|
state["status_msg"] = None
|
||||||
|
|
||||||
for k in state.keys():
|
for k in state.keys():
|
||||||
if k not in ("presence", "state", "status_msg"):
|
if k not in ("presence", "status_msg"):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400, "Unexpected presence state key '%s'" % (k,)
|
400, "Unexpected presence state key '%s'" % (k,)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle legacy "state" key for now
|
|
||||||
if "state" in state:
|
|
||||||
state["presence"] = state.pop("state")
|
|
||||||
|
|
||||||
if state["presence"] not in self.STATE_LEVELS:
|
if state["presence"] not in self.STATE_LEVELS:
|
||||||
raise SynapseError(400, "'%s' is not a valid presence state" %
|
raise SynapseError(400, "'%s' is not a valid presence state" %
|
||||||
state["presence"]
|
state["presence"]
|
||||||
|
@ -601,7 +596,7 @@ class PresenceHandler(BaseHandler):
|
||||||
if state is None:
|
if state is None:
|
||||||
state = yield self.store.get_presence_state(user.localpart)
|
state = yield self.store.get_presence_state(user.localpart)
|
||||||
del state["mtime"]
|
del state["mtime"]
|
||||||
state["presence"] = state["state"]
|
state["presence"] = state.pop("state")
|
||||||
|
|
||||||
if user in self._user_cachemap:
|
if user in self._user_cachemap:
|
||||||
state["last_active"] = (
|
state["last_active"] = (
|
||||||
|
@ -622,8 +617,6 @@ class PresenceHandler(BaseHandler):
|
||||||
"user_id": user.to_string(),
|
"user_id": user.to_string(),
|
||||||
}
|
}
|
||||||
user_state.update(**state)
|
user_state.update(**state)
|
||||||
if "state" in user_state and "presence" not in user_state:
|
|
||||||
user_state["presence"] = user_state["state"]
|
|
||||||
|
|
||||||
yield self.federation.send_edu(
|
yield self.federation.send_edu(
|
||||||
destination=destination,
|
destination=destination,
|
||||||
|
@ -655,21 +648,12 @@ class PresenceHandler(BaseHandler):
|
||||||
state = dict(push)
|
state = dict(push)
|
||||||
del state["user_id"]
|
del state["user_id"]
|
||||||
|
|
||||||
if "presence" in state:
|
if "presence" not in state:
|
||||||
# all is OK
|
|
||||||
pass
|
|
||||||
elif "state" in state:
|
|
||||||
# Legacy handling
|
|
||||||
state["presence"] = state["state"]
|
|
||||||
else:
|
|
||||||
logger.warning("Received a presence 'push' EDU from %s without"
|
logger.warning("Received a presence 'push' EDU from %s without"
|
||||||
+ " either a 'presence' or 'state' key", origin
|
+ " a 'presence' key", origin
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "state" in state:
|
|
||||||
del state["state"]
|
|
||||||
|
|
||||||
if "last_active_ago" in state:
|
if "last_active_ago" in state:
|
||||||
state["last_active"] = int(
|
state["last_active"] = int(
|
||||||
self.clock.time_msec() - state.pop("last_active_ago")
|
self.clock.time_msec() - state.pop("last_active_ago")
|
||||||
|
@ -773,15 +757,52 @@ class PresenceEventSource(object):
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def is_visible(self, observer_user, observed_user):
|
||||||
|
if observer_user == observed_user:
|
||||||
|
defer.returnValue(True)
|
||||||
|
|
||||||
|
presence = self.hs.get_handlers().presence_handler
|
||||||
|
|
||||||
|
if (yield presence.store.user_rooms_intersect(
|
||||||
|
[u.to_string() for u in observer_user, observed_user]
|
||||||
|
)):
|
||||||
|
defer.returnValue(True)
|
||||||
|
|
||||||
|
if observed_user.is_mine:
|
||||||
|
pushmap = presence._local_pushmap
|
||||||
|
|
||||||
|
defer.returnValue(
|
||||||
|
observed_user.localpart in pushmap and
|
||||||
|
observer_user in pushmap[observed_user.localpart]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
recvmap = presence._remote_recvmap
|
||||||
|
|
||||||
|
defer.returnValue(
|
||||||
|
observed_user in recvmap and
|
||||||
|
observer_user in recvmap[observed_user]
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
def get_new_events_for_user(self, user, from_key, limit):
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
from_key = int(from_key)
|
from_key = int(from_key)
|
||||||
|
|
||||||
|
observer_user = user
|
||||||
|
|
||||||
presence = self.hs.get_handlers().presence_handler
|
presence = self.hs.get_handlers().presence_handler
|
||||||
cachemap = presence._user_cachemap
|
cachemap = presence._user_cachemap
|
||||||
|
|
||||||
# TODO(paul): limit, and filter by visibility
|
updates = []
|
||||||
updates = [(k, cachemap[k]) for k in cachemap
|
# TODO(paul): use a DeferredList ? How to limit concurrency.
|
||||||
if from_key < cachemap[k].serial]
|
for observed_user in cachemap.keys():
|
||||||
|
if not (from_key < cachemap[observed_user].serial):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (yield self.is_visible(observer_user, observed_user)):
|
||||||
|
updates.append((observed_user, cachemap[observed_user]))
|
||||||
|
|
||||||
|
# TODO(paul): limit
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
clock = self.clock
|
clock = self.clock
|
||||||
|
@ -789,20 +810,23 @@ class PresenceEventSource(object):
|
||||||
latest_serial = max([x[1].serial for x in updates])
|
latest_serial = max([x[1].serial for x in updates])
|
||||||
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
||||||
|
|
||||||
return ((data, latest_serial))
|
defer.returnValue((data, latest_serial))
|
||||||
else:
|
else:
|
||||||
return (([], presence._user_cachemap_latest_serial))
|
defer.returnValue(([], presence._user_cachemap_latest_serial))
|
||||||
|
|
||||||
def get_current_key(self):
|
def get_current_key(self):
|
||||||
presence = self.hs.get_handlers().presence_handler
|
presence = self.hs.get_handlers().presence_handler
|
||||||
return presence._user_cachemap_latest_serial
|
return presence._user_cachemap_latest_serial
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
def get_pagination_rows(self, user, pagination_config, key):
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
# TODO (erikj): Does this make sense? Ordering?
|
# TODO (erikj): Does this make sense? Ordering?
|
||||||
|
|
||||||
from_token = pagination_config.from_token
|
from_token = pagination_config.from_token
|
||||||
to_token = pagination_config.to_token
|
to_token = pagination_config.to_token
|
||||||
|
|
||||||
|
observer_user = user
|
||||||
|
|
||||||
from_key = int(from_token.presence_key)
|
from_key = int(from_token.presence_key)
|
||||||
|
|
||||||
if to_token:
|
if to_token:
|
||||||
|
@ -813,7 +837,17 @@ class PresenceEventSource(object):
|
||||||
presence = self.hs.get_handlers().presence_handler
|
presence = self.hs.get_handlers().presence_handler
|
||||||
cachemap = presence._user_cachemap
|
cachemap = presence._user_cachemap
|
||||||
|
|
||||||
# TODO(paul): limit, and filter by visibility
|
updates = []
|
||||||
|
# TODO(paul): use a DeferredList ? How to limit concurrency.
|
||||||
|
for observed_user in cachemap.keys():
|
||||||
|
if not (to_key < cachemap[observed_user].serial < from_key):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (yield self.is_visible(observer_user, observed_user)):
|
||||||
|
updates.append((observed_user, cachemap[observed_user]))
|
||||||
|
|
||||||
|
# TODO(paul): limit
|
||||||
|
|
||||||
updates = [(k, cachemap[k]) for k in cachemap
|
updates = [(k, cachemap[k]) for k in cachemap
|
||||||
if to_key < cachemap[k].serial < from_key]
|
if to_key < cachemap[k].serial < from_key]
|
||||||
|
|
||||||
|
@ -831,13 +865,13 @@ class PresenceEventSource(object):
|
||||||
next_token = next_token.copy_and_replace(
|
next_token = next_token.copy_and_replace(
|
||||||
"presence_key", earliest_serial
|
"presence_key", earliest_serial
|
||||||
)
|
)
|
||||||
return ((data, next_token))
|
defer.returnValue((data, next_token))
|
||||||
else:
|
else:
|
||||||
if not to_token:
|
if not to_token:
|
||||||
to_token = from_token.copy_and_replace(
|
to_token = from_token.copy_and_replace(
|
||||||
"presence_key", 0
|
"presence_key", 0
|
||||||
)
|
)
|
||||||
return (([], to_token))
|
defer.returnValue(([], to_token))
|
||||||
|
|
||||||
|
|
||||||
class UserPresenceCache(object):
|
class UserPresenceCache(object):
|
||||||
|
@ -851,7 +885,6 @@ class UserPresenceCache(object):
|
||||||
|
|
||||||
def update(self, state, serial):
|
def update(self, state, serial):
|
||||||
assert("mtime_age" not in state)
|
assert("mtime_age" not in state)
|
||||||
assert("state" not in state)
|
|
||||||
|
|
||||||
self.state.update(state)
|
self.state.update(state)
|
||||||
# Delete keys that are now 'None'
|
# Delete keys that are now 'None'
|
||||||
|
@ -869,11 +902,6 @@ class UserPresenceCache(object):
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
# clone it so caller can't break our cache
|
# clone it so caller can't break our cache
|
||||||
state = dict(self.state)
|
state = dict(self.state)
|
||||||
|
|
||||||
# Legacy handling
|
|
||||||
if "presence" in state:
|
|
||||||
state["state"] = state["presence"]
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def make_event(self, user, clock):
|
def make_event(self, user, clock):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -20,9 +20,13 @@ from synapse.types import UserID
|
||||||
from synapse.api.errors import SynapseError, RegistrationError
|
from synapse.api.errors import SynapseError, RegistrationError
|
||||||
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
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RegistrationHandler(BaseHandler):
|
class RegistrationHandler(BaseHandler):
|
||||||
|
@ -34,7 +38,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
self.distributor.declare("registered_user")
|
self.distributor.declare("registered_user")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def register(self, localpart=None, password=None):
|
def register(self, localpart=None, password=None, threepidCreds=None):
|
||||||
"""Registers a new client on the server.
|
"""Registers a new client on the server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -47,6 +51,20 @@ class RegistrationHandler(BaseHandler):
|
||||||
Raises:
|
Raises:
|
||||||
RegistrationError if there was a problem registering.
|
RegistrationError if there was a problem registering.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if threepidCreds:
|
||||||
|
for c in threepidCreds:
|
||||||
|
logger.info("validating theeepidcred sid %s on id server %s", c['sid'], c['idServer'])
|
||||||
|
try:
|
||||||
|
threepid = yield self._threepid_from_creds(c)
|
||||||
|
except:
|
||||||
|
logger.err()
|
||||||
|
raise RegistrationError(400, "Couldn't validate 3pid")
|
||||||
|
|
||||||
|
if not threepid:
|
||||||
|
raise RegistrationError(400, "Couldn't validate 3pid")
|
||||||
|
logger.info("got threepid medium %s address %s", threepid['medium'], threepid['address'])
|
||||||
|
|
||||||
password_hash = None
|
password_hash = None
|
||||||
if password:
|
if password:
|
||||||
password_hash = bcrypt.hashpw(password, bcrypt.gensalt())
|
password_hash = bcrypt.hashpw(password, bcrypt.gensalt())
|
||||||
|
@ -61,7 +79,6 @@ class RegistrationHandler(BaseHandler):
|
||||||
password_hash=password_hash)
|
password_hash=password_hash)
|
||||||
|
|
||||||
self.distributor.fire("registered_user", user)
|
self.distributor.fire("registered_user", user)
|
||||||
defer.returnValue((user_id, token))
|
|
||||||
else:
|
else:
|
||||||
# autogen a random user ID
|
# autogen a random user ID
|
||||||
attempts = 0
|
attempts = 0
|
||||||
|
@ -80,7 +97,6 @@ class RegistrationHandler(BaseHandler):
|
||||||
password_hash=password_hash)
|
password_hash=password_hash)
|
||||||
|
|
||||||
self.distributor.fire("registered_user", user)
|
self.distributor.fire("registered_user", user)
|
||||||
defer.returnValue((user_id, token))
|
|
||||||
except SynapseError:
|
except SynapseError:
|
||||||
# if user id is taken, just generate another
|
# if user id is taken, just generate another
|
||||||
user_id = None
|
user_id = None
|
||||||
|
@ -90,6 +106,15 @@ class RegistrationHandler(BaseHandler):
|
||||||
raise RegistrationError(
|
raise RegistrationError(
|
||||||
500, "Cannot generate user ID.")
|
500, "Cannot generate user ID.")
|
||||||
|
|
||||||
|
# Now we have a matrix ID, bind it to the threepids we were given
|
||||||
|
if threepidCreds:
|
||||||
|
for c in threepidCreds:
|
||||||
|
# XXX: This should be a deferred list, shouldn't it?
|
||||||
|
yield self._bind_threepid(c, user_id)
|
||||||
|
|
||||||
|
|
||||||
|
defer.returnValue((user_id, token))
|
||||||
|
|
||||||
def _generate_token(self, user_id):
|
def _generate_token(self, user_id):
|
||||||
# urlsafe variant uses _ and - so use . as the separator and replace
|
# urlsafe variant uses _ and - so use . as the separator and replace
|
||||||
# all =s with .s so http clients don't quote =s when it is used as
|
# all =s with .s so http clients don't quote =s when it is used as
|
||||||
|
@ -99,3 +124,34 @@ class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
def _generate_user_id(self):
|
def _generate_user_id(self):
|
||||||
return "-" + stringutils.random_string(18)
|
return "-" + stringutils.random_string(18)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _threepid_from_creds(self, creds):
|
||||||
|
httpCli = PlainHttpClient(self.hs)
|
||||||
|
# XXX: make this configurable!
|
||||||
|
trustedIdServers = [ 'matrix.org:8090' ]
|
||||||
|
if not creds['idServer'] in trustedIdServers:
|
||||||
|
logger.warn('%s is not a trusted ID server: rejecting 3pid credentials', creds['idServer'])
|
||||||
|
defer.returnValue(None)
|
||||||
|
data = yield httpCli.get_json(
|
||||||
|
creds['idServer'],
|
||||||
|
"/_matrix/identity/api/v1/3pid/getValidated3pid",
|
||||||
|
{ 'sid': creds['sid'], 'clientSecret': creds['clientSecret'] }
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'medium' in data:
|
||||||
|
defer.returnValue(data)
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _bind_threepid(self, creds, mxid):
|
||||||
|
httpCli = PlainHttpClient(self.hs)
|
||||||
|
data = yield httpCli.post_urlencoded_get_json(
|
||||||
|
creds['idServer'],
|
||||||
|
"/_matrix/identity/api/v1/3pid/bind",
|
||||||
|
{ 'sid': creds['sid'], 'clientSecret': creds['clientSecret'], 'mxid':mxid }
|
||||||
|
)
|
||||||
|
defer.returnValue(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -49,6 +49,7 @@ class RoomCreationHandler(BaseRoomHandler):
|
||||||
SynapseError if the room ID was taken, couldn't be stored, or
|
SynapseError if the room ID was taken, couldn't be stored, or
|
||||||
something went horribly wrong.
|
something went horribly wrong.
|
||||||
"""
|
"""
|
||||||
|
self.ratelimit(user_id)
|
||||||
|
|
||||||
if "room_alias_name" in config:
|
if "room_alias_name" in config:
|
||||||
room_alias = RoomAlias.create_local(
|
room_alias = RoomAlias.create_local(
|
||||||
|
@ -110,8 +111,6 @@ class RoomCreationHandler(BaseRoomHandler):
|
||||||
servers=[self.hs.hostname],
|
servers=[self.hs.hostname],
|
||||||
)
|
)
|
||||||
|
|
||||||
federation_handler = self.hs.get_handlers().federation_handler
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def handle_event(event):
|
def handle_event(event):
|
||||||
snapshot = yield self.store.snapshot_room(
|
snapshot = yield self.store.snapshot_room(
|
||||||
|
@ -137,6 +136,17 @@ class RoomCreationHandler(BaseRoomHandler):
|
||||||
content={"name": name},
|
content={"name": name},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield handle_event(name_event)
|
||||||
|
elif room_alias:
|
||||||
|
name = room_alias.to_string()
|
||||||
|
name_event = self.event_factory.create_event(
|
||||||
|
etype=RoomNameEvent.TYPE,
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
required_power_level=5,
|
||||||
|
content={"name": name},
|
||||||
|
)
|
||||||
|
|
||||||
yield handle_event(name_event)
|
yield handle_event(name_event)
|
||||||
|
|
||||||
if "topic" in config:
|
if "topic" in config:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
|
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.web.client import _AgentBase, _URI, readBody
|
from twisted.internet.error import DNSLookupError
|
||||||
|
from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
|
|
||||||
from synapse.http.endpoint import matrix_endpoint
|
from synapse.http.endpoint import matrix_endpoint
|
||||||
|
@ -23,7 +24,9 @@ from synapse.util.async import sleep
|
||||||
|
|
||||||
from syutil.jsonutil import encode_canonical_json
|
from syutil.jsonutil import encode_canonical_json
|
||||||
|
|
||||||
from synapse.api.errors import CodeMessageException
|
from synapse.api.errors import CodeMessageException, SynapseError
|
||||||
|
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -43,6 +46,7 @@ _destination_mappings = {
|
||||||
class HttpClient(object):
|
class HttpClient(object):
|
||||||
""" Interface for talking json over http
|
""" Interface for talking json over http
|
||||||
"""
|
"""
|
||||||
|
RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
|
||||||
|
|
||||||
def put_json(self, destination, path, data):
|
def put_json(self, destination, path, data):
|
||||||
""" Sends the specifed json data using PUT
|
""" Sends the specifed json data using PUT
|
||||||
|
@ -142,13 +146,43 @@ class TwistedHttpClient(HttpClient):
|
||||||
destination = _destination_mappings[destination]
|
destination = _destination_mappings[destination]
|
||||||
|
|
||||||
logger.debug("get_json args: %s", args)
|
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)
|
query_bytes = urllib.urlencode(args, True)
|
||||||
|
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
|
||||||
|
|
||||||
response = yield self._create_request(
|
response = yield self._create_request(
|
||||||
destination.encode("ascii"),
|
destination.encode("ascii"),
|
||||||
"GET",
|
"GET",
|
||||||
path.encode("ascii"),
|
path.encode("ascii"),
|
||||||
query_bytes=query_bytes
|
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)
|
body = yield readBody(response)
|
||||||
|
@ -157,7 +191,8 @@ class TwistedHttpClient(HttpClient):
|
||||||
|
|
||||||
@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={},
|
||||||
|
retry_on_dns_fail=True):
|
||||||
""" Creates and sends a request to the given url
|
""" Creates and sends a request to the given url
|
||||||
"""
|
"""
|
||||||
headers_dict[b"User-Agent"] = [b"Synapse"]
|
headers_dict[b"User-Agent"] = [b"Synapse"]
|
||||||
|
@ -178,10 +213,7 @@ class TwistedHttpClient(HttpClient):
|
||||||
retries_left = 5
|
retries_left = 5
|
||||||
|
|
||||||
# TODO: setup and pass in an ssl_context to enable TLS
|
# TODO: setup and pass in an ssl_context to enable TLS
|
||||||
endpoint = matrix_endpoint(
|
endpoint = self._getEndpoint(reactor, destination);
|
||||||
reactor, destination, timeout=10,
|
|
||||||
ssl_context_factory=self.hs.tls_context_factory
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -199,6 +231,11 @@ class TwistedHttpClient(HttpClient):
|
||||||
logger.debug("Got response to %s", method)
|
logger.debug("Got response to %s", method)
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if not retry_on_dns_fail and isinstance(e, DNSLookupError):
|
||||||
|
logger.warn("DNS Lookup failed to %s with %s", destination,
|
||||||
|
e)
|
||||||
|
raise SynapseError(400, "Domain specified not found.")
|
||||||
|
|
||||||
logger.exception("Got error in _create_request")
|
logger.exception("Got error in _create_request")
|
||||||
_print_ex(e)
|
_print_ex(e)
|
||||||
|
|
||||||
|
@ -223,6 +260,17 @@ class TwistedHttpClient(HttpClient):
|
||||||
|
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
def _getEndpoint(self, reactor, destination):
|
||||||
|
return matrix_endpoint(
|
||||||
|
reactor, destination, timeout=10,
|
||||||
|
ssl_context_factory=self.hs.tls_context_factory
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlainHttpClient(TwistedHttpClient):
|
||||||
|
def _getEndpoint(self, reactor, destination):
|
||||||
|
return matrix_endpoint(reactor, destination, timeout=10)
|
||||||
|
|
||||||
|
|
||||||
def _print_ex(e):
|
def _print_ex(e):
|
||||||
if hasattr(e, "reasons") and e.reasons:
|
if hasattr(e, "reasons") and e.reasons:
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from .server import respond_with_json_bytes
|
||||||
|
|
||||||
|
from synapse.util.stringutils import random_string
|
||||||
|
from synapse.api.errors import (
|
||||||
|
cs_exception, SynapseError, CodeMessageException, Codes, cs_error
|
||||||
|
)
|
||||||
|
|
||||||
|
from twisted.protocols.basic import FileSender
|
||||||
|
from twisted.web import server, resource
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ContentRepoResource(resource.Resource):
|
||||||
|
"""Provides file uploading and downloading.
|
||||||
|
|
||||||
|
Uploads are POSTed to wherever this Resource is linked to. This resource
|
||||||
|
returns a "content token" which can be used to GET this content again. The
|
||||||
|
token is typically a path, but it may not be. Tokens can expire, be one-time
|
||||||
|
uses, etc.
|
||||||
|
|
||||||
|
In this case, the token is a path to the file and contains 3 interesting
|
||||||
|
sections:
|
||||||
|
- User ID base64d (for namespacing content to each user)
|
||||||
|
- random 24 char string
|
||||||
|
- Content type base64d (so we can return it when clients GET it)
|
||||||
|
|
||||||
|
"""
|
||||||
|
isLeaf = True
|
||||||
|
|
||||||
|
def __init__(self, hs, directory, auth, external_addr):
|
||||||
|
resource.Resource.__init__(self)
|
||||||
|
self.hs = hs
|
||||||
|
self.directory = directory
|
||||||
|
self.auth = auth
|
||||||
|
self.external_addr = external_addr.rstrip('/')
|
||||||
|
self.max_upload_size = hs.config.max_upload_size
|
||||||
|
|
||||||
|
if not os.path.isdir(self.directory):
|
||||||
|
os.mkdir(self.directory)
|
||||||
|
logger.info("ContentRepoResource : Created %s directory.",
|
||||||
|
self.directory)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def map_request_to_name(self, request):
|
||||||
|
# auth the user
|
||||||
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
# namespace all file uploads on the user
|
||||||
|
prefix = base64.urlsafe_b64encode(
|
||||||
|
auth_user.to_string()
|
||||||
|
).replace('=', '')
|
||||||
|
|
||||||
|
# use a random string for the main portion
|
||||||
|
main_part = random_string(24)
|
||||||
|
|
||||||
|
# suffix with a file extension if we can make one. This is nice to
|
||||||
|
# provide a hint to clients on the file information. We will also reuse
|
||||||
|
# this info to spit back the content type to the client.
|
||||||
|
suffix = ""
|
||||||
|
if request.requestHeaders.hasHeader("Content-Type"):
|
||||||
|
content_type = request.requestHeaders.getRawHeaders(
|
||||||
|
"Content-Type")[0]
|
||||||
|
suffix = "." + base64.urlsafe_b64encode(content_type)
|
||||||
|
if (content_type.split("/")[0].lower() in
|
||||||
|
["image", "video", "audio"]):
|
||||||
|
file_ext = content_type.split("/")[-1]
|
||||||
|
# be a little paranoid and only allow a-z
|
||||||
|
file_ext = re.sub("[^a-z]", "", file_ext)
|
||||||
|
suffix += "." + file_ext
|
||||||
|
|
||||||
|
file_name = prefix + main_part + suffix
|
||||||
|
file_path = os.path.join(self.directory, file_name)
|
||||||
|
logger.info("User %s is uploading a file to path %s",
|
||||||
|
auth_user.to_string(),
|
||||||
|
file_path)
|
||||||
|
|
||||||
|
# keep trying to make a non-clashing file, with a sensible max attempts
|
||||||
|
attempts = 0
|
||||||
|
while os.path.exists(file_path):
|
||||||
|
main_part = random_string(24)
|
||||||
|
file_name = prefix + main_part + suffix
|
||||||
|
file_path = os.path.join(self.directory, file_name)
|
||||||
|
attempts += 1
|
||||||
|
if attempts > 25: # really? Really?
|
||||||
|
raise SynapseError(500, "Unable to create file.")
|
||||||
|
|
||||||
|
defer.returnValue(file_path)
|
||||||
|
|
||||||
|
def render_GET(self, request):
|
||||||
|
# no auth here on purpose, to allow anyone to view, even across home
|
||||||
|
# servers.
|
||||||
|
|
||||||
|
# TODO: A little crude here, we could do this better.
|
||||||
|
filename = request.path.split('/')[-1]
|
||||||
|
# be paranoid
|
||||||
|
filename = re.sub("[^0-9A-z.-_]", "", filename)
|
||||||
|
|
||||||
|
file_path = self.directory + "/" + filename
|
||||||
|
|
||||||
|
logger.debug("Searching for %s", file_path)
|
||||||
|
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
# filename has the content type
|
||||||
|
base64_contentype = filename.split(".")[1]
|
||||||
|
content_type = base64.urlsafe_b64decode(base64_contentype)
|
||||||
|
logger.info("Sending file %s", file_path)
|
||||||
|
f = open(file_path, 'rb')
|
||||||
|
request.setHeader('Content-Type', content_type)
|
||||||
|
d = FileSender().beginFileTransfer(f, request)
|
||||||
|
|
||||||
|
# after the file has been sent, clean up and finish the request
|
||||||
|
def cbFinished(ignored):
|
||||||
|
f.close()
|
||||||
|
request.finish()
|
||||||
|
d.addCallback(cbFinished)
|
||||||
|
else:
|
||||||
|
respond_with_json_bytes(
|
||||||
|
request,
|
||||||
|
404,
|
||||||
|
json.dumps(cs_error("Not found", code=Codes.NOT_FOUND)),
|
||||||
|
send_cors=True)
|
||||||
|
|
||||||
|
return server.NOT_DONE_YET
|
||||||
|
|
||||||
|
def render_POST(self, request):
|
||||||
|
self._async_render(request)
|
||||||
|
return server.NOT_DONE_YET
|
||||||
|
|
||||||
|
def render_OPTIONS(self, request):
|
||||||
|
respond_with_json_bytes(request, 200, {}, send_cors=True)
|
||||||
|
return server.NOT_DONE_YET
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _async_render(self, request):
|
||||||
|
try:
|
||||||
|
# TODO: The checks here are a bit late. The content will have
|
||||||
|
# already been uploaded to a tmp file at this point
|
||||||
|
content_length = request.getHeader("Content-Length")
|
||||||
|
if content_length is None:
|
||||||
|
raise SynapseError(
|
||||||
|
msg="Request must specify a Content-Length", code=400
|
||||||
|
)
|
||||||
|
if int(content_length) > self.max_upload_size:
|
||||||
|
raise SynapseError(
|
||||||
|
msg="Upload request body is too large",
|
||||||
|
code=413,
|
||||||
|
)
|
||||||
|
|
||||||
|
fname = yield self.map_request_to_name(request)
|
||||||
|
|
||||||
|
# TODO I have a suspcious feeling this is just going to block
|
||||||
|
with open(fname, "wb") as f:
|
||||||
|
f.write(request.content.read())
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME (erikj): These should use constants.
|
||||||
|
file_name = os.path.basename(fname)
|
||||||
|
# FIXME: we can't assume what the public mounted path of the repo is
|
||||||
|
# ...plus self-signed SSL won't work to remote clients anyway
|
||||||
|
# ...and we can't assume that it's SSL anyway, as we might want to
|
||||||
|
# server it via the non-SSL listener...
|
||||||
|
url = "%s/_matrix/content/%s" % (
|
||||||
|
self.external_addr, file_name
|
||||||
|
)
|
||||||
|
|
||||||
|
respond_with_json_bytes(request, 200,
|
||||||
|
json.dumps({"content_token": url}),
|
||||||
|
send_cors=True)
|
||||||
|
|
||||||
|
except CodeMessageException as e:
|
||||||
|
logger.exception(e)
|
||||||
|
respond_with_json_bytes(request, e.code,
|
||||||
|
json.dumps(cs_exception(e)))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to store file: %s" % e)
|
||||||
|
respond_with_json_bytes(
|
||||||
|
request,
|
||||||
|
500,
|
||||||
|
json.dumps({"error": "Internal server error"}),
|
||||||
|
send_cors=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -18,22 +18,16 @@ from syutil.jsonutil import (
|
||||||
encode_canonical_json, encode_pretty_printed_json
|
encode_canonical_json, encode_pretty_printed_json
|
||||||
)
|
)
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
cs_exception, SynapseError, CodeMessageException, Codes, cs_error
|
cs_exception, SynapseError, CodeMessageException
|
||||||
)
|
)
|
||||||
from synapse.util.stringutils import random_string
|
|
||||||
|
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.protocols.basic import FileSender
|
|
||||||
from twisted.web import server, resource
|
from twisted.web import server, resource
|
||||||
from twisted.web.server import NOT_DONE_YET
|
from twisted.web.server import NOT_DONE_YET
|
||||||
from twisted.web.util import redirectTo
|
from twisted.web.util import redirectTo
|
||||||
|
|
||||||
import base64
|
|
||||||
import collections
|
import collections
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -140,7 +134,8 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
self._send_response(
|
self._send_response(
|
||||||
request,
|
request,
|
||||||
e.code,
|
e.code,
|
||||||
cs_exception(e)
|
cs_exception(e),
|
||||||
|
response_code_message=e.response_code_message
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
@ -150,7 +145,8 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
{"error": "Internal server error"}
|
{"error": "Internal server error"}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _send_response(self, request, code, response_json_object):
|
def _send_response(self, request, code, response_json_object,
|
||||||
|
response_code_message=None):
|
||||||
# could alternatively use request.notifyFinish() and flip a flag when
|
# could alternatively use request.notifyFinish() and flip a flag when
|
||||||
# the Deferred fires, but since the flag is RIGHT THERE it seems like
|
# the Deferred fires, but since the flag is RIGHT THERE it seems like
|
||||||
# a waste.
|
# a waste.
|
||||||
|
@ -166,7 +162,8 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
json_bytes = encode_pretty_printed_json(response_json_object)
|
json_bytes = encode_pretty_printed_json(response_json_object)
|
||||||
|
|
||||||
# TODO: Only enable CORS for the requests that need it.
|
# TODO: Only enable CORS for the requests that need it.
|
||||||
respond_with_json_bytes(request, code, json_bytes, send_cors=True)
|
respond_with_json_bytes(request, code, json_bytes, send_cors=True,
|
||||||
|
response_code_message=response_code_message)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _request_user_agent_is_curl(request):
|
def _request_user_agent_is_curl(request):
|
||||||
|
@ -195,162 +192,8 @@ class RootRedirect(resource.Resource):
|
||||||
return resource.Resource.getChild(self, name, request)
|
return resource.Resource.getChild(self, name, request)
|
||||||
|
|
||||||
|
|
||||||
class ContentRepoResource(resource.Resource):
|
def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
||||||
"""Provides file uploading and downloading.
|
response_code_message=None):
|
||||||
|
|
||||||
Uploads are POSTed to wherever this Resource is linked to. This resource
|
|
||||||
returns a "content token" which can be used to GET this content again. The
|
|
||||||
token is typically a path, but it may not be. Tokens can expire, be one-time
|
|
||||||
uses, etc.
|
|
||||||
|
|
||||||
In this case, the token is a path to the file and contains 3 interesting
|
|
||||||
sections:
|
|
||||||
- User ID base64d (for namespacing content to each user)
|
|
||||||
- random 24 char string
|
|
||||||
- Content type base64d (so we can return it when clients GET it)
|
|
||||||
|
|
||||||
"""
|
|
||||||
isLeaf = True
|
|
||||||
|
|
||||||
def __init__(self, hs, directory, auth):
|
|
||||||
resource.Resource.__init__(self)
|
|
||||||
self.hs = hs
|
|
||||||
self.directory = directory
|
|
||||||
self.auth = auth
|
|
||||||
|
|
||||||
if not os.path.isdir(self.directory):
|
|
||||||
os.mkdir(self.directory)
|
|
||||||
logger.info("ContentRepoResource : Created %s directory.",
|
|
||||||
self.directory)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def map_request_to_name(self, request):
|
|
||||||
# auth the user
|
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
|
||||||
|
|
||||||
# namespace all file uploads on the user
|
|
||||||
prefix = base64.urlsafe_b64encode(
|
|
||||||
auth_user.to_string()
|
|
||||||
).replace('=', '')
|
|
||||||
|
|
||||||
# use a random string for the main portion
|
|
||||||
main_part = random_string(24)
|
|
||||||
|
|
||||||
# suffix with a file extension if we can make one. This is nice to
|
|
||||||
# provide a hint to clients on the file information. We will also reuse
|
|
||||||
# this info to spit back the content type to the client.
|
|
||||||
suffix = ""
|
|
||||||
if request.requestHeaders.hasHeader("Content-Type"):
|
|
||||||
content_type = request.requestHeaders.getRawHeaders(
|
|
||||||
"Content-Type")[0]
|
|
||||||
suffix = "." + base64.urlsafe_b64encode(content_type)
|
|
||||||
if (content_type.split("/")[0].lower() in
|
|
||||||
["image", "video", "audio"]):
|
|
||||||
file_ext = content_type.split("/")[-1]
|
|
||||||
# be a little paranoid and only allow a-z
|
|
||||||
file_ext = re.sub("[^a-z]", "", file_ext)
|
|
||||||
suffix += "." + file_ext
|
|
||||||
|
|
||||||
file_name = prefix + main_part + suffix
|
|
||||||
file_path = os.path.join(self.directory, file_name)
|
|
||||||
logger.info("User %s is uploading a file to path %s",
|
|
||||||
auth_user.to_string(),
|
|
||||||
file_path)
|
|
||||||
|
|
||||||
# keep trying to make a non-clashing file, with a sensible max attempts
|
|
||||||
attempts = 0
|
|
||||||
while os.path.exists(file_path):
|
|
||||||
main_part = random_string(24)
|
|
||||||
file_name = prefix + main_part + suffix
|
|
||||||
file_path = os.path.join(self.directory, file_name)
|
|
||||||
attempts += 1
|
|
||||||
if attempts > 25: # really? Really?
|
|
||||||
raise SynapseError(500, "Unable to create file.")
|
|
||||||
|
|
||||||
defer.returnValue(file_path)
|
|
||||||
|
|
||||||
def render_GET(self, request):
|
|
||||||
# no auth here on purpose, to allow anyone to view, even across home
|
|
||||||
# servers.
|
|
||||||
|
|
||||||
# TODO: A little crude here, we could do this better.
|
|
||||||
filename = request.path.split('/')[-1]
|
|
||||||
# be paranoid
|
|
||||||
filename = re.sub("[^0-9A-z.-_]", "", filename)
|
|
||||||
|
|
||||||
file_path = self.directory + "/" + filename
|
|
||||||
|
|
||||||
logger.debug("Searching for %s", file_path)
|
|
||||||
|
|
||||||
if os.path.isfile(file_path):
|
|
||||||
# filename has the content type
|
|
||||||
base64_contentype = filename.split(".")[1]
|
|
||||||
content_type = base64.urlsafe_b64decode(base64_contentype)
|
|
||||||
logger.info("Sending file %s", file_path)
|
|
||||||
f = open(file_path, 'rb')
|
|
||||||
request.setHeader('Content-Type', content_type)
|
|
||||||
d = FileSender().beginFileTransfer(f, request)
|
|
||||||
|
|
||||||
# after the file has been sent, clean up and finish the request
|
|
||||||
def cbFinished(ignored):
|
|
||||||
f.close()
|
|
||||||
request.finish()
|
|
||||||
d.addCallback(cbFinished)
|
|
||||||
else:
|
|
||||||
respond_with_json_bytes(
|
|
||||||
request,
|
|
||||||
404,
|
|
||||||
json.dumps(cs_error("Not found", code=Codes.NOT_FOUND)),
|
|
||||||
send_cors=True)
|
|
||||||
|
|
||||||
return server.NOT_DONE_YET
|
|
||||||
|
|
||||||
def render_POST(self, request):
|
|
||||||
self._async_render(request)
|
|
||||||
return server.NOT_DONE_YET
|
|
||||||
|
|
||||||
def render_OPTIONS(self, request):
|
|
||||||
respond_with_json_bytes(request, 200, {}, send_cors=True)
|
|
||||||
return server.NOT_DONE_YET
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def _async_render(self, request):
|
|
||||||
try:
|
|
||||||
fname = yield self.map_request_to_name(request)
|
|
||||||
|
|
||||||
# TODO I have a suspcious feeling this is just going to block
|
|
||||||
with open(fname, "wb") as f:
|
|
||||||
f.write(request.content.read())
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME (erikj): These should use constants.
|
|
||||||
file_name = os.path.basename(fname)
|
|
||||||
# FIXME: we can't assume what the public mounted path of the repo is
|
|
||||||
# ...plus self-signed SSL won't work to remote clients anyway
|
|
||||||
# ...and we can't assume that it's SSL anyway, as we might want to
|
|
||||||
# server it via the non-SSL listener...
|
|
||||||
url = "https://%s/_matrix/content/%s" % (
|
|
||||||
self.hs.domain_with_port, file_name
|
|
||||||
)
|
|
||||||
|
|
||||||
respond_with_json_bytes(request, 200,
|
|
||||||
json.dumps({"content_token": url}),
|
|
||||||
send_cors=True)
|
|
||||||
|
|
||||||
except CodeMessageException as e:
|
|
||||||
logger.exception(e)
|
|
||||||
respond_with_json_bytes(request, e.code,
|
|
||||||
json.dumps(cs_exception(e)))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Failed to store file: %s" % e)
|
|
||||||
respond_with_json_bytes(
|
|
||||||
request,
|
|
||||||
500,
|
|
||||||
json.dumps({"error": "Internal server error"}),
|
|
||||||
send_cors=True)
|
|
||||||
|
|
||||||
|
|
||||||
def respond_with_json_bytes(request, code, json_bytes, send_cors=False):
|
|
||||||
"""Sends encoded JSON in response to the given request.
|
"""Sends encoded JSON in response to the given request.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -362,7 +205,7 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False):
|
||||||
Returns:
|
Returns:
|
||||||
twisted.web.server.NOT_DONE_YET"""
|
twisted.web.server.NOT_DONE_YET"""
|
||||||
|
|
||||||
request.setResponseCode(code)
|
request.setResponseCode(code, message=response_code_message)
|
||||||
request.setHeader(b"Content-Type", b"application/json")
|
request.setHeader(b"Content-Type", b"application/json")
|
||||||
|
|
||||||
if send_cors:
|
if send_cors:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -167,7 +167,12 @@ class Notifier(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def eb(failure):
|
def eb(failure):
|
||||||
logger.exception("Failed to notify listener", failure)
|
logger.error("Failed to notify listener",
|
||||||
|
exc_info=(
|
||||||
|
failure.type,
|
||||||
|
failure.value,
|
||||||
|
failure.getTracebackObject())
|
||||||
|
)
|
||||||
|
|
||||||
yield defer.DeferredList(
|
yield defer.DeferredList(
|
||||||
[notify(l).addErrback(eb) for l in listeners]
|
[notify(l).addErrback(eb) for l in listeners]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError, Codes
|
||||||
from base import RestServlet, client_path_pattern
|
from base import RestServlet, client_path_pattern
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -44,8 +45,10 @@ class ClientDirectoryServer(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_alias):
|
def on_PUT(self, request, room_alias):
|
||||||
# TODO(erikj): Exceptions
|
content = _parse_json(request)
|
||||||
content = json.loads(request.content.read())
|
if not "room_id" in content:
|
||||||
|
raise SynapseError(400, "Missing room_id key",
|
||||||
|
errcode=Codes.BAD_JSON)
|
||||||
|
|
||||||
logger.debug("Got content: %s", content)
|
logger.debug("Got content: %s", content)
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ class ClientDirectoryServer(RestServlet):
|
||||||
logger.debug("Got room name: %s", room_alias.to_string())
|
logger.debug("Got room name: %s", room_alias.to_string())
|
||||||
|
|
||||||
room_id = content["room_id"]
|
room_id = content["room_id"]
|
||||||
servers = content["servers"]
|
servers = content["servers"] if "servers" in content else None
|
||||||
|
|
||||||
logger.debug("Got room_id: %s", room_id)
|
logger.debug("Got room_id: %s", room_id)
|
||||||
logger.debug("Got servers: %s", servers)
|
logger.debug("Got servers: %s", servers)
|
||||||
|
@ -68,7 +71,20 @@ class ClientDirectoryServer(RestServlet):
|
||||||
yield dir_handler.create_association(
|
yield dir_handler.create_association(
|
||||||
room_alias, room_id, servers
|
room_alias, room_id, servers
|
||||||
)
|
)
|
||||||
|
except SynapseError as e:
|
||||||
|
raise e
|
||||||
except:
|
except:
|
||||||
logger.exception("Failed to create association")
|
logger.exception("Failed to create association")
|
||||||
|
|
||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_json(request):
|
||||||
|
try:
|
||||||
|
content = json.loads(request.content.read())
|
||||||
|
if type(content) != dict:
|
||||||
|
raise SynapseError(400, "Content must be a JSON object.",
|
||||||
|
errcode=Codes.NOT_JSON)
|
||||||
|
return content
|
||||||
|
except ValueError:
|
||||||
|
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -17,11 +17,12 @@
|
||||||
"""
|
"""
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError
|
||||||
from base import RestServlet, client_path_pattern
|
from base import RestServlet, client_path_pattern
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import urllib
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ class PresenceStatusRestServlet(RestServlet):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
state = yield self.handlers.presence_handler.get_state(
|
state = yield self.handlers.presence_handler.get_state(
|
||||||
|
@ -42,25 +44,26 @@ class PresenceStatusRestServlet(RestServlet):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, user_id):
|
def on_PUT(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
state = {}
|
state = {}
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
|
|
||||||
# Legacy handling
|
state["presence"] = content.pop("presence")
|
||||||
if "state" in content:
|
|
||||||
state["presence"] = content.pop("state")
|
|
||||||
else:
|
|
||||||
state["presence"] = content.pop("presence")
|
|
||||||
|
|
||||||
if "status_msg" in content:
|
if "status_msg" in content:
|
||||||
state["status_msg"] = content.pop("status_msg")
|
state["status_msg"] = content.pop("status_msg")
|
||||||
|
if not isinstance(state["status_msg"], basestring):
|
||||||
|
raise SynapseError(400, "status_msg must be a string.")
|
||||||
|
|
||||||
if content:
|
if content:
|
||||||
raise KeyError()
|
raise KeyError()
|
||||||
|
except SynapseError as e:
|
||||||
|
raise e
|
||||||
except:
|
except:
|
||||||
defer.returnValue((400, "Unable to parse state"))
|
raise SynapseError(400, "Unable to parse state")
|
||||||
|
|
||||||
yield self.handlers.presence_handler.set_state(
|
yield self.handlers.presence_handler.set_state(
|
||||||
target_user=user, auth_user=auth_user, state=state)
|
target_user=user, auth_user=auth_user, state=state)
|
||||||
|
@ -77,13 +80,14 @@ class PresenceListRestServlet(RestServlet):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
if not user.is_mine:
|
if not user.is_mine:
|
||||||
defer.returnValue((400, "User not hosted on this Home Server"))
|
raise SynapseError(400, "User not hosted on this Home Server")
|
||||||
|
|
||||||
if auth_user != user:
|
if auth_user != user:
|
||||||
defer.returnValue((400, "Cannot get another user's presence list"))
|
raise SynapseError(400, "Cannot get another user's presence list")
|
||||||
|
|
||||||
presence = yield self.handlers.presence_handler.get_presence_list(
|
presence = yield self.handlers.presence_handler.get_presence_list(
|
||||||
observer_user=user, accepted=True)
|
observer_user=user, accepted=True)
|
||||||
|
@ -97,31 +101,40 @@ class PresenceListRestServlet(RestServlet):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, user_id):
|
def on_POST(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
if not user.is_mine:
|
if not user.is_mine:
|
||||||
defer.returnValue((400, "User not hosted on this Home Server"))
|
raise SynapseError(400, "User not hosted on this Home Server")
|
||||||
|
|
||||||
if auth_user != user:
|
if auth_user != user:
|
||||||
defer.returnValue((
|
raise SynapseError(
|
||||||
400, "Cannot modify another user's presence list"))
|
400, "Cannot modify another user's presence list")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
except:
|
except:
|
||||||
logger.exception("JSON parse error")
|
logger.exception("JSON parse error")
|
||||||
defer.returnValue((400, "Unable to parse content"))
|
raise SynapseError(400, "Unable to parse content")
|
||||||
|
|
||||||
deferreds = []
|
deferreds = []
|
||||||
|
|
||||||
if "invite" in content:
|
if "invite" in content:
|
||||||
for u in content["invite"]:
|
for u in content["invite"]:
|
||||||
|
if not isinstance(u, basestring):
|
||||||
|
raise SynapseError(400, "Bad invite value.")
|
||||||
|
if len(u) == 0:
|
||||||
|
continue
|
||||||
invited_user = self.hs.parse_userid(u)
|
invited_user = self.hs.parse_userid(u)
|
||||||
deferreds.append(self.handlers.presence_handler.send_invite(
|
deferreds.append(self.handlers.presence_handler.send_invite(
|
||||||
observer_user=user, observed_user=invited_user))
|
observer_user=user, observed_user=invited_user))
|
||||||
|
|
||||||
if "drop" in content:
|
if "drop" in content:
|
||||||
for u in content["drop"]:
|
for u in content["drop"]:
|
||||||
|
if not isinstance(u, basestring):
|
||||||
|
raise SynapseError(400, "Bad drop value.")
|
||||||
|
if len(u) == 0:
|
||||||
|
continue
|
||||||
dropped_user = self.hs.parse_userid(u)
|
dropped_user = self.hs.parse_userid(u)
|
||||||
deferreds.append(self.handlers.presence_handler.drop(
|
deferreds.append(self.handlers.presence_handler.drop(
|
||||||
observer_user=user, observed_user=dropped_user))
|
observer_user=user, observed_user=dropped_user))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -19,6 +19,7 @@ from twisted.internet import defer
|
||||||
from base import RestServlet, client_path_pattern
|
from base import RestServlet, client_path_pattern
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
class ProfileDisplaynameRestServlet(RestServlet):
|
class ProfileDisplaynameRestServlet(RestServlet):
|
||||||
|
@ -26,6 +27,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
displayname = yield self.handlers.profile_handler.get_displayname(
|
displayname = yield self.handlers.profile_handler.get_displayname(
|
||||||
|
@ -37,6 +39,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, user_id):
|
def on_PUT(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -59,6 +62,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
avatar_url = yield self.handlers.profile_handler.get_avatar_url(
|
avatar_url = yield self.handlers.profile_handler.get_avatar_url(
|
||||||
|
@ -70,6 +74,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, user_id):
|
def on_PUT(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -92,6 +97,7 @@ class ProfileRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
user_id = urllib.unquote(user_id)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
displayname = yield self.handlers.profile_handler.get_displayname(
|
displayname = yield self.handlers.profile_handler.get_displayname(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -47,10 +47,15 @@ class RegisterRestServlet(RestServlet):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # user_id is optional
|
pass # user_id is optional
|
||||||
|
|
||||||
|
threepidCreds = None
|
||||||
|
if 'threepidCreds' in register_json:
|
||||||
|
threepidCreds = register_json['threepidCreds']
|
||||||
|
|
||||||
handler = self.handlers.registration_handler
|
handler = self.handlers.registration_handler
|
||||||
(user_id, token) = yield handler.register(
|
(user_id, token) = yield handler.register(
|
||||||
localpart=desired_user_id,
|
localpart=desired_user_id,
|
||||||
password=password)
|
password=password,
|
||||||
|
threepidCreds=threepidCreds)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -388,7 +388,7 @@ class RoomMembershipRestServlet(RestServlet):
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms/$roomid/[invite|join|leave]
|
# /rooms/$roomid/[invite|join|leave]
|
||||||
PATTERN = ("/rooms/(?P<room_id>[^/]*)/" +
|
PATTERN = ("/rooms/(?P<room_id>[^/]*)/" +
|
||||||
"(?P<membership_action>join|invite|leave)")
|
"(?P<membership_action>join|invite|leave|ban|kick)")
|
||||||
register_txn_path(self, PATTERN, http_server)
|
register_txn_path(self, PATTERN, http_server)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -399,11 +399,14 @@ class RoomMembershipRestServlet(RestServlet):
|
||||||
|
|
||||||
# target user is you unless it is an invite
|
# target user is you unless it is an invite
|
||||||
state_key = user.to_string()
|
state_key = user.to_string()
|
||||||
if membership_action == "invite":
|
if membership_action in ["invite", "ban", "kick"]:
|
||||||
if "user_id" not in content:
|
if "user_id" not in content:
|
||||||
raise SynapseError(400, "Missing user_id key.")
|
raise SynapseError(400, "Missing user_id key.")
|
||||||
state_key = content["user_id"]
|
state_key = content["user_id"]
|
||||||
|
|
||||||
|
if membership_action == "kick":
|
||||||
|
membership_action = "leave"
|
||||||
|
|
||||||
event = self.event_factory.create_event(
|
event = self.event_factory.create_event(
|
||||||
etype=RoomMemberEvent.TYPE,
|
etype=RoomMemberEvent.TYPE,
|
||||||
content={"membership": unicode(membership_action)},
|
content={"membership": unicode(membership_action)},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -32,6 +32,7 @@ from synapse.util import Clock
|
||||||
from synapse.util.distributor import Distributor
|
from synapse.util.distributor import Distributor
|
||||||
from synapse.util.lockutils import LockManager
|
from synapse.util.lockutils import LockManager
|
||||||
from synapse.streams.events import EventSources
|
from synapse.streams.events import EventSources
|
||||||
|
from synapse.api.ratelimiting import Ratelimiter
|
||||||
|
|
||||||
|
|
||||||
class BaseHomeServer(object):
|
class BaseHomeServer(object):
|
||||||
|
@ -73,6 +74,7 @@ class BaseHomeServer(object):
|
||||||
'resource_for_web_client',
|
'resource_for_web_client',
|
||||||
'resource_for_content_repo',
|
'resource_for_content_repo',
|
||||||
'event_sources',
|
'event_sources',
|
||||||
|
'ratelimiter',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, hostname, **kwargs):
|
def __init__(self, hostname, **kwargs):
|
||||||
|
@ -190,6 +192,9 @@ class HomeServer(BaseHomeServer):
|
||||||
def build_event_sources(self):
|
def build_event_sources(self):
|
||||||
return EventSources(self)
|
return EventSources(self)
|
||||||
|
|
||||||
|
def build_ratelimiter(self):
|
||||||
|
return Ratelimiter()
|
||||||
|
|
||||||
def register_servlets(self):
|
def register_servlets(self):
|
||||||
""" Register all servlets associated with this HomeServer.
|
""" Register all servlets associated with this HomeServer.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014 matrix.org
|
# Copyright 2014 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -179,6 +179,18 @@ class StateHandler(object):
|
||||||
key=lambda x: x.depth
|
key=lambda x: x.depth
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not hasattr(missing_prev, "prev_state_id"):
|
||||||
|
# FIXME Hmm
|
||||||
|
# temporary fallback
|
||||||
|
for algo in conflict_res:
|
||||||
|
new_res, curr_res = algo(new_branch, current_branch)
|
||||||
|
|
||||||
|
if new_res < curr_res:
|
||||||
|
defer.returnValue(False)
|
||||||
|
elif new_res > curr_res:
|
||||||
|
defer.returnValue(True)
|
||||||
|
return
|
||||||
|
|
||||||
pdu_id = missing_prev.prev_state_id
|
pdu_id = missing_prev.prev_state_id
|
||||||
origin = missing_prev.prev_state_origin
|
origin = missing_prev.prev_state_origin
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue