Merge branch 'develop' of github.com:matrix-org/synapse into develop

Conflicts:
	synapse/http/client.py
This commit is contained in:
David Baker 2014-09-03 18:25:17 +01:00
commit d72ce4da64
168 changed files with 1898 additions and 789 deletions

View file

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

View file

@ -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,8 +193,10 @@ 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:
@ -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",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,48 +30,52 @@ ecosystem to communicate with one another.
The principles that Matrix attempts to follow are: The principles that Matrix attempts to follow are:
- Pragmatic Web-friendly APIs (i.e. JSON over REST) - Pragmatic Web-friendly APIs (i.e. JSON over REST)
- Keep It Simple & Stupid - Keep It Simple & Stupid
+ provide a simple architecture with minimal third-party dependencies. + provide a simple architecture with minimal third-party dependencies.
- Fully open: - Fully open:
+ Fully open federation - anyone should be able to participate in the global Matrix network + Fully open federation - anyone should be able to participate in the global
+ Fully open standard - publicly documented standard with no IP or patent licensing encumbrances Matrix network
+ Fully open source reference implementation - liberally-licensed example implementations with no + Fully open standard - publicly documented standard with no IP or patent
IP or patent licensing encumbrances licensing encumbrances
+ Fully open source reference implementation - liberally-licensed example
implementations with no IP or patent licensing encumbrances
- Empowering the end-user - Empowering the end-user
+ The user should be able to choose the server and clients they use + The user should be able to choose the server and clients they use
+ The user should be control how private their communication is + The user should be control how private their communication is
+ The user should know precisely where their data is stored + The user should know precisely where their data is stored
- Fully decentralised - no single points of control over conversations or the network as a whole - Fully decentralised - no single points of control over conversations or the
- Learning from history to avoid repeating it network as a whole
- Learning from history to avoid repeating it
+ Trying to take the best aspects of XMPP, SIP, IRC, SMTP, IMAP and NNTP whilst trying to avoid their failings + Trying to take the best aspects of XMPP, SIP, IRC, SMTP, IMAP and NNTP
whilst trying to avoid their failings
The functionality that Matrix provides includes: The functionality that Matrix provides includes:
- Creation and management of fully distributed chat rooms with no - Creation and management of fully distributed chat rooms with no
single points of control or failure single points of control or failure
- Eventually-consistent cryptographically secure synchronisation of room - Eventually-consistent cryptographically secure 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 end-to-end encryption
- Extensible user management (inviting, joining, leaving, kicking, banning) - Extensible user management (inviting, joining, leaving, kicking, banning)
mediated by a power-level based user privilege system. mediated by a power-level based user privilege system.
- Extensible room state management (room naming, aliasing, topics, bans) - Extensible room state management (room naming, aliasing, topics, bans)
- Extensible user profile management (avatars, displaynames, etc) - Extensible user profile management (avatars, displaynames, etc)
- Managing user accounts (registration, login, logout) - Managing user accounts (registration, login, logout)
- Use of 3rd Party IDs (3PIDs) such as email addresses, phone numbers, - Use of 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.
- Trusted federation of Identity servers for: - Trusted federation of Identity servers for:
+ Publishing user public keys for PKI + Publishing user public keys for PKI
+ Mapping of 3PIDs to Matrix IDs + Mapping of 3PIDs to Matrix IDs
The end goal of Matrix is to be a ubiquitous messaging layer for synchronising The end goal of Matrix is to be a ubiquitous messaging layer for synchronising
arbitrary data between sets of people, devices and services - be that for instant arbitrary data between sets of people, devices and services - be that for instant
@ -428,9 +432,10 @@ event also has a ``creator`` key which contains the user ID of the room
creator. It will also generate several other events in order to manage creator. It will also generate several other events in order to manage
permissions in this room. This includes: permissions in this room. This includes:
- ``m.room.power_levels`` : Sets the authority of the room creator. - ``m.room.power_levels`` : Sets the power levels of users.
- ``m.room.join_rules`` : Whether the room is "invite-only" or not. - ``m.room.join_rules`` : Whether the room is "invite-only" or not.
- ``m.room.add_state_level`` - ``m.room.add_state_level``: The power level required in order to
add new state to the room (as opposed to updating exisiting state)
- ``m.room.send_event_level`` : The power level required in order to - ``m.room.send_event_level`` : The power level required in order to
send a message in this room. send a message in this room.
- ``m.room.ops_level`` : The power level required in order to kick or - ``m.room.ops_level`` : The power level required in order to kick or
@ -463,6 +468,27 @@ Permissions
Link through to respective sections where necessary. How does this tie in with permissions, e.g. Link through to respective sections where necessary. How does this tie in with permissions, e.g.
give example of creating a read-only room. give example of creating a read-only room.
Permissions for rooms are done via the concept of power levels - to do any
action in a room a user must have a suitable power level.
Power levels for users are defined in ``m.room.power_levels``, where both
a default and specific users' power levels can be set. By default all users
have a power level of 0.
State events may contain a ``required_power_level`` key, which indicates the
minimum power a user must have before they can update that state key. The only
exception to this is when a user leaves a room.
To perform certain actions there are additional power level requirements
defined in the following state events:
- ``m.room.send_event_level`` defines the minimum level for sending non-state
events. Defaults to 5.
- ``m.room.add_state_level`` defines the minimum level for adding new state,
rather than updating existing state. Defaults to 5.
- ``m.room.ops_level`` defines the minimum levels to ban and kick other users.
This defaults to a kick and ban levels of 5 each.
Joining rooms Joining rooms
------------- -------------
@ -797,89 +823,88 @@ prefixed with ``m.``
to force this state change directly will fail. See the `Rooms`_ section for how to to force this state change directly will fail. See the `Rooms`_ section for how to
use the membership APIs. use the membership APIs.
``m.room.config`` ``m.room.create``
Summary: Summary:
The room config. The first event in the room.
Type: Type:
State event State event
JSON format: JSON format:
TODO ``{ "creator": "string"}``
Example: Example:
TODO ``{ "creator": "@user:example.com" }``
Description: Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what This is the first event in a room and cannot be changed. It acts as the
root of all other events.
``m.room.invite_join``
Summary:
TODO.
Type:
State event
JSON format:
TODO
Example:
TODO
Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what
``m.room.join_rules`` ``m.room.join_rules``
Summary: Summary:
TODO. Descripes how/if people are allowed to join.
Type: Type:
State event State event
JSON format: JSON format:
TODO ``{ "join_rule": "enum [ public|knock|invite|private ]" }``
Example: Example:
TODO ``{ "join_rule": "public" }``
Description: Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what TODO : Use docs/models/rooms.rst
``m.room.power_levels`` ``m.room.power_levels``
Summary: Summary:
TODO. Defines the power levels of users in the room.
Type: Type:
State event State event
JSON format: JSON format:
TODO ``{ "<user_id>": <int>, ..., "default": <int>}``
Example: Example:
TODO ``{ "@user:example.com": 5, "@user2:example.com": 10, "default": 0 }``
Description: Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what If a user is in the list, then they have the associated power level.
Otherwise they have the default level. If not ``default`` key is supplied,
it is assumed to be 0.
``m.room.add_state_level`` ``m.room.add_state_level``
Summary: Summary:
TODO. Defines the minimum power level a user needs to add state.
Type: Type:
State event State event
JSON format: JSON format:
TODO ``{ "level": <int> }``
Example: Example:
TODO ``{ "level": 5 }``
Description: Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what To add a new piece of state to the room a user must have the given power
level. This does not apply to updating current state, which is goverened
by the ``required_power_level`` event key.
``m.room.send_event_level`` ``m.room.send_event_level``
Summary: Summary:
TODO. Defines the minimum power level a user needs to send an event.
Type: Type:
State event State event
JSON format: JSON format:
TODO ``{ "level": <int> }``
Example: Example:
TODO ``{ "level": 0 }``
Description: Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what To send a new event into the room a user must have at least this power
level. This allows ops to make the room read only by increasing this level,
or muting individual users by lowering their power level below this
threshold.
``m.room.ops_levels`` ``m.room.ops_levels``
Summary: Summary:
TODO. Defines the minimum power levels that a user must have before they can
kick and/or ban other users.
Type: Type:
State event State event
JSON format: JSON format:
TODO ``{ "ban_level": <int>, "kick_level": <int> }``
Example: Example:
TODO ``{ "ban_level": 5, "kick_level": 5 }``
Description: Description:
TODO : What it represents, What are the valid keys / values and what they represent, When is this event emitted and by what This defines who can ban and/or kick people in the room. Most of the time
``ban_level`` will be greater than or equal to ``kick_level`` since
banning is more severe than kicking.
``m.room.message`` ``m.room.message``
Summary: Summary:
@ -1485,6 +1510,31 @@ Each transaction has:
- An origin and destination server name. - An origin and destination server name.
- A list of "previous IDs". - A list of "previous IDs".
- A list of PDUs and EDUs - the actual message payload that the Transaction carries. - A list of PDUs and EDUs - the actual message payload that the Transaction carries.
``origin``
Type:
String
Description:
DNS name of homeserver making this transaction.
``ts``
Type:
Integer
Description:
Timestamp in milliseconds on originating homeserver when this transaction
started.
``previous_ids``
Type:
List of strings
Description:
List of transactions that were sent immediately prior to this transaction.
``pdus``
Type:
List of Objects.
Description:
List of updates contained in this transaction.
:: ::
@ -1526,8 +1576,98 @@ All PDUs have:
- A list of other PDU IDs that have been seen recently on that context (regardless of which origin - A list of other PDU IDs that have been seen recently on that context (regardless of which origin
sent them) sent them)
[[TODO(paul): Update this structure so that 'pdu_id' is a two-element ``context``
[origin,ref] pair like the prev_pdus are]] Type:
String
Description:
Event context identifier
``origin``
Type:
String
Description:
DNS name of homeserver that created this PDU.
``pdu_id``
Type:
String
Description:
Unique identifier for PDU within the context for the originating homeserver
``ts``
Type:
Integer
Description:
Timestamp in milliseconds on originating homeserver when this PDU was created.
``pdu_type``
Type:
String
Description:
PDU event type.
``prev_pdus``
Type:
List of pairs of strings
Description:
The originating homeserver and PDU ids of the most recent PDUs the
homeserver was aware of for this context when it made this PDU.
``depth``
Type:
Integer
Description:
The maximum depth of the previous PDUs plus one.
.. TODO paul
[[TODO(paul): Update this structure so that 'pdu_id' is a two-element
[origin,ref] pair like the prev_pdus are]]
For state updates:
``is_state``
Type:
Boolean
Description:
True if this PDU is updating state.
``state_key``
Type:
String
Description:
Optional key identifying the updated state within the context.
``power_level``
Type:
Integer
Description:
The asserted power level of the user performing the update.
``min_update``
Type:
Integer
Description:
The required power level needed to replace this update.
``prev_state_id``
Type:
String
Description:
PDU event type.
``prev_state_origin``
Type:
String
Description:
The PDU id of the update this replaces.
``user``
Type:
String
Description:
The user updating the state.
:: ::
@ -1568,12 +1708,13 @@ keys exist to support this:
"prev_state_id":TODO "prev_state_id":TODO
"prev_state_origin":TODO} "prev_state_origin":TODO}
[[TODO(paul): At this point we should probably have a long description of how .. TODO paul
State management works, with descriptions of clobbering rules, power levels, etc [[TODO(paul): At this point we should probably have a long description of how
etc... But some of that detail is rather up-in-the-air, on the whiteboard, and State management works, with descriptions of clobbering rules, power levels, etc
so on. This part needs refining. And writing in its own document as the details etc... But some of that detail is rather up-in-the-air, on the whiteboard, and
relate to the server/system as a whole, not specifically to server-server so on. This part needs refining. And writing in its own document as the details
federation.]] relate to the server/system as a whole, not specifically to server-server
federation.]]
EDUs, by comparison to PDUs, do not have an ID, a context, or a list of EDUs, by comparison to PDUs, do not have an ID, a context, or a list of
"previous" IDs. The only mandatory fields for these are the type, origin and "previous" IDs. The only mandatory fields for these are the type, origin and
@ -1585,6 +1726,79 @@ destination home server names, and the actual nested content.
"origin":"blue", "origin":"blue",
"destination":"orange", "destination":"orange",
"content":...} "content":...}
Protocol URLs
=============
.. WARNING::
This section may be misleading or inaccurate.
All these URLs are namespaced within a prefix of::
/_matrix/federation/v1/...
For active pushing of messages representing live activity "as it happens"::
PUT .../send/:transaction_id/
Body: JSON encoding of a single Transaction
Response: TODO
The transaction_id path argument will override any ID given in the JSON body.
The destination name will be set to that of the receiving server itself. Each
embedded PDU in the transaction body will be processed.
To fetch a particular PDU::
GET .../pdu/:origin/:pdu_id/
Response: JSON encoding of a single Transaction containing one PDU
Retrieves a given PDU from the server. The response will contain a single new
Transaction, inside which will be the requested PDU.
To fetch all the state of a given context::
GET .../state/:context/
Response: JSON encoding of a single Transaction containing multiple PDUs
Retrieves a snapshot of the entire current state of the given context. The
response will contain a single Transaction, inside which will be a list of
PDUs that encode the state.
To backfill events on a given context::
GET .../backfill/:context/
Query args: v, limit
Response: JSON encoding of a single Transaction containing multiple PDUs
Retrieves a sliding-window history of previous PDUs that occurred on the
given context. Starting from the PDU ID(s) given in the "v" argument, the
PDUs that preceeded it are retrieved, up to a total number given by the
"limit" argument. These are then returned in a new Transaction containing all
off the PDUs.
To stream events all the events::
GET .../pull/
Query args: origin, v
Response: JSON encoding of a single Transaction consisting of multiple PDUs
Retrieves all of the transactions later than any version given by the "v"
arguments.
To make a query::
GET .../query/:query_type
Query args: as specified by the individual query types
Response: JSON encoding of a response object
Performs a single query request on the receiving home server. The Query Type
part of the path specifies the kind of query being made, and its query
arguments have a meaning specific to that kind of query. The response is a
JSON-encoded object whose meaning also depends on the kind of query.
Backfilling Backfilling
----------- -----------
@ -1604,9 +1818,133 @@ SRV Records
Security Security
======== ========
.. NOTE:: .. NOTE::
This section is a work in progress. This section is a work in progress.
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.
Rate limiting Rate limiting
------------- -------------
Home servers SHOULD implement rate limiting to reduce the risk of being overloaded. If a Home servers SHOULD implement rate limiting to reduce the risk of being overloaded. If a
@ -1660,57 +1998,121 @@ Glossary
.. NOTE:: .. NOTE::
This section is a work in progress. This section is a work in progress.
.. TODO Backfilling:
- domain specific words/acronyms with definitions The process of synchronising historic state from one home server to another,
to backfill the event storage so that scrollback can be presented to the
client(s). Not to be confused with pagination.
Context:
A single human-level entity of interest (currently, a chat room)
EDU (Ephemeral Data Unit):
A message that relates directly to a given pair of home servers that are
exchanging it. EDUs are short-lived messages that related only to one single
pair of servers; they are not persisted for a long time and are not forwarded
on to other servers. Because of this, they have no internal ID nor previous
EDUs reference chain.
Event:
A record of activity that records a single thing that happened on to a context
(currently, a chat room). These are the "chat messages" that Synapse makes
available.
PDU (Persistent Data Unit):
A message that relates to a single context, irrespective of the server that
is communicating it. PDUs either encode a single Event, or a single State
change. A PDU is referred to by its PDU ID; the pair of its origin server
and local reference from that server.
PDU ID:
The pair of PDU Origin and PDU Reference, that together globally uniquely
refers to a specific PDU.
PDU Origin:
The name of the origin server that generated a given PDU. This may not be the
server from which it has been received, due to the way they are copied around
from server to server. The origin always records the original server that
created it.
PDU Reference:
A local ID used to refer to a specific PDU from a given origin server. These
references are opaque at the protocol level, but may optionally have some
structured meaning within a given origin server or implementation.
Presence:
The concept of whether a user is currently online, how available they declare
they are, and so on. See also: doc/model/presence
Profile:
A set of metadata about a user, such as a display name, provided for the
benefit of other users. See also: doc/model/profiles
Room ID:
An opaque string (of as-yet undecided format) that identifies a particular
room and used in PDUs referring to it.
Room Alias:
A human-readable string of the form #name:some.domain that users can use as a
pointer to identify a room; a Directory Server will map this to its Room ID
State:
A set of metadata maintained about a Context, which is replicated among the
servers in addition to the history of Events.
User ID: User ID:
An opaque ID which identifies an end-user, which consists of some opaque A string of the form @localpart:domain.name that identifies a user for
localpart combined with the domain name of their home server. wire-protocol purposes. The localpart is meaningless outside of a particular
home server. This takes a human-readable form that end-users can use directly
if they so wish, avoiding the 3PIDs.
Transaction:
A message which relates to the communication between a given pair of servers.
A transaction contains possibly-empty lists of PDUs and EDUs.
.. Links through the external API docs are below .. Links through the external API docs are below
.. ============================================= .. =============================================
.. |createRoom| replace:: ``/createRoom`` .. |createRoom| replace:: ``/createRoom``
.. _createRoom: /-rooms/create_room .. _createRoom: /docs/api/client-server/#!/-rooms/create_room
.. |initialSync| replace:: ``/initialSync`` .. |initialSync| replace:: ``/initialSync``
.. _initialSync: /-events/initial_sync .. _initialSync: /docs/api/client-server/#!/-events/initial_sync
.. |/rooms/<room_id>/initialSync| replace:: ``/rooms/<room_id>/initialSync`` .. |/rooms/<room_id>/initialSync| replace:: ``/rooms/<room_id>/initialSync``
.. _/rooms/<room_id>/initialSync: /-rooms/get_room_sync_data .. _/rooms/<room_id>/initialSync: /docs/api/client-server/#!/-rooms/get_room_sync_data
.. |login| replace:: ``/login`` .. |login| replace:: ``/login``
.. _login: /-login .. _login: /docs/api/client-server/#!/-login
.. |/rooms/<room_id>/messages| replace:: ``/rooms/<room_id>/messages`` .. |/rooms/<room_id>/messages| replace:: ``/rooms/<room_id>/messages``
.. _/rooms/<room_id>/messages: /-rooms/get_messages .. _/rooms/<room_id>/messages: /docs/api/client-server/#!/-rooms/get_messages
.. |/rooms/<room_id>/members| replace:: ``/rooms/<room_id>/members`` .. |/rooms/<room_id>/members| replace:: ``/rooms/<room_id>/members``
.. _/rooms/<room_id>/members: /-rooms/get_members .. _/rooms/<room_id>/members: /docs/api/client-server/#!/-rooms/get_members
.. |/rooms/<room_id>/state| replace:: ``/rooms/<room_id>/state`` .. |/rooms/<room_id>/state| replace:: ``/rooms/<room_id>/state``
.. _/rooms/<room_id>/state: /-rooms/get_state_events .. _/rooms/<room_id>/state: /docs/api/client-server/#!/-rooms/get_state_events
.. |/rooms/<room_id>/send/<event_type>| replace:: ``/rooms/<room_id>/send/<event_type>`` .. |/rooms/<room_id>/send/<event_type>| replace:: ``/rooms/<room_id>/send/<event_type>``
.. _/rooms/<room_id>/send/<event_type>: /-rooms/send_non_state_event .. _/rooms/<room_id>/send/<event_type>: /docs/api/client-server/#!/-rooms/send_non_state_event
.. |/rooms/<room_id>/state/<event_type>/<state_key>| replace:: ``/rooms/<room_id>/state/<event_type>/<state_key>`` .. |/rooms/<room_id>/state/<event_type>/<state_key>| replace:: ``/rooms/<room_id>/state/<event_type>/<state_key>``
.. _/rooms/<room_id>/state/<event_type>/<state_key>: /-rooms/send_state_event .. _/rooms/<room_id>/state/<event_type>/<state_key>: /docs/api/client-server/#!/-rooms/send_state_event
.. |/rooms/<room_id>/invite| replace:: ``/rooms/<room_id>/invite`` .. |/rooms/<room_id>/invite| replace:: ``/rooms/<room_id>/invite``
.. _/rooms/<room_id>/invite: /-rooms/invite .. _/rooms/<room_id>/invite: /docs/api/client-server/#!/-rooms/invite
.. |/rooms/<room_id>/join| replace:: ``/rooms/<room_id>/join`` .. |/rooms/<room_id>/join| replace:: ``/rooms/<room_id>/join``
.. _/rooms/<room_id>/join: /-rooms/join_room .. _/rooms/<room_id>/join: /docs/api/client-server/#!/-rooms/join_room
.. |/rooms/<room_id>/leave| replace:: ``/rooms/<room_id>/leave`` .. |/rooms/<room_id>/leave| replace:: ``/rooms/<room_id>/leave``
.. _/rooms/<room_id>/leave: /-rooms/leave .. _/rooms/<room_id>/leave: /docs/api/client-server/#!/-rooms/leave
.. |/rooms/<room_id>/ban| replace:: ``/rooms/<room_id>/ban`` .. |/rooms/<room_id>/ban| replace:: ``/rooms/<room_id>/ban``
.. _/rooms/<room_id>/ban: /-rooms/ban .. _/rooms/<room_id>/ban: /docs/api/client-server/#!/-rooms/ban
.. |/join/<room_alias_or_id>| replace:: ``/join/<room_alias_or_id>`` .. |/join/<room_alias_or_id>| replace:: ``/join/<room_alias_or_id>``
.. _/join/<room_alias_or_id>: /-rooms/join .. _/join/<room_alias_or_id>: /docs/api/client-server/#!/-rooms/join
.. _`Event Stream`: /-events/get_event_stream .. _`Event Stream`: /docs/api/client-server/#!/-events/get_event_stream

