diff --git a/synapse/api/__init__.py b/synapse/api/__init__.py
index c488b10d3c..bfebb0f644 100644
--- a/synapse/api/__init__.py
+++ b/synapse/api/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index adb7d64482..e2f84c4d57 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2014 - 2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,8 +22,9 @@ from twisted.internet import defer
from synapse.api.constants import EventTypes, Membership, JoinRules
from synapse.api.errors import AuthError, Codes, SynapseError, EventSizeError
-from synapse.types import RoomID, UserID, EventID
+from synapse.types import Requester, RoomID, UserID, EventID
from synapse.util.logutils import log_function
+from synapse.util.logcontext import preserve_context_over_fn
from unpaddedbase64 import decode_base64
import logging
@@ -510,35 +511,14 @@ class Auth(object):
"""
# Can optionally look elsewhere in the request (e.g. headers)
try:
- access_token = request.args["access_token"][0]
-
- # Check for application service tokens with a user_id override
- try:
- app_service = yield self.store.get_app_service_by_token(
- access_token
- )
- if not app_service:
- raise KeyError
-
- user_id = app_service.sender
- if "user_id" in request.args:
- user_id = request.args["user_id"][0]
- if not app_service.is_interested_in_user(user_id):
- raise AuthError(
- 403,
- "Application service cannot masquerade as this user."
- )
-
- if not user_id:
- raise KeyError
-
+ user_id = yield self._get_appservice_user_id(request.args)
+ if user_id:
request.authenticated_entity = user_id
+ defer.returnValue(
+ Requester(UserID.from_string(user_id), "", False)
+ )
- defer.returnValue((UserID.from_string(user_id), "", False))
- return
- except KeyError:
- pass # normal users won't have the user_id query parameter set.
-
+ access_token = request.args["access_token"][0]
user_info = yield self._get_user_by_access_token(access_token)
user = user_info["user"]
token_id = user_info["token_id"]
@@ -550,7 +530,8 @@ class Auth(object):
default=[""]
)[0]
if user and access_token and ip_addr:
- self.store.insert_client_ip(
+ preserve_context_over_fn(
+ self.store.insert_client_ip,
user=user,
access_token=access_token,
ip=ip_addr,
@@ -564,7 +545,7 @@ class Auth(object):
request.authenticated_entity = user.to_string()
- defer.returnValue((user, token_id, is_guest,))
+ defer.returnValue(Requester(user, token_id, is_guest))
except KeyError:
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token.",
@@ -572,6 +553,33 @@ class Auth(object):
)
@defer.inlineCallbacks
+ def _get_appservice_user_id(self, request_args):
+ app_service = yield self.store.get_app_service_by_token(
+ request_args["access_token"][0]
+ )
+ if app_service is None:
+ defer.returnValue(None)
+
+ if "user_id" not in request_args:
+ defer.returnValue(app_service.sender)
+
+ user_id = request_args["user_id"][0]
+ if app_service.sender == user_id:
+ defer.returnValue(app_service.sender)
+
+ if not app_service.is_interested_in_user(user_id):
+ raise AuthError(
+ 403,
+ "Application service cannot masquerade as this user."
+ )
+ if not (yield self.store.get_user_by_id(user_id)):
+ raise AuthError(
+ 403,
+ "Application service has not registered this user"
+ )
+ defer.returnValue(user_id)
+
+ @defer.inlineCallbacks
def _get_user_by_access_token(self, token):
""" Get a registered user's ID.
@@ -583,7 +591,7 @@ class Auth(object):
AuthError if no user by that token exists or the token is invalid.
"""
try:
- ret = yield self._get_user_from_macaroon(token)
+ ret = yield self.get_user_from_macaroon(token)
except AuthError:
# TODO(daniel): Remove this fallback when all existing access tokens
# have been re-issued as macaroons.
@@ -591,7 +599,7 @@ class Auth(object):
defer.returnValue(ret)
@defer.inlineCallbacks
- def _get_user_from_macaroon(self, macaroon_str):
+ def get_user_from_macaroon(self, macaroon_str):
try:
macaroon = pymacaroons.Macaroon.deserialize(macaroon_str)
self.validate_macaroon(macaroon, "access", False)
@@ -690,6 +698,7 @@ class Auth(object):
def _look_up_user_by_access_token(self, token):
ret = yield self.store.get_user_by_access_token(token)
if not ret:
+ logger.warn("Unrecognised access token - not in store: %s" % (token,))
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS, "Unrecognised access token.",
errcode=Codes.UNKNOWN_TOKEN
@@ -707,6 +716,7 @@ class Auth(object):
token = request.args["access_token"][0]
service = yield self.store.get_app_service_by_token(token)
if not service:
+ logger.warn("Unrecognised appservice access token: %s" % (token,))
raise AuthError(
self.TOKEN_NOT_FOUND_HTTP_STATUS,
"Unrecognised access token.",
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index c2450b771a..84cbe710b3 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 8bc7b9e6db..b106fbed6d 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ class Codes(object):
USER_IN_USE = "M_USER_IN_USE"
ROOM_IN_USE = "M_ROOM_IN_USE"
BAD_PAGINATION = "M_BAD_PAGINATION"
+ BAD_STATE = "M_BAD_STATE"
UNKNOWN = "M_UNKNOWN"
NOT_FOUND = "M_NOT_FOUND"
MISSING_TOKEN = "M_MISSING_TOKEN"
@@ -42,6 +43,7 @@ class Codes(object):
EXCLUSIVE = "M_EXCLUSIVE"
THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
THREEPID_IN_USE = "THREEPID_IN_USE"
+ INVALID_USERNAME = "M_INVALID_USERNAME"
class CodeMessageException(RuntimeError):
@@ -120,22 +122,6 @@ class AuthError(SynapseError):
super(AuthError, self).__init__(*args, **kwargs)
-class GuestAccessError(AuthError):
- """An error raised when a there is a problem with a guest user accessing
- a room"""
-
- def __init__(self, rooms, *args, **kwargs):
- self.rooms = rooms
- super(GuestAccessError, self).__init__(*args, **kwargs)
-
- def error_dict(self):
- return cs_error(
- self.msg,
- self.errcode,
- rooms=self.rooms,
- )
-
-
class EventSizeError(SynapseError):
"""An error raised when an event is too big."""
diff --git a/synapse/api/filtering.py b/synapse/api/filtering.py
index 5287aaa757..6eff83e5f8 100644
--- a/synapse/api/filtering.py
+++ b/synapse/api/filtering.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright 2015 OpenMarket Ltd
+# Copyright 2015, 2016 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,8 @@
from synapse.api.errors import SynapseError
from synapse.types import UserID, RoomID
+import ujson as json
+
class Filtering(object):
@@ -28,14 +30,14 @@ class Filtering(object):
return result
def add_user_filter(self, user_localpart, user_filter):
- self._check_valid_filter(user_filter)
+ self.check_valid_filter(user_filter)
return self.store.add_user_filter(user_localpart, user_filter)
# TODO(paul): surely we should probably add a delete_user_filter or
# replace_user_filter at some point? There's no REST API specified for
# them however
- def _check_valid_filter(self, user_filter_json):
+ def check_valid_filter(self, user_filter_json):
"""Check if the provided filter is valid.
This inspects all definitions contained within the filter.
@@ -129,88 +131,80 @@ class Filtering(object):
class FilterCollection(object):
def __init__(self, filter_json):
- self.filter_json = filter_json
+ self._filter_json = filter_json
- room_filter_json = self.filter_json.get("room", {})
+ room_filter_json = self._filter_json.get("room", {})
- self.room_filter = Filter({
+ self._room_filter = Filter({
k: v for k, v in room_filter_json.items()
if k in ("rooms", "not_rooms")
})
- self.room_timeline_filter = Filter(room_filter_json.get("timeline", {}))
- self.room_state_filter = Filter(room_filter_json.get("state", {}))
- self.room_ephemeral_filter = Filter(room_filter_json.get("ephemeral", {}))
- self.room_account_data = Filter(room_filter_json.get("account_data", {}))
- self.presence_filter = Filter(self.filter_json.get("presence", {}))
- self.account_data = Filter(self.filter_json.get("account_data", {}))
+ self._room_timeline_filter = Filter(room_filter_json.get("timeline", {}))
+ self._room_state_filter = Filter(room_filter_json.get("state", {}))
+ self._room_ephemeral_filter = Filter(room_filter_json.get("ephemeral", {}))
+ self._room_account_data = Filter(room_filter_json.get("account_data", {}))
+ self._presence_filter = Filter(filter_json.get("presence", {}))
+ self._account_data = Filter(filter_json.get("account_data", {}))
- self.include_leave = self.filter_json.get("room", {}).get(
+ self.include_leave = filter_json.get("room", {}).get(
"include_leave", False
)
- def list_rooms(self):
- return self.room_filter.list_rooms()
+ def __repr__(self):
+ return "<FilterCollection %s>" % (json.dumps(self._filter_json),)
+
+ def get_filter_json(self):
+ return self._filter_json
def timeline_limit(self):
- return self.room_timeline_filter.limit()
+ return self._room_timeline_filter.limit()
def presence_limit(self):
- return self.presence_filter.limit()
+ return self._presence_filter.limit()
def ephemeral_limit(self):
- return self.room_ephemeral_filter.limit()
+ return self._room_ephemeral_filter.limit()
def filter_presence(self, events):
- return self.presence_filter.filter(events)
+ return self._presence_filter.filter(events)
def filter_account_data(self, events):
- return self.account_data.filter(events)
+ return self._account_data.filter(events)
def filter_room_state(self, events):
- return self.room_state_filter.filter(self.room_filter.filter(events))
+ return self._room_state_filter.filter(self._room_filter.filter(events))
def filter_room_timeline(self, events):
- return self.room_timeline_filter.filter(self.room_filter.filter(events))
+ return self._room_timeline_filter.filter(self._room_filter.filter(events))
def filter_room_ephemeral(self, events):
- return self.room_ephemeral_filter.filter(self.room_filter.filter(events))
+ return self._room_ephemeral_filter.filter(self._room_filter.filter(events))
def filter_room_account_data(self, events):
- return self.room_account_data.filter(self.room_filter.filter(events))
+ return self._room_account_data.filter(self._room_filter.filter(events))
class Filter(object):
def __init__(self, filter_json):
self.filter_json = filter_json
- def list_rooms(self):
- """The list of room_id strings this filter restricts the output to
- or None if the this filter doesn't list the room ids.
- """
- if "rooms" in self.filter_json:
- return list(set(self.filter_json["rooms"]))
- else:
- return None
-
def check(self, event):
"""Checks whether the filter matches the given event.
Returns:
bool: True if the event matches
"""
- if isinstance(event, dict):
- return self.check_fields(
- event.get("room_id", None),
- event.get("sender", None),
- event.get("type", None),
- )
- else:
- return self.check_fields(
- getattr(event, "room_id", None),
- getattr(event, "sender", None),
- event.type,
- )
+ sender = event.get("sender", None)
+ if not sender:
+ # Presence events have their 'sender' in content.user_id
+ sender = event.get("content", {}).get("user_id", None)
+
+ return self.check_fields(
+ event.get("room_id", None),
+ sender,
+ event.get("type", None),
+ )
def check_fields(self, room_id, sender, event_type):
"""Checks whether the filter matches the given event fields.
@@ -270,3 +264,6 @@ def _matches_wildcard(actual_value, filter_value):
return actual_value.startswith(type_prefix)
else:
return actual_value == filter_value
+
+
+DEFAULT_FILTER_COLLECTION = FilterCollection({})
diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py
index 3f9ad4ce89..660dfb56e5 100644
--- a/synapse/api/ratelimiting.py
+++ b/synapse/api/ratelimiting.py
@@ -1,4 +1,4 @@
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/synapse/api/urls.py b/synapse/api/urls.py
index 15c8558ea7..0fd9b7f244 100644
--- a/synapse/api/urls.py
+++ b/synapse/api/urls.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2014-2016 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,5 +23,6 @@ WEB_CLIENT_PREFIX = "/_matrix/client"
CONTENT_REPO_PREFIX = "/_matrix/content"
SERVER_KEY_PREFIX = "/_matrix/key/v1"
SERVER_KEY_V2_PREFIX = "/_matrix/key/v2"
-MEDIA_PREFIX = "/_matrix/media/v1"
+MEDIA_PREFIX = "/_matrix/media/r0"
+LEGACY_MEDIA_PREFIX = "/_matrix/media/v1"
APP_SERVICE_PREFIX = "/_matrix/appservice/v1"
|