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,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -103,8 +103,7 @@ class FeedbackEvent(SynapseEvent):
def get_content_template(self):
return {
"type": u"string",
"target_event_id": u"string",
"msg_sender_id": u"string"
"target_event_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");
# you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -23,7 +23,8 @@ from twisted.enterprise import adbapi
from twisted.web.resource import Resource
from twisted.web.static import File
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.api.urls import (
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
@ -74,7 +75,9 @@ class SynapseHomeServer(HomeServer):
return File("webclient") # TODO configurable?
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):
""" 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]:
user_version = row[0]
if user_version < SCHEMA_VERSION:
# TODO(paul): add some kind of intelligent fixup here
raise ValueError("Cannot use this database as the " +
"schema version (%d) does not match (%d)" %
(user_version, SCHEMA_VERSION)
if user_version > SCHEMA_VERSION:
raise ValueError("Cannot use this database as it is too " +
"new for the server to understand"
)
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:
for sql_loc in SCHEMAS:
sql_script = read_schema(sql_loc)
c.executescript(sql_script)
db_conn.commit()
db_conn.commit()
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
c.close()
@ -248,6 +259,7 @@ def setup():
db_name=config.database_path,
tls_context_factory=tls_context_factory,
config=config,
content_addr=config.content_addr,
)
hs.register_servlets()

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -18,9 +18,10 @@ from .server import ServerConfig
from .logger import LoggingConfig
from .database import DatabaseConfig
from .ratelimiting import RatelimitConfig
from .repository import ContentRepositoryConfig
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig):
RatelimitConfig, ContentRepositoryConfig):
pass
if __name__=='__main__':

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,4 +1,4 @@
# Copyright 2014 matrix.org
# 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.

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 -*-
# Copyright 2014 matrix.org
# 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.
@ -32,6 +32,14 @@ class ServerConfig(Config):
self.webclient = True
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
def add_arguments(cls, parser):
super(ServerConfig, cls).add_arguments(parser)
@ -50,13 +58,16 @@ class ServerConfig(Config):
help="Local interface to listen on")
server_group.add_argument("-D", "--daemonize", action='store_true',
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"
" store the pid in")
server_group.add_argument("--manhole", metavar="PORT", dest="manhole",
type=int,
help="Turn on the twisted telnet manhole"
" 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):
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:
key = nacl.signing.SigningKey.generate()
signing_key_file.write(encode_base64(key.encode()))

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,4 +1,4 @@
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -628,7 +628,6 @@ class _TransactionQueue(object):
for deferred in deferreds:
deferred.errback(e)
yield deferred
finally:
# We want to be *very* sure we delete this after we stop processing

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -18,6 +18,7 @@ from twisted.internet import defer
from ._base import BaseHandler
from synapse.api.errors import SynapseError
from synapse.http.client import HttpClient
import logging
@ -36,7 +37,7 @@ class DirectoryHandler(BaseHandler):
)
@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.
if not room_alias.is_mine:
@ -47,6 +48,12 @@ class DirectoryHandler(BaseHandler):
# 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(
room_alias,
room_id,
@ -68,7 +75,10 @@ class DirectoryHandler(BaseHandler):
result = yield self.federation.make_query(
destination=room_alias.domain,
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:
@ -79,6 +89,9 @@ class DirectoryHandler(BaseHandler):
defer.returnValue({})
return
extra_servers = yield self.store.get_joined_hosts_for_room(room_id)
servers = list(set(extra_servers) | set(servers))
defer.returnValue({
"room_id": room_id,
"servers": servers,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -126,5 +126,7 @@ class EventHandler(BaseHandler):
defer.returnValue(None)
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)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -142,7 +142,12 @@ class MessageHandler(BaseRoomHandler):
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)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -155,19 +155,18 @@ class PresenceHandler(BaseHandler):
if observer_user == observed_user:
defer.returnValue(True)
allowed_by_subscription = yield self.store.is_presence_visible(
observed_localpart=observed_user.localpart,
observer_userid=observer_user.to_string(),
)
if allowed_by_subscription:
if (yield self.store.user_rooms_intersect(
[u.to_string() for u in observer_user, observed_user]
)):
defer.returnValue(True)
share_room = yield self.store.do_users_share_a_room(
[observer_user, observed_user]
)
if (yield self.store.is_presence_visible(
observed_localpart=observed_user.localpart,
observer_userid=observer_user.to_string(),
)):
defer.returnValue(True)
defer.returnValue(share_room)
defer.returnValue(False)
@defer.inlineCallbacks
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)
if "mtime" in state:
del state["mtime"]
state["presence"] = state["state"]
state["presence"] = state.pop("state")
if target_user in self._user_cachemap:
state["last_active"] = (
@ -208,21 +207,17 @@ class PresenceHandler(BaseHandler):
raise SynapseError(400, "User is not hosted on this Home Server")
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:
state["status_msg"] = None
for k in state.keys():
if k not in ("presence", "state", "status_msg"):
if k not in ("presence", "status_msg"):
raise SynapseError(
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:
raise SynapseError(400, "'%s' is not a valid presence state" %
state["presence"]
@ -601,7 +596,7 @@ class PresenceHandler(BaseHandler):
if state is None:
state = yield self.store.get_presence_state(user.localpart)
del state["mtime"]
state["presence"] = state["state"]
state["presence"] = state.pop("state")
if user in self._user_cachemap:
state["last_active"] = (
@ -622,8 +617,6 @@ class PresenceHandler(BaseHandler):
"user_id": user.to_string(),
}
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(
destination=destination,
@ -655,21 +648,12 @@ class PresenceHandler(BaseHandler):
state = dict(push)
del state["user_id"]
if "presence" in state:
# all is OK
pass
elif "state" in state:
# Legacy handling
state["presence"] = state["state"]
else:
if "presence" not in state:
logger.warning("Received a presence 'push' EDU from %s without"
+ " either a 'presence' or 'state' key", origin
+ " a 'presence' key", origin
)
continue
if "state" in state:
del state["state"]
if "last_active_ago" in state:
state["last_active"] = int(
self.clock.time_msec() - state.pop("last_active_ago")
@ -773,15 +757,52 @@ class PresenceEventSource(object):
self.hs = hs
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):
from_key = int(from_key)
observer_user = user
presence = self.hs.get_handlers().presence_handler
cachemap = presence._user_cachemap
# TODO(paul): limit, and filter by visibility
updates = [(k, cachemap[k]) for k in cachemap
if from_key < cachemap[k].serial]
updates = []
# TODO(paul): use a DeferredList ? How to limit concurrency.
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:
clock = self.clock
@ -789,20 +810,23 @@ class PresenceEventSource(object):
latest_serial = max([x[1].serial 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:
return (([], presence._user_cachemap_latest_serial))
defer.returnValue(([], presence._user_cachemap_latest_serial))
def get_current_key(self):
presence = self.hs.get_handlers().presence_handler
return presence._user_cachemap_latest_serial
@defer.inlineCallbacks
def get_pagination_rows(self, user, pagination_config, key):
# TODO (erikj): Does this make sense? Ordering?
from_token = pagination_config.from_token
to_token = pagination_config.to_token
observer_user = user
from_key = int(from_token.presence_key)
if to_token:
@ -813,7 +837,17 @@ class PresenceEventSource(object):
presence = self.hs.get_handlers().presence_handler
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
if to_key < cachemap[k].serial < from_key]
@ -831,13 +865,13 @@ class PresenceEventSource(object):
next_token = next_token.copy_and_replace(
"presence_key", earliest_serial
)
return ((data, next_token))
defer.returnValue((data, next_token))
else:
if not to_token:
to_token = from_token.copy_and_replace(
"presence_key", 0
)
return (([], to_token))
defer.returnValue(([], to_token))
class UserPresenceCache(object):
@ -851,7 +885,6 @@ class UserPresenceCache(object):
def update(self, state, serial):
assert("mtime_age" not in state)
assert("state" not in state)
self.state.update(state)
# Delete keys that are now 'None'
@ -869,11 +902,6 @@ class UserPresenceCache(object):
def get_state(self):
# clone it so caller can't break our cache
state = dict(self.state)
# Legacy handling
if "presence" in state:
state["state"] = state["presence"]
return state
def make_event(self, user, clock):

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -15,6 +15,7 @@
from twisted.internet import defer, reactor
from twisted.internet.error import DNSLookupError
from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer
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 synapse.api.errors import CodeMessageException
from synapse.api.errors import CodeMessageException, SynapseError
from StringIO import StringIO
@ -45,6 +46,7 @@ _destination_mappings = {
class HttpClient(object):
""" Interface for talking json over http
"""
RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
def put_json(self, destination, path, data):
""" Sends the specifed json data using PUT
@ -144,13 +146,23 @@ class TwistedHttpClient(HttpClient):
destination = _destination_mappings[destination]
logger.debug("get_json args: %s", args)
retry_on_dns_fail = True
if HttpClient.RETRY_DNS_LOOKUP_FAILURES in args:
# FIXME: This isn't ideal, but the interface exposed in get_json
# isn't comprehensive enough to give caller's any control over
# their connection mechanics.
retry_on_dns_fail = args.pop(HttpClient.RETRY_DNS_LOOKUP_FAILURES)
query_bytes = urllib.urlencode(args, True)
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
response = yield self._create_request(
destination.encode("ascii"),
"GET",
path.encode("ascii"),
query_bytes=query_bytes
query_bytes=query_bytes,
retry_on_dns_fail=retry_on_dns_fail
)
body = yield readBody(response)
@ -179,7 +191,8 @@ class TwistedHttpClient(HttpClient):
@defer.inlineCallbacks
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
"""
headers_dict[b"User-Agent"] = [b"Synapse"]
@ -218,6 +231,11 @@ class TwistedHttpClient(HttpClient):
logger.debug("Got response to %s", method)
break
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")
_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 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -18,22 +18,16 @@ from syutil.jsonutil import (
encode_canonical_json, encode_pretty_printed_json
)
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.protocols.basic import FileSender
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
from twisted.web.util import redirectTo
import base64
import collections
import json
import logging
import os
import re
logger = logging.getLogger(__name__)
@ -198,161 +192,6 @@ class RootRedirect(resource.Resource):
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,
response_code_message=None):
"""Sends encoded JSON in response to the given request.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -167,7 +167,12 @@ class Notifier(object):
)
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(
[notify(l).addErrback(eb) for l in listeners]

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -16,6 +16,7 @@
from twisted.internet import defer
from synapse.api.errors import SynapseError, Codes
from base import RestServlet, client_path_pattern
import json
@ -44,8 +45,10 @@ class ClientDirectoryServer(RestServlet):
@defer.inlineCallbacks
def on_PUT(self, request, room_alias):
# TODO(erikj): Exceptions
content = json.loads(request.content.read())
content = _parse_json(request)
if not "room_id" in content:
raise SynapseError(400, "Missing room_id key",
errcode=Codes.BAD_JSON)
logger.debug("Got content: %s", content)
@ -54,7 +57,7 @@ class ClientDirectoryServer(RestServlet):
logger.debug("Got room name: %s", room_alias.to_string())
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 servers: %s", servers)
@ -68,7 +71,20 @@ class ClientDirectoryServer(RestServlet):
yield dir_handler.create_association(
room_alias, room_id, servers
)
except SynapseError as e:
raise e
except:
logger.exception("Failed to create association")
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 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -17,11 +17,12 @@
"""
from twisted.internet import defer
from synapse.api.errors import SynapseError
from base import RestServlet, client_path_pattern
import json
import logging
import urllib
logger = logging.getLogger(__name__)
@ -32,6 +33,7 @@ class PresenceStatusRestServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id):
auth_user = yield self.auth.get_user_by_req(request)
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
state = yield self.handlers.presence_handler.get_state(
@ -42,25 +44,26 @@ class PresenceStatusRestServlet(RestServlet):
@defer.inlineCallbacks
def on_PUT(self, request, user_id):
auth_user = yield self.auth.get_user_by_req(request)
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
state = {}
try:
content = json.loads(request.content.read())
# Legacy handling
if "state" in content:
state["presence"] = content.pop("state")
else:
state["presence"] = content.pop("presence")
state["presence"] = content.pop("presence")
if "status_msg" in content:
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:
raise KeyError()
except SynapseError as e:
raise e
except:
defer.returnValue((400, "Unable to parse state"))
raise SynapseError(400, "Unable to parse state")
yield self.handlers.presence_handler.set_state(
target_user=user, auth_user=auth_user, state=state)
@ -77,13 +80,14 @@ class PresenceListRestServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id):
auth_user = yield self.auth.get_user_by_req(request)
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
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:
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(
observer_user=user, accepted=True)
@ -97,31 +101,40 @@ class PresenceListRestServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, user_id):
auth_user = yield self.auth.get_user_by_req(request)
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
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:
defer.returnValue((
400, "Cannot modify another user's presence list"))
raise SynapseError(
400, "Cannot modify another user's presence list")
try:
content = json.loads(request.content.read())
except:
logger.exception("JSON parse error")
defer.returnValue((400, "Unable to parse content"))
raise SynapseError(400, "Unable to parse content")
deferreds = []
if "invite" in content:
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)
deferreds.append(self.handlers.presence_handler.send_invite(
observer_user=user, observed_user=invited_user))
if "drop" in content:
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)
deferreds.append(self.handlers.presence_handler.drop(
observer_user=user, observed_user=dropped_user))

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -19,6 +19,7 @@ from twisted.internet import defer
from base import RestServlet, client_path_pattern
import json
import urllib
class ProfileDisplaynameRestServlet(RestServlet):
@ -26,6 +27,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id):
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
displayname = yield self.handlers.profile_handler.get_displayname(
@ -37,6 +39,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
@defer.inlineCallbacks
def on_PUT(self, request, user_id):
auth_user = yield self.auth.get_user_by_req(request)
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
try:
@ -59,6 +62,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id):
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
avatar_url = yield self.handlers.profile_handler.get_avatar_url(
@ -70,6 +74,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
@defer.inlineCallbacks
def on_PUT(self, request, user_id):
auth_user = yield self.auth.get_user_by_req(request)
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
try:
@ -92,6 +97,7 @@ class ProfileRestServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id):
user_id = urllib.unquote(user_id)
user = self.hs.parse_userid(user_id)
displayname = yield self.handlers.profile_handler.get_displayname(

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -388,7 +388,7 @@ class RoomMembershipRestServlet(RestServlet):
def register(self, http_server):
# /rooms/$roomid/[invite|join|leave]
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)
@defer.inlineCallbacks
@ -399,11 +399,14 @@ class RoomMembershipRestServlet(RestServlet):
# target user is you unless it is an invite
state_key = user.to_string()
if membership_action in ["invite", "ban"]:
if membership_action in ["invite", "ban", "kick"]:
if "user_id" not in content:
raise SynapseError(400, "Missing user_id key.")
state_key = content["user_id"]
if membership_action == "kick":
membership_action = "leave"
event = self.event_factory.create_event(
etype=RoomMemberEvent.TYPE,
content={"membership": unicode(membership_action)},

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -179,6 +179,18 @@ class StateHandler(object):
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
origin = missing_prev.prev_state_origin

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -79,19 +79,21 @@ class SQLBaseStore(object):
# "Simple" SQL API methods that operate on a single table with no JOINs,
# 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.
Args:
table : string giving the table name
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(
self._simple_insert_txn, table, values,
self._simple_insert_txn, table, values, or_replace=or_replace
)
def _simple_insert_txn(self, txn, table, values):
sql = "INSERT INTO %s (%s) VALUES(%s)" % (
def _simple_insert_txn(self, txn, table, values, or_replace=False):
sql = "%s INTO %s (%s) VALUES(%s)" % (
("INSERT OR REPLACE" if or_replace else "INSERT"),
table,
", ".join(k for k in values),
", ".join("?" for k in values)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -165,7 +165,7 @@ class RoomMemberStore(SQLBaseStore):
defer.returnValue(results)
@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.
"""
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");
* 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");
* 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");
* 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");
* 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");
* 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");
* 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");
* 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");
* 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");
* you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.
@ -32,7 +32,9 @@ class Distributor(object):
model will do for today.
"""
def __init__(self):
def __init__(self, suppress_failures=True):
self.suppress_failures = suppress_failures
self.signals = {}
self.pre_registration = {}
@ -40,7 +42,9 @@ class Distributor(object):
if name in self.signals:
raise KeyError("%r already has a signal named %s" % (self, name))
self.signals[name] = Signal(name)
self.signals[name] = Signal(name,
suppress_failures=self.suppress_failures,
)
if name in self.pre_registration:
signal = self.signals[name]
@ -74,8 +78,9 @@ class Signal(object):
method into all of the observers.
"""
def __init__(self, name):
def __init__(self, name, suppress_failures):
self.name = name
self.suppress_failures = suppress_failures
self.observers = []
def observe(self, observer):
@ -104,6 +109,10 @@ class Signal(object):
failure.type,
failure.value,
failure.getTracebackObject()))
if not self.suppress_failures:
raise failure
deferreds.append(d.addErrback(eb))
return defer.DeferredList(deferreds)
return defer.DeferredList(
deferreds, fireOnOneErrback=not self.suppress_failures
)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# 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.