View file

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

View file

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

View file

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

View file

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

View file

@ -9,4 +9,6 @@ perl -pi -e 's#<head>#<head><link rel="stylesheet" href="/site.css">#' $MATRIXDO
perl -pi -e 's#<body>#<body><div id="header"><div id="headerContent">&nbsp;</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>#<body><div id="header"><div id="headerContent">&nbsp;</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">&copy 2014 Matrix.org</div></div></body>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html perl -pi -e 's#</body>#</div></div><div id="footer"><div id="footerContent">&copy 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()
@ -248,6 +259,7 @@ def setup():
db_name=config.database_path, db_name=config.database_path,
tls_context_factory=tls_context_factory, tls_context_factory=tls_context_factory,
config=config, config=config,
content_addr=config.content_addr,
) )
hs.register_servlets() hs.register_servlets()

View file

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

View file

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

View file

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

View file

@ -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,9 +18,10 @@ 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 .ratelimiting import RatelimitConfig
from .repository import ContentRepositoryConfig
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig, class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig): RatelimitConfig, ContentRepositoryConfig):
pass pass
if __name__=='__main__': if __name__=='__main__':

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
@ -142,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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,6 +15,7 @@
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from twisted.internet.error import DNSLookupError
from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer
from twisted.web.http_headers import Headers from twisted.web.http_headers import Headers
@ -23,7 +24,7 @@ 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 from StringIO import StringIO
@ -45,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
@ -144,13 +146,23 @@ 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) body = yield readBody(response)
@ -179,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"]
@ -218,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)

View file

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

View file

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

View file

@ -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__)
@ -198,161 +192,6 @@ class RootRedirect(resource.Resource):
return resource.Resource.getChild(self, name, request) return resource.Resource.getChild(self, name, request)
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):
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, def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
response_code_message=None): response_code_message=None):
"""Sends encoded JSON in response to the given request. """Sends encoded JSON in response to the given request.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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|ban)") "(?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 in ["invite", "ban"]: 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)},

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
@ -79,19 +79,21 @@ class SQLBaseStore(object):
# "Simple" SQL API methods that operate on a single table with no JOINs, # "Simple" SQL API methods that operate on a single table with no JOINs,
# no complex WHERE clauses, just a dict of values for columns. # no complex WHERE clauses, just a dict of values for columns.
def _simple_insert(self, table, values): def _simple_insert(self, table, values, or_replace=False):
"""Executes an INSERT query on the named table. """Executes an INSERT query on the named table.
Args: Args:
table : string giving the table name table : string giving the table name
values : dict of new column names and values for them values : dict of new column names and values for them
or_replace : bool; if True performs an INSERT OR REPLACE
""" """
return self._db_pool.runInteraction( return self._db_pool.runInteraction(
self._simple_insert_txn, table, values, self._simple_insert_txn, table, values, or_replace=or_replace
) )
def _simple_insert_txn(self, txn, table, values): def _simple_insert_txn(self, txn, table, values, or_replace=False):
sql = "INSERT INTO %s (%s) VALUES(%s)" % ( sql = "%s INTO %s (%s) VALUES(%s)" % (
("INSERT OR REPLACE" if or_replace else "INSERT"),
table, table,
", ".join(k for k in values), ", ".join(k for k in values),
", ".join("?" for k in values) ", ".join("?" for k in values)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
@ -165,7 +165,7 @@ class RoomMemberStore(SQLBaseStore):
defer.returnValue(results) defer.returnValue(results)
@defer.inlineCallbacks @defer.inlineCallbacks
def do_users_share_a_room(self, user_list): def user_rooms_intersect(self, user_list):
""" Checks whether a list of users share a room. """ Checks whether a list of users share a room.
""" """
user_list_clause = " OR ".join(["m.user_id = ?"] * len(user_list)) user_list_clause = " OR ".join(["m.user_id = ?"] * len(user_list))

View file

@ -0,0 +1,168 @@
/* 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.
*/
CREATE TABLE IF NOT EXISTS events(
stream_ordering INTEGER PRIMARY KEY AUTOINCREMENT,
topological_ordering INTEGER NOT NULL,
event_id TEXT NOT NULL,
type TEXT NOT NULL,
room_id TEXT NOT NULL,
content TEXT NOT NULL,
unrecognized_keys TEXT,
processed BOOL NOT NULL,
outlier BOOL NOT NULL,
CONSTRAINT ev_uniq UNIQUE (event_id)
);
CREATE INDEX IF NOT EXISTS events_event_id ON events (event_id);
CREATE INDEX IF NOT EXISTS events_stream_ordering ON events (stream_ordering);
CREATE INDEX IF NOT EXISTS events_topological_ordering ON events (topological_ordering);
CREATE INDEX IF NOT EXISTS events_room_id ON events (room_id);
CREATE TABLE IF NOT EXISTS state_events(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
type TEXT NOT NULL,
state_key TEXT NOT NULL,
prev_state TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS state_events_event_id ON state_events (event_id);
CREATE INDEX IF NOT EXISTS state_events_room_id ON state_events (room_id);
CREATE INDEX IF NOT EXISTS state_events_type ON state_events (type);
CREATE INDEX IF NOT EXISTS state_events_state_key ON state_events (state_key);
CREATE TABLE IF NOT EXISTS current_state_events(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
type TEXT NOT NULL,
state_key TEXT NOT NULL,
CONSTRAINT curr_uniq UNIQUE (room_id, type, state_key) ON CONFLICT REPLACE
);
CREATE INDEX IF NOT EXISTS curr_events_event_id ON current_state_events (event_id);
CREATE INDEX IF NOT EXISTS current_state_events_room_id ON current_state_events (room_id);
CREATE INDEX IF NOT EXISTS current_state_events_type ON current_state_events (type);
CREATE INDEX IF NOT EXISTS current_state_events_state_key ON current_state_events (state_key);
CREATE TABLE IF NOT EXISTS room_memberships(
event_id TEXT NOT NULL,
user_id TEXT NOT NULL,
sender TEXT NOT NULL,
room_id TEXT NOT NULL,
membership TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS room_memberships_event_id ON room_memberships (event_id);
CREATE INDEX IF NOT EXISTS room_memberships_room_id ON room_memberships (room_id);
CREATE INDEX IF NOT EXISTS room_memberships_user_id ON room_memberships (user_id);
CREATE TABLE IF NOT EXISTS feedback(
event_id TEXT NOT NULL,
feedback_type TEXT,
target_event_id TEXT,
sender TEXT,
room_id TEXT
);
CREATE TABLE IF NOT EXISTS topics(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
topic TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS room_names(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS rooms(
room_id TEXT PRIMARY KEY NOT NULL,
is_public INTEGER,
creator TEXT
);
CREATE TABLE IF NOT EXISTS room_join_rules(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
join_rule TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS room_join_rules_event_id ON room_join_rules(event_id);
CREATE INDEX IF NOT EXISTS room_join_rules_room_id ON room_join_rules(room_id);
CREATE TABLE IF NOT EXISTS room_power_levels(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
user_id TEXT NOT NULL,
level INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS room_power_levels_event_id ON room_power_levels(event_id);
CREATE INDEX IF NOT EXISTS room_power_levels_room_id ON room_power_levels(room_id);
CREATE INDEX IF NOT EXISTS room_power_levels_room_user ON room_power_levels(room_id, user_id);
CREATE TABLE IF NOT EXISTS room_default_levels(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
level INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS room_default_levels_event_id ON room_default_levels(event_id);
CREATE INDEX IF NOT EXISTS room_default_levels_room_id ON room_default_levels(room_id);
CREATE TABLE IF NOT EXISTS room_add_state_levels(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
level INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS room_add_state_levels_event_id ON room_add_state_levels(event_id);
CREATE INDEX IF NOT EXISTS room_add_state_levels_room_id ON room_add_state_levels(room_id);
CREATE TABLE IF NOT EXISTS room_send_event_levels(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
level INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS room_send_event_levels_event_id ON room_send_event_levels(event_id);
CREATE INDEX IF NOT EXISTS room_send_event_levels_room_id ON room_send_event_levels(room_id);
CREATE TABLE IF NOT EXISTS room_ops_levels(
event_id TEXT NOT NULL,
room_id TEXT NOT NULL,
ban_level INTEGER,
kick_level INTEGER
);
CREATE INDEX IF NOT EXISTS room_ops_levels_event_id ON room_ops_levels(event_id);
CREATE INDEX IF NOT EXISTS room_ops_levels_room_id ON room_ops_levels(room_id);
CREATE TABLE IF NOT EXISTS room_hosts(
room_id TEXT NOT NULL,
host TEXT NOT NULL,
CONSTRAINT room_hosts_uniq UNIQUE (room_id, host) ON CONFLICT IGNORE
);
CREATE INDEX IF NOT EXISTS room_hosts_room_id ON room_hosts (room_id);
PRAGMA user_version = 2;

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more