diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py
index 433237c204..6688fa8fa0 100644
--- a/synapse/rest/__init__.py
+++ b/synapse/rest/__init__.py
@@ -30,6 +30,7 @@ from synapse.rest.client.v1 import (
push_rule,
register as v1_register,
login as v1_login,
+ logout,
)
from synapse.rest.client.v2_alpha import (
@@ -72,6 +73,7 @@ class ClientRestResource(JsonResource):
admin.register_servlets(hs, client_resource)
pusher.register_servlets(hs, client_resource)
push_rule.register_servlets(hs, client_resource)
+ logout.register_servlets(hs, client_resource)
# "v2"
sync.register_servlets(hs, client_resource)
diff --git a/synapse/rest/client/v1/admin.py b/synapse/rest/client/v1/admin.py
index e2f5eb7b29..aa05b3f023 100644
--- a/synapse/rest/client/v1/admin.py
+++ b/synapse/rest/client/v1/admin.py
@@ -18,7 +18,7 @@ from twisted.internet import defer
from synapse.api.errors import AuthError, SynapseError
from synapse.types import UserID
-from base import ClientV1RestServlet, client_path_patterns
+from .base import ClientV1RestServlet, client_path_patterns
import logging
diff --git a/synapse/rest/client/v1/directory.py b/synapse/rest/client/v1/directory.py
index 74ec1e50e0..8ac09419dc 100644
--- a/synapse/rest/client/v1/directory.py
+++ b/synapse/rest/client/v1/directory.py
@@ -18,9 +18,10 @@ from twisted.internet import defer
from synapse.api.errors import AuthError, SynapseError, Codes
from synapse.types import RoomAlias
+from synapse.http.servlet import parse_json_object_from_request
+
from .base import ClientV1RestServlet, client_path_patterns
-import simplejson as json
import logging
@@ -29,6 +30,7 @@ logger = logging.getLogger(__name__)
def register_servlets(hs, http_server):
ClientDirectoryServer(hs).register(http_server)
+ ClientDirectoryListServer(hs).register(http_server)
class ClientDirectoryServer(ClientV1RestServlet):
@@ -45,7 +47,7 @@ class ClientDirectoryServer(ClientV1RestServlet):
@defer.inlineCallbacks
def on_PUT(self, request, room_alias):
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
if "room_id" not in content:
raise SynapseError(400, "Missing room_id key",
errcode=Codes.BAD_JSON)
@@ -75,7 +77,11 @@ class ClientDirectoryServer(ClientV1RestServlet):
yield dir_handler.create_association(
user_id, room_alias, room_id, servers
)
- yield dir_handler.send_room_alias_update_event(user_id, room_id)
+ yield dir_handler.send_room_alias_update_event(
+ requester,
+ user_id,
+ room_id
+ )
except SynapseError as e:
raise e
except:
@@ -118,15 +124,13 @@ class ClientDirectoryServer(ClientV1RestServlet):
requester = yield self.auth.get_user_by_req(request)
user = requester.user
- is_admin = yield self.auth.is_server_admin(user)
- if not is_admin:
- raise AuthError(403, "You need to be a server admin")
room_alias = RoomAlias.from_string(room_alias)
yield dir_handler.delete_association(
- user.to_string(), room_alias
+ requester, user.to_string(), room_alias
)
+
logger.info(
"User %s deleted alias %s",
user.to_string(),
@@ -136,12 +140,42 @@ class ClientDirectoryServer(ClientV1RestServlet):
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)
+class ClientDirectoryListServer(ClientV1RestServlet):
+ PATTERNS = client_path_patterns("/directory/list/room/(?P<room_id>[^/]*)$")
+
+ def __init__(self, hs):
+ super(ClientDirectoryListServer, self).__init__(hs)
+ self.store = hs.get_datastore()
+
+ @defer.inlineCallbacks
+ def on_GET(self, request, room_id):
+ room = yield self.store.get_room(room_id)
+ if room is None:
+ raise SynapseError(400, "Unknown room")
+
+ defer.returnValue((200, {
+ "visibility": "public" if room["is_public"] else "private"
+ }))
+
+ @defer.inlineCallbacks
+ def on_PUT(self, request, room_id):
+ requester = yield self.auth.get_user_by_req(request)
+
+ content = parse_json_object_from_request(request)
+ visibility = content.get("visibility", "public")
+
+ yield self.handlers.directory_handler.edit_published_room_list(
+ requester, room_id, visibility,
+ )
+
+ defer.returnValue((200, {}))
+
+ @defer.inlineCallbacks
+ def on_DELETE(self, request, room_id):
+ requester = yield self.auth.get_user_by_req(request)
+
+ yield self.handlers.directory_handler.edit_published_room_list(
+ requester, room_id, "private",
+ )
+
+ defer.returnValue((200, {}))
diff --git a/synapse/rest/client/v1/initial_sync.py b/synapse/rest/client/v1/initial_sync.py
index ad161bdbab..36c3520567 100644
--- a/synapse/rest/client/v1/initial_sync.py
+++ b/synapse/rest/client/v1/initial_sync.py
@@ -16,7 +16,7 @@
from twisted.internet import defer
from synapse.streams.config import PaginationConfig
-from base import ClientV1RestServlet, client_path_patterns
+from .base import ClientV1RestServlet, client_path_patterns
# TODO: Needs unit testing
diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py
index 7199113dac..fe593d07ce 100644
--- a/synapse/rest/client/v1/login.py
+++ b/synapse/rest/client/v1/login.py
@@ -17,7 +17,10 @@ from twisted.internet import defer
from synapse.api.errors import SynapseError, LoginError, Codes
from synapse.types import UserID
-from base import ClientV1RestServlet, client_path_patterns
+from synapse.http.server import finish_request
+from synapse.http.servlet import parse_json_object_from_request
+
+from .base import ClientV1RestServlet, client_path_patterns
import simplejson as json
import urllib
@@ -77,7 +80,7 @@ class LoginRestServlet(ClientV1RestServlet):
@defer.inlineCallbacks
def on_POST(self, request):
- login_submission = _parse_json(request)
+ login_submission = parse_json_object_from_request(request)
try:
if login_submission["type"] == LoginRestServlet.PASS_TYPE:
if not self.password_enabled:
@@ -250,7 +253,7 @@ class SAML2RestServlet(ClientV1RestServlet):
SP = Saml2Client(conf)
saml2_auth = SP.parse_authn_request_response(
request.args['SAMLResponse'][0], BINDING_HTTP_POST)
- except Exception, e: # Not authenticated
+ except Exception as e: # Not authenticated
logger.exception(e)
if saml2_auth and saml2_auth.status_ok() and not saml2_auth.not_signed:
username = saml2_auth.name_id.text
@@ -263,7 +266,7 @@ class SAML2RestServlet(ClientV1RestServlet):
'?status=authenticated&access_token=' +
token + '&user_id=' + user_id + '&ava=' +
urllib.quote(json.dumps(saml2_auth.ava)))
- request.finish()
+ finish_request(request)
defer.returnValue(None)
defer.returnValue((200, {"status": "authenticated",
"user_id": user_id, "token": token,
@@ -272,7 +275,7 @@ class SAML2RestServlet(ClientV1RestServlet):
request.redirect(urllib.unquote(
request.args['RelayState'][0]) +
'?status=not_authenticated')
- request.finish()
+ finish_request(request)
defer.returnValue(None)
defer.returnValue((200, {"status": "not_authenticated"}))
@@ -309,7 +312,7 @@ class CasRedirectServlet(ClientV1RestServlet):
"service": "%s?%s" % (hs_redirect_url, client_redirect_url_param)
})
request.redirect("%s?%s" % (self.cas_server_url, service_param))
- request.finish()
+ finish_request(request)
class CasTicketServlet(ClientV1RestServlet):
@@ -362,7 +365,7 @@ class CasTicketServlet(ClientV1RestServlet):
redirect_url = self.add_login_token_to_redirect_url(client_redirect_url,
login_token)
request.redirect(redirect_url)
- request.finish()
+ finish_request(request)
def add_login_token_to_redirect_url(self, url, token):
url_parts = list(urlparse.urlparse(url))
@@ -398,16 +401,6 @@ class CasTicketServlet(ClientV1RestServlet):
return (user, attributes)
-def _parse_json(request):
- try:
- content = json.loads(request.content.read())
- if type(content) != dict:
- raise SynapseError(400, "Content must be a JSON object.")
- return content
- except ValueError:
- raise SynapseError(400, "Content not JSON.")
-
-
def register_servlets(hs, http_server):
LoginRestServlet(hs).register(http_server)
if hs.config.saml2_enabled:
diff --git a/synapse/rest/client/v1/logout.py b/synapse/rest/client/v1/logout.py
new file mode 100644
index 0000000000..9bff02ee4e
--- /dev/null
+++ b/synapse/rest/client/v1/logout.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Copyright 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from twisted.internet import defer
+
+from synapse.api.errors import AuthError, Codes
+
+from .base import ClientV1RestServlet, client_path_patterns
+
+import logging
+
+
+logger = logging.getLogger(__name__)
+
+
+class LogoutRestServlet(ClientV1RestServlet):
+ PATTERNS = client_path_patterns("/logout$")
+
+ def __init__(self, hs):
+ super(LogoutRestServlet, self).__init__(hs)
+ self.store = hs.get_datastore()
+
+ def on_OPTIONS(self, request):
+ return (200, {})
+
+ @defer.inlineCallbacks
+ def on_POST(self, request):
+ try:
+ access_token = request.args["access_token"][0]
+ except KeyError:
+ raise AuthError(
+ self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token.",
+ errcode=Codes.MISSING_TOKEN
+ )
+ yield self.store.delete_access_token(access_token)
+ defer.returnValue((200, {}))
+
+
+class LogoutAllRestServlet(ClientV1RestServlet):
+ PATTERNS = client_path_patterns("/logout/all$")
+
+ def __init__(self, hs):
+ super(LogoutAllRestServlet, self).__init__(hs)
+ self.store = hs.get_datastore()
+ self.auth = hs.get_auth()
+
+ def on_OPTIONS(self, request):
+ return (200, {})
+
+ @defer.inlineCallbacks
+ def on_POST(self, request):
+ requester = yield self.auth.get_user_by_req(request)
+ user_id = requester.user.to_string()
+ yield self.store.user_delete_access_tokens(user_id)
+ defer.returnValue((200, {}))
+
+
+def register_servlets(hs, http_server):
+ LogoutRestServlet(hs).register(http_server)
+ LogoutAllRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v1/presence.py b/synapse/rest/client/v1/presence.py
index a6f8754e32..27d9ed586b 100644
--- a/synapse/rest/client/v1/presence.py
+++ b/synapse/rest/client/v1/presence.py
@@ -17,11 +17,11 @@
"""
from twisted.internet import defer
-from synapse.api.errors import SynapseError
+from synapse.api.errors import SynapseError, AuthError
from synapse.types import UserID
+from synapse.http.servlet import parse_json_object_from_request
from .base import ClientV1RestServlet, client_path_patterns
-import simplejson as json
import logging
logger = logging.getLogger(__name__)
@@ -35,8 +35,15 @@ class PresenceStatusRestServlet(ClientV1RestServlet):
requester = yield self.auth.get_user_by_req(request)
user = UserID.from_string(user_id)
- state = yield self.handlers.presence_handler.get_state(
- target_user=user, auth_user=requester.user)
+ if requester.user != user:
+ allowed = yield self.handlers.presence_handler.is_visible(
+ observed_user=user, observer_user=requester.user,
+ )
+
+ if not allowed:
+ raise AuthError(403, "You are not allowed to see their presence.")
+
+ state = yield self.handlers.presence_handler.get_state(target_user=user)
defer.returnValue((200, state))
@@ -45,10 +52,14 @@ class PresenceStatusRestServlet(ClientV1RestServlet):
requester = yield self.auth.get_user_by_req(request)
user = UserID.from_string(user_id)
+ if requester.user != user:
+ raise AuthError(403, "Can only set your own presence state")
+
state = {}
- try:
- content = json.loads(request.content.read())
+ content = parse_json_object_from_request(request)
+
+ try:
state["presence"] = content.pop("presence")
if "status_msg" in content:
@@ -63,8 +74,7 @@ class PresenceStatusRestServlet(ClientV1RestServlet):
except:
raise SynapseError(400, "Unable to parse state")
- yield self.handlers.presence_handler.set_state(
- target_user=user, auth_user=requester.user, state=state)
+ yield self.handlers.presence_handler.set_state(user, state)
defer.returnValue((200, {}))
@@ -87,11 +97,8 @@ class PresenceListRestServlet(ClientV1RestServlet):
raise SynapseError(400, "Cannot get another user's presence list")
presence = yield self.handlers.presence_handler.get_presence_list(
- observer_user=user, accepted=True)
-
- for p in presence:
- observed_user = p.pop("observed_user")
- p["user_id"] = observed_user.to_string()
+ observer_user=user, accepted=True
+ )
defer.returnValue((200, presence))
@@ -107,11 +114,7 @@ class PresenceListRestServlet(ClientV1RestServlet):
raise SynapseError(
400, "Cannot modify another user's presence list")
- try:
- content = json.loads(request.content.read())
- except:
- logger.exception("JSON parse error")
- raise SynapseError(400, "Unable to parse content")
+ content = parse_json_object_from_request(request)
if "invite" in content:
for u in content["invite"]:
diff --git a/synapse/rest/client/v1/profile.py b/synapse/rest/client/v1/profile.py
index 3c5a212920..65c4e2ebef 100644
--- a/synapse/rest/client/v1/profile.py
+++ b/synapse/rest/client/v1/profile.py
@@ -18,8 +18,7 @@ from twisted.internet import defer
from .base import ClientV1RestServlet, client_path_patterns
from synapse.types import UserID
-
-import simplejson as json
+from synapse.http.servlet import parse_json_object_from_request
class ProfileDisplaynameRestServlet(ClientV1RestServlet):
@@ -44,14 +43,15 @@ class ProfileDisplaynameRestServlet(ClientV1RestServlet):
requester = yield self.auth.get_user_by_req(request, allow_guest=True)
user = UserID.from_string(user_id)
+ content = parse_json_object_from_request(request)
+
try:
- content = json.loads(request.content.read())
new_name = content["displayname"]
except:
defer.returnValue((400, "Unable to parse name"))
yield self.handlers.profile_handler.set_displayname(
- user, requester.user, new_name)
+ user, requester, new_name)
defer.returnValue((200, {}))
@@ -81,14 +81,14 @@ class ProfileAvatarURLRestServlet(ClientV1RestServlet):
requester = yield self.auth.get_user_by_req(request)
user = UserID.from_string(user_id)
+ content = parse_json_object_from_request(request)
try:
- content = json.loads(request.content.read())
new_name = content["avatar_url"]
except:
defer.returnValue((400, "Unable to parse name"))
yield self.handlers.profile_handler.set_avatar_url(
- user, requester.user, new_name)
+ user, requester, new_name)
defer.returnValue((200, {}))
diff --git a/synapse/rest/client/v1/push_rule.py b/synapse/rest/client/v1/push_rule.py
index 96633a176c..02d837ee6a 100644
--- a/synapse/rest/client/v1/push_rule.py
+++ b/synapse/rest/client/v1/push_rule.py
@@ -16,19 +16,16 @@
from twisted.internet import defer
from synapse.api.errors import (
- SynapseError, Codes, UnrecognizedRequestError, NotFoundError, StoreError
+ SynapseError, UnrecognizedRequestError, NotFoundError, StoreError
)
from .base import ClientV1RestServlet, client_path_patterns
from synapse.storage.push_rule import (
InconsistentRuleException, RuleNotFoundException
)
-import synapse.push.baserules as baserules
-from synapse.push.rulekinds import (
- PRIORITY_CLASS_MAP, PRIORITY_CLASS_INVERSE_MAP
-)
-
-import copy
-import simplejson as json
+from synapse.push.clientformat import format_push_rules_for_user
+from synapse.push.baserules import BASE_RULE_IDS
+from synapse.push.rulekinds import PRIORITY_CLASS_MAP
+from synapse.http.servlet import parse_json_value_from_request
class PushRuleRestServlet(ClientV1RestServlet):
@@ -36,6 +33,11 @@ class PushRuleRestServlet(ClientV1RestServlet):
SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR = (
"Unrecognised request: You probably wanted a trailing slash")
+ def __init__(self, hs):
+ super(PushRuleRestServlet, self).__init__(hs)
+ self.store = hs.get_datastore()
+ self.notifier = hs.get_notifier()
+
@defer.inlineCallbacks
def on_PUT(self, request):
spec = _rule_spec_from_path(request.postpath)
@@ -49,18 +51,24 @@ class PushRuleRestServlet(ClientV1RestServlet):
if '/' in spec['rule_id'] or '\\' in spec['rule_id']:
raise SynapseError(400, "rule_id may not contain slashes")
- content = _parse_json(request)
+ content = parse_json_value_from_request(request)
+
+ user_id = requester.user.to_string()
if 'attr' in spec:
- yield self.set_rule_attr(requester.user.to_string(), spec, content)
+ yield self.set_rule_attr(user_id, spec, content)
+ self.notify_user(user_id)
defer.returnValue((200, {}))
+ if spec['rule_id'].startswith('.'):
+ # Rule ids starting with '.' are reserved for server default rules.
+ raise SynapseError(400, "cannot add new rule_ids that start with '.'")
+
try:
(conditions, actions) = _rule_tuple_from_request_object(
spec['template'],
spec['rule_id'],
content,
- device=spec['device'] if 'device' in spec else None
)
except InvalidRuleException as e:
raise SynapseError(400, e.message)
@@ -74,8 +82,8 @@ class PushRuleRestServlet(ClientV1RestServlet):
after = _namespaced_rule_id(spec, after[0])
try:
- yield self.hs.get_datastore().add_push_rule(
- user_id=requester.user.to_string(),
+ yield self.store.add_push_rule(
+ user_id=user_id,
rule_id=_namespaced_rule_id_from_spec(spec),
priority_class=priority_class,
conditions=conditions,
@@ -83,6 +91,7 @@ class PushRuleRestServlet(ClientV1RestServlet):
before=before,
after=after
)
+ self.notify_user(user_id)
except InconsistentRuleException as e:
raise SynapseError(400, e.message)
except RuleNotFoundException as e:
@@ -95,13 +104,15 @@ class PushRuleRestServlet(ClientV1RestServlet):
spec = _rule_spec_from_path(request.postpath)
requester = yield self.auth.get_user_by_req(request)
+ user_id = requester.user.to_string()
namespaced_rule_id = _namespaced_rule_id_from_spec(spec)
try:
- yield self.hs.get_datastore().delete_push_rule(
- requester.user.to_string(), namespaced_rule_id
+ yield self.store.delete_push_rule(
+ user_id, namespaced_rule_id
)
+ self.notify_user(user_id)
defer.returnValue((200, {}))
except StoreError as e:
if e.code == 404:
@@ -112,74 +123,16 @@ class PushRuleRestServlet(ClientV1RestServlet):
@defer.inlineCallbacks
def on_GET(self, request):
requester = yield self.auth.get_user_by_req(request)
- user = requester.user
+ user_id = requester.user.to_string()
# we build up the full structure and then decide which bits of it
# to send which means doing unnecessary work sometimes but is
# is probably not going to make a whole lot of difference
- rawrules = yield self.hs.get_datastore().get_push_rules_for_user(
- user.to_string()
- )
+ rawrules = yield self.store.get_push_rules_for_user(user_id)
- ruleslist = []
- for rawrule in rawrules:
- rule = dict(rawrule)
- rule["conditions"] = json.loads(rawrule["conditions"])
- rule["actions"] = json.loads(rawrule["actions"])
- ruleslist.append(rule)
-
- # We're going to be mutating this a lot, so do a deep copy
- ruleslist = copy.deepcopy(baserules.list_with_base_rules(ruleslist))
-
- rules = {'global': {}, 'device': {}}
-
- rules['global'] = _add_empty_priority_class_arrays(rules['global'])
-
- enabled_map = yield self.hs.get_datastore().\
- get_push_rules_enabled_for_user(user.to_string())
-
- for r in ruleslist:
- rulearray = None
-
- template_name = _priority_class_to_template_name(r['priority_class'])
-
- # Remove internal stuff.
- for c in r["conditions"]:
- c.pop("_id", None)
-
- pattern_type = c.pop("pattern_type", None)
- if pattern_type == "user_id":
- c["pattern"] = user.to_string()
- elif pattern_type == "user_localpart":
- c["pattern"] = user.localpart
-
- if r['priority_class'] > PRIORITY_CLASS_MAP['override']:
- # per-device rule
- profile_tag = _profile_tag_from_conditions(r["conditions"])
- r = _strip_device_condition(r)
- if not profile_tag:
- continue
- if profile_tag not in rules['device']:
- rules['device'][profile_tag] = {}
- rules['device'][profile_tag] = (
- _add_empty_priority_class_arrays(
- rules['device'][profile_tag]
- )
- )
-
- rulearray = rules['device'][profile_tag][template_name]
- else:
- rulearray = rules['global'][template_name]
-
- template_rule = _rule_to_template(r)
- if template_rule:
- if r['rule_id'] in enabled_map:
- template_rule['enabled'] = enabled_map[r['rule_id']]
- elif 'enabled' in r:
- template_rule['enabled'] = r['enabled']
- else:
- template_rule['enabled'] = True
- rulearray.append(template_rule)
+ enabled_map = yield self.store.get_push_rules_enabled_for_user(user_id)
+
+ rules = format_push_rules_for_user(requester.user, rawrules, enabled_map)
path = request.postpath[1:]
@@ -195,30 +148,18 @@ class PushRuleRestServlet(ClientV1RestServlet):
path = path[1:]
result = _filter_ruleset_with_path(rules['global'], path)
defer.returnValue((200, result))
- elif path[0] == 'device':
- path = path[1:]
- if path == []:
- raise UnrecognizedRequestError(
- PushRuleRestServlet.SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR
- )
- if path[0] == '':
- defer.returnValue((200, rules['device']))
-
- profile_tag = path[0]
- path = path[1:]
- if profile_tag not in rules['device']:
- ret = {}
- ret = _add_empty_priority_class_arrays(ret)
- defer.returnValue((200, ret))
- ruleset = rules['device'][profile_tag]
- result = _filter_ruleset_with_path(ruleset, path)
- defer.returnValue((200, result))
else:
raise UnrecognizedRequestError()
def on_OPTIONS(self, _):
return 200, {}
+ def notify_user(self, user_id):
+ stream_id, _ = self.store.get_push_rules_stream_token()
+ self.notifier.on_new_event(
+ "push_rules_key", stream_id, users=[user_id]
+ )
+
def set_rule_attr(self, user_id, spec, val):
if spec['attr'] == 'enabled':
if isinstance(val, dict) and "enabled" in val:
@@ -229,16 +170,20 @@ class PushRuleRestServlet(ClientV1RestServlet):
# bools directly, so let's not break them.
raise SynapseError(400, "Value for 'enabled' must be boolean")
namespaced_rule_id = _namespaced_rule_id_from_spec(spec)
- return self.hs.get_datastore().set_push_rule_enabled(
+ return self.store.set_push_rule_enabled(
user_id, namespaced_rule_id, val
)
- else:
- raise UnrecognizedRequestError()
-
- def get_rule_attr(self, user_id, namespaced_rule_id, attr):
- if attr == 'enabled':
- return self.hs.get_datastore().get_push_rule_enabled_by_user_rule_id(
- user_id, namespaced_rule_id
+ elif spec['attr'] == 'actions':
+ actions = val.get('actions')
+ _check_actions(actions)
+ namespaced_rule_id = _namespaced_rule_id_from_spec(spec)
+ rule_id = spec['rule_id']
+ is_default_rule = rule_id.startswith(".")
+ if is_default_rule:
+ if namespaced_rule_id not in BASE_RULE_IDS:
+ raise SynapseError(404, "Unknown rule %r" % (namespaced_rule_id,))
+ return self.store.set_push_rule_actions(
+ user_id, namespaced_rule_id, actions, is_default_rule
)
else:
raise UnrecognizedRequestError()
@@ -252,16 +197,9 @@ def _rule_spec_from_path(path):
scope = path[1]
path = path[2:]
- if scope not in ['global', 'device']:
+ if scope != 'global':
raise UnrecognizedRequestError()
- device = None
- if scope == 'device':
- if len(path) == 0:
- raise UnrecognizedRequestError()
- device = path[0]
- path = path[1:]
-
if len(path) == 0:
raise UnrecognizedRequestError()
@@ -278,8 +216,6 @@ def _rule_spec_from_path(path):
'template': template,
'rule_id': rule_id
}
- if device:
- spec['profile_tag'] = device
path = path[1:]
@@ -289,7 +225,7 @@ def _rule_spec_from_path(path):
return spec
-def _rule_tuple_from_request_object(rule_template, rule_id, req_obj, device=None):
+def _rule_tuple_from_request_object(rule_template, rule_id, req_obj):
if rule_template in ['override', 'underride']:
if 'conditions' not in req_obj:
raise InvalidRuleException("Missing 'conditions'")
@@ -322,16 +258,19 @@ def _rule_tuple_from_request_object(rule_template, rule_id, req_obj, device=None
else:
raise InvalidRuleException("Unknown rule template: %s" % (rule_template,))
- if device:
- conditions.append({
- 'kind': 'device',
- 'profile_tag': device
- })
-
if 'actions' not in req_obj:
raise InvalidRuleException("No actions found")
actions = req_obj['actions']
+ _check_actions(actions)
+
+ return conditions, actions
+
+
+def _check_actions(actions):
+ if not isinstance(actions, list):
+ raise InvalidRuleException("No actions found")
+
for a in actions:
if a in ['notify', 'dont_notify', 'coalesce']:
pass
@@ -340,25 +279,6 @@ def _rule_tuple_from_request_object(rule_template, rule_id, req_obj, device=None
else:
raise InvalidRuleException("Unrecognised action")
- return conditions, actions
-
-
-def _add_empty_priority_class_arrays(d):
- for pc in PRIORITY_CLASS_MAP.keys():
- d[pc] = []
- return d
-
-
-def _profile_tag_from_conditions(conditions):
- """
- Given a list of conditions, return the profile tag of the
- device rule if there is one
- """
- for c in conditions:
- if c['kind'] == 'device':
- return c['profile_tag']
- return None
-
def _filter_ruleset_with_path(ruleset, path):
if path == []:
@@ -393,93 +313,32 @@ def _filter_ruleset_with_path(ruleset, path):
attr = path[0]
if attr in the_rule:
- return the_rule[attr]
+ # Make sure we return a JSON object as the attribute may be a
+ # JSON value.
+ return {attr: the_rule[attr]}
else:
raise UnrecognizedRequestError()
def _priority_class_from_spec(spec):
if spec['template'] not in PRIORITY_CLASS_MAP.keys():
- raise InvalidRuleException("Unknown template: %s" % (spec['kind']))
+ raise InvalidRuleException("Unknown template: %s" % (spec['template']))
pc = PRIORITY_CLASS_MAP[spec['template']]
- if spec['scope'] == 'device':
- pc += len(PRIORITY_CLASS_MAP)
-
return pc
-def _priority_class_to_template_name(pc):
- if pc > PRIORITY_CLASS_MAP['override']:
- # per-device
- prio_class_index = pc - len(PRIORITY_CLASS_MAP)
- return PRIORITY_CLASS_INVERSE_MAP[prio_class_index]
- else:
- return PRIORITY_CLASS_INVERSE_MAP[pc]
-
-
-def _rule_to_template(rule):
- unscoped_rule_id = None
- if 'rule_id' in rule:
- unscoped_rule_id = _rule_id_from_namespaced(rule['rule_id'])
-
- template_name = _priority_class_to_template_name(rule['priority_class'])
- if template_name in ['override', 'underride']:
- templaterule = {k: rule[k] for k in ["conditions", "actions"]}
- elif template_name in ["sender", "room"]:
- templaterule = {'actions': rule['actions']}
- unscoped_rule_id = rule['conditions'][0]['pattern']
- elif template_name == 'content':
- if len(rule["conditions"]) != 1:
- return None
- thecond = rule["conditions"][0]
- if "pattern" not in thecond:
- return None
- templaterule = {'actions': rule['actions']}
- templaterule["pattern"] = thecond["pattern"]
-
- if unscoped_rule_id:
- templaterule['rule_id'] = unscoped_rule_id
- if 'default' in rule:
- templaterule['default'] = rule['default']
- return templaterule
-
-
-def _strip_device_condition(rule):
- for i, c in enumerate(rule['conditions']):
- if c['kind'] == 'device':
- del rule['conditions'][i]
- return rule
-
-
def _namespaced_rule_id_from_spec(spec):
return _namespaced_rule_id(spec, spec['rule_id'])
def _namespaced_rule_id(spec, rule_id):
- if spec['scope'] == 'global':
- scope = 'global'
- else:
- scope = 'device/%s' % (spec['profile_tag'])
- return "%s/%s/%s" % (scope, spec['template'], rule_id)
-
-
-def _rule_id_from_namespaced(in_rule_id):
- return in_rule_id.split('/')[-1]
+ return "global/%s/%s" % (spec['template'], rule_id)
class InvalidRuleException(Exception):
pass
-# XXX: C+ped from rest/room.py - surely this should be common?
-def _parse_json(request):
- try:
- content = json.loads(request.content.read())
- return content
- except ValueError:
- raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
-
-
def register_servlets(hs, http_server):
PushRuleRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v1/pusher.py b/synapse/rest/client/v1/pusher.py
index 5547f1b112..9881f068c3 100644
--- a/synapse/rest/client/v1/pusher.py
+++ b/synapse/rest/client/v1/pusher.py
@@ -17,9 +17,10 @@ from twisted.internet import defer
from synapse.api.errors import SynapseError, Codes
from synapse.push import PusherConfigException
+from synapse.http.servlet import parse_json_object_from_request
+
from .base import ClientV1RestServlet, client_path_patterns
-import simplejson as json
import logging
logger = logging.getLogger(__name__)
@@ -28,12 +29,16 @@ logger = logging.getLogger(__name__)
class PusherRestServlet(ClientV1RestServlet):
PATTERNS = client_path_patterns("/pushers/set$")
+ def __init__(self, hs):
+ super(PusherRestServlet, self).__init__(hs)
+ self.notifier = hs.get_notifier()
+
@defer.inlineCallbacks
def on_POST(self, request):
requester = yield self.auth.get_user_by_req(request)
user = requester.user
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
pusher_pool = self.hs.get_pusherpool()
@@ -45,7 +50,7 @@ class PusherRestServlet(ClientV1RestServlet):
)
defer.returnValue((200, {}))
- reqd = ['profile_tag', 'kind', 'app_id', 'app_display_name',
+ reqd = ['kind', 'app_id', 'app_display_name',
'device_display_name', 'pushkey', 'lang', 'data']
missing = []
for i in reqd:
@@ -73,36 +78,26 @@ class PusherRestServlet(ClientV1RestServlet):
yield pusher_pool.add_pusher(
user_id=user.to_string(),
access_token=requester.access_token_id,
- profile_tag=content['profile_tag'],
kind=content['kind'],
app_id=content['app_id'],
app_display_name=content['app_display_name'],
device_display_name=content['device_display_name'],
pushkey=content['pushkey'],
lang=content['lang'],
- data=content['data']
+ data=content['data'],
+ profile_tag=content.get('profile_tag', ""),
)
except PusherConfigException as pce:
raise SynapseError(400, "Config Error: " + pce.message,
errcode=Codes.MISSING_PARAM)
+ self.notifier.on_new_replication_data()
+
defer.returnValue((200, {}))
def on_OPTIONS(self, _):
return 200, {}
-# XXX: C+ped from rest/room.py - surely this should be common?
-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)
-
-
def register_servlets(hs, http_server):
PusherRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v1/register.py b/synapse/rest/client/v1/register.py
index 6d6d03c34c..c6a2ef2ccc 100644
--- a/synapse/rest/client/v1/register.py
+++ b/synapse/rest/client/v1/register.py
@@ -18,14 +18,14 @@ from twisted.internet import defer
from synapse.api.errors import SynapseError, Codes
from synapse.api.constants import LoginType
-from base import ClientV1RestServlet, client_path_patterns
+from .base import ClientV1RestServlet, client_path_patterns
import synapse.util.stringutils as stringutils
+from synapse.http.servlet import parse_json_object_from_request
from synapse.util.async import run_on_reactor
from hashlib import sha1
import hmac
-import simplejson as json
import logging
logger = logging.getLogger(__name__)
@@ -98,7 +98,7 @@ class RegisterRestServlet(ClientV1RestServlet):
@defer.inlineCallbacks
def on_POST(self, request):
- register_json = _parse_json(request)
+ register_json = parse_json_object_from_request(request)
session = (register_json["session"]
if "session" in register_json else None)
@@ -355,15 +355,5 @@ class RegisterRestServlet(ClientV1RestServlet):
)
-def _parse_json(request):
- try:
- content = json.loads(request.content.read())
- if type(content) != dict:
- raise SynapseError(400, "Content must be a JSON object.")
- return content
- except ValueError:
- raise SynapseError(400, "Content not JSON.")
-
-
def register_servlets(hs, http_server):
RegisterRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py
index 81bfe377bd..a1fa7daf79 100644
--- a/synapse/rest/client/v1/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -16,14 +16,14 @@
""" This module contains REST servlets to do with rooms: /rooms/<paths> """
from twisted.internet import defer
-from base import ClientV1RestServlet, client_path_patterns
+from .base import ClientV1RestServlet, client_path_patterns
from synapse.api.errors import SynapseError, Codes, AuthError
from synapse.streams.config import PaginationConfig
from synapse.api.constants import EventTypes, Membership
from synapse.types import UserID, RoomID, RoomAlias
from synapse.events.utils import serialize_event
+from synapse.http.servlet import parse_json_object_from_request
-import simplejson as json
import logging
import urllib
@@ -63,35 +63,18 @@ class RoomCreateRestServlet(ClientV1RestServlet):
def on_POST(self, request):
requester = yield self.auth.get_user_by_req(request)
- room_config = self.get_room_config(request)
- info = yield self.make_room(
- room_config,
- requester.user,
- None,
- )
- room_config.update(info)
- defer.returnValue((200, info))
-
- @defer.inlineCallbacks
- def make_room(self, room_config, auth_user, room_id):
handler = self.handlers.room_creation_handler
info = yield handler.create_room(
- user_id=auth_user.to_string(),
- room_id=room_id,
- config=room_config
+ requester, self.get_room_config(request)
)
- defer.returnValue(info)
+
+ defer.returnValue((200, info))
def get_room_config(self, request):
- try:
- user_supplied_config = json.loads(request.content.read())
- if "visibility" not in user_supplied_config:
- # default visibility
- user_supplied_config["visibility"] = "public"
- return user_supplied_config
- except (ValueError, TypeError):
- raise SynapseError(400, "Body must be JSON.",
- errcode=Codes.BAD_JSON)
+ user_supplied_config = parse_json_object_from_request(request)
+ # default visibility
+ user_supplied_config.setdefault("visibility", "public")
+ return user_supplied_config
def on_OPTIONS(self, request):
return (200, {})
@@ -149,7 +132,7 @@ class RoomStateEventRestServlet(ClientV1RestServlet):
def on_PUT(self, request, room_id, event_type, state_key, txn_id=None):
requester = yield self.auth.get_user_by_req(request)
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
event_dict = {
"type": event_type,
@@ -162,11 +145,22 @@ class RoomStateEventRestServlet(ClientV1RestServlet):
event_dict["state_key"] = state_key
msg_handler = self.handlers.message_handler
- yield msg_handler.create_and_send_event(
- event_dict, token_id=requester.access_token_id, txn_id=txn_id,
+ event, context = yield msg_handler.create_event(
+ event_dict,
+ token_id=requester.access_token_id,
+ txn_id=txn_id,
)
- defer.returnValue((200, {}))
+ if event_type == EventTypes.Member:
+ yield self.handlers.room_member_handler.send_membership_event(
+ requester,
+ event,
+ context,
+ )
+ else:
+ yield msg_handler.send_nonmember_event(requester, event, context)
+
+ defer.returnValue((200, {"event_id": event.event_id}))
# TODO: Needs unit testing for generic events + feedback
@@ -180,17 +174,17 @@ class RoomSendEventRestServlet(ClientV1RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, room_id, event_type, txn_id=None):
requester = yield self.auth.get_user_by_req(request, allow_guest=True)
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
msg_handler = self.handlers.message_handler
- event = yield msg_handler.create_and_send_event(
+ event = yield msg_handler.create_and_send_nonmember_event(
+ requester,
{
"type": event_type,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
},
- token_id=requester.access_token_id,
txn_id=txn_id,
)
@@ -229,46 +223,37 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
allow_guest=True,
)
- # the identifier could be a room alias or a room id. Try one then the
- # other if it fails to parse, without swallowing other valid
- # SynapseErrors.
-
- identifier = None
- is_room_alias = False
try:
- identifier = RoomAlias.from_string(room_identifier)
- is_room_alias = True
- except SynapseError:
- identifier = RoomID.from_string(room_identifier)
-
- # TODO: Support for specifying the home server to join with?
-
- if is_room_alias:
+ content = parse_json_object_from_request(request)
+ except:
+ # Turns out we used to ignore the body entirely, and some clients
+ # cheekily send invalid bodies.
+ content = {}
+
+ if RoomID.is_valid(room_identifier):
+ room_id = room_identifier
+ remote_room_hosts = None
+ elif RoomAlias.is_valid(room_identifier):
handler = self.handlers.room_member_handler
- ret_dict = yield handler.join_room_alias(
- requester.user,
- identifier,
- )
- defer.returnValue((200, ret_dict))
- else: # room id
- msg_handler = self.handlers.message_handler
- content = {"membership": Membership.JOIN}
- if requester.is_guest:
- content["kind"] = "guest"
- yield msg_handler.create_and_send_event(
- {
- "type": EventTypes.Member,
- "content": content,
- "room_id": identifier.to_string(),
- "sender": requester.user.to_string(),
- "state_key": requester.user.to_string(),
- },
- token_id=requester.access_token_id,
- txn_id=txn_id,
- is_guest=requester.is_guest,
- )
+ room_alias = RoomAlias.from_string(room_identifier)
+ room_id, remote_room_hosts = yield handler.lookup_room_alias(room_alias)
+ room_id = room_id.to_string()
+ else:
+ raise SynapseError(400, "%s was not legal room ID or room alias" % (
+ room_identifier,
+ ))
+
+ yield self.handlers.room_member_handler.update_membership(
+ requester=requester,
+ target=requester.user,
+ room_id=room_id,
+ action="join",
+ txn_id=txn_id,
+ remote_room_hosts=remote_room_hosts,
+ third_party_signed=content.get("third_party_signed", None),
+ )
- defer.returnValue((200, {"room_id": identifier.to_string()}))
+ defer.returnValue((200, {"room_id": room_id}))
@defer.inlineCallbacks
def on_PUT(self, request, room_identifier, txn_id):
@@ -316,18 +301,6 @@ class RoomMemberListRestServlet(ClientV1RestServlet):
if event["type"] != EventTypes.Member:
continue
chunk.append(event)
- # FIXME: should probably be state_key here, not user_id
- target_user = UserID.from_string(event["user_id"])
- # Presence is an optional cache; don't fail if we can't fetch it
- try:
- presence_handler = self.handlers.presence_handler
- presence_state = yield presence_handler.get_state(
- target_user=target_user,
- auth_user=requester.user,
- )
- event["content"].update(presence_state)
- except:
- pass
defer.returnValue((200, {
"chunk": chunk
@@ -454,7 +427,12 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
}:
raise AuthError(403, "Guest access not allowed")
- content = _parse_json(request)
+ try:
+ content = parse_json_object_from_request(request)
+ except:
+ # Turns out we used to ignore the body entirely, and some clients
+ # cheekily send invalid bodies.
+ content = {}
if membership_action == "invite" and self._has_3pid_invite_keys(content):
yield self.handlers.room_member_handler.do_3pid_invite(
@@ -463,7 +441,7 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
content["medium"],
content["address"],
content["id_server"],
- requester.access_token_id,
+ requester,
txn_id
)
defer.returnValue((200, {}))
@@ -481,6 +459,7 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
room_id=room_id,
action=membership_action,
txn_id=txn_id,
+ third_party_signed=content.get("third_party_signed", None),
)
defer.returnValue((200, {}))
@@ -516,10 +495,11 @@ class RoomRedactEventRestServlet(ClientV1RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, room_id, event_id, txn_id=None):
requester = yield self.auth.get_user_by_req(request)
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
msg_handler = self.handlers.message_handler
- event = yield msg_handler.create_and_send_event(
+ event = yield msg_handler.create_and_send_nonmember_event(
+ requester,
{
"type": EventTypes.Redaction,
"content": content,
@@ -527,7 +507,6 @@ class RoomRedactEventRestServlet(ClientV1RestServlet):
"sender": requester.user.to_string(),
"redacts": event_id,
},
- token_id=requester.access_token_id,
txn_id=txn_id,
)
@@ -553,6 +532,10 @@ class RoomTypingRestServlet(ClientV1RestServlet):
"/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$"
)
+ def __init__(self, hs):
+ super(RoomTypingRestServlet, self).__init__(hs)
+ self.presence_handler = hs.get_handlers().presence_handler
+
@defer.inlineCallbacks
def on_PUT(self, request, room_id, user_id):
requester = yield self.auth.get_user_by_req(request)
@@ -560,10 +543,12 @@ class RoomTypingRestServlet(ClientV1RestServlet):
room_id = urllib.unquote(room_id)
target_user = UserID.from_string(urllib.unquote(user_id))
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
typing_handler = self.handlers.typing_notification_handler
+ yield self.presence_handler.bump_presence_active_time(requester.user)
+
if content["typing"]:
yield typing_handler.started_typing(
target_user=target_user,
@@ -590,7 +575,7 @@ class SearchRestServlet(ClientV1RestServlet):
def on_POST(self, request):
requester = yield self.auth.get_user_by_req(request)
- content = _parse_json(request)
+ content = parse_json_object_from_request(request)
batch = request.args.get("next_batch", [None])[0]
results = yield self.handlers.search_handler.search(
@@ -602,17 +587,6 @@ class SearchRestServlet(ClientV1RestServlet):
defer.returnValue((200, results))
-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)
-
-
def register_txn_path(servlet, regex_string, http_server, with_get=False):
"""Registers a transaction-based path.
diff --git a/synapse/rest/client/v1/voip.py b/synapse/rest/client/v1/voip.py
index ec4cf8db79..c40442f958 100644
--- a/synapse/rest/client/v1/voip.py
+++ b/synapse/rest/client/v1/voip.py
@@ -15,7 +15,7 @@
from twisted.internet import defer
-from base import ClientV1RestServlet, client_path_patterns
+from .base import ClientV1RestServlet, client_path_patterns
import hmac
diff --git a/synapse/rest/client/v2_alpha/_base.py b/synapse/rest/client/v2_alpha/_base.py
index 24af322126..b6faa2b0e6 100644
--- a/synapse/rest/client/v2_alpha/_base.py
+++ b/synapse/rest/client/v2_alpha/_base.py
@@ -17,11 +17,9 @@
"""
from synapse.api.urls import CLIENT_V2_ALPHA_PREFIX
-from synapse.api.errors import SynapseError
import re
import logging
-import simplejson
logger = logging.getLogger(__name__)
@@ -44,23 +42,3 @@ def client_v2_patterns(path_regex, releases=(0,)):
new_prefix = CLIENT_V2_ALPHA_PREFIX.replace("/v2_alpha", "/r%d" % release)
patterns.append(re.compile("^" + new_prefix + path_regex))
return patterns
-
-
-def parse_request_allow_empty(request):
- content = request.content.read()
- if content is None or content == '':
- return None
- try:
- return simplejson.loads(content)
- except simplejson.JSONDecodeError:
- raise SynapseError(400, "Content not JSON.")
-
-
-def parse_json_dict_from_request(request):
- try:
- content = simplejson.loads(request.content.read())
- if type(content) != dict:
- raise SynapseError(400, "Content must be a JSON object.")
- return content
- except simplejson.JSONDecodeError:
- raise SynapseError(400, "Content not JSON.")
diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py
index a614b79d45..7f8a6a4cf7 100644
--- a/synapse/rest/client/v2_alpha/account.py
+++ b/synapse/rest/client/v2_alpha/account.py
@@ -17,10 +17,10 @@ from twisted.internet import defer
from synapse.api.constants import LoginType
from synapse.api.errors import LoginError, SynapseError, Codes
-from synapse.http.servlet import RestServlet
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.util.async import run_on_reactor
-from ._base import client_v2_patterns, parse_json_dict_from_request
+from ._base import client_v2_patterns
import logging
@@ -41,9 +41,9 @@ class PasswordRestServlet(RestServlet):
def on_POST(self, request):
yield run_on_reactor()
- body = parse_json_dict_from_request(request)
+ body = parse_json_object_from_request(request)
- authed, result, params = yield self.auth_handler.check_auth([
+ authed, result, params, _ = yield self.auth_handler.check_auth([
[LoginType.PASSWORD],
[LoginType.EMAIL_IDENTITY]
], body, self.hs.get_ip_from_request(request))
@@ -79,7 +79,7 @@ class PasswordRestServlet(RestServlet):
new_password = params['new_password']
yield self.auth_handler.set_password(
- user_id, new_password
+ user_id, new_password, requester
)
defer.returnValue((200, {}))
@@ -114,7 +114,7 @@ class ThreepidRestServlet(RestServlet):
def on_POST(self, request):
yield run_on_reactor()
- body = parse_json_dict_from_request(request)
+ body = parse_json_object_from_request(request)
threePidCreds = body.get('threePidCreds')
threePidCreds = body.get('three_pid_creds', threePidCreds)
diff --git a/synapse/rest/client/v2_alpha/account_data.py b/synapse/rest/client/v2_alpha/account_data.py
index 1456881c1a..b16079cece 100644
--- a/synapse/rest/client/v2_alpha/account_data.py
+++ b/synapse/rest/client/v2_alpha/account_data.py
@@ -15,15 +15,13 @@
from ._base import client_v2_patterns
-from synapse.http.servlet import RestServlet
-from synapse.api.errors import AuthError, SynapseError
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
+from synapse.api.errors import AuthError
from twisted.internet import defer
import logging
-import simplejson as json
-
logger = logging.getLogger(__name__)
@@ -47,11 +45,7 @@ class AccountDataServlet(RestServlet):
if user_id != requester.user.to_string():
raise AuthError(403, "Cannot add account data for other users.")
- try:
- content_bytes = request.content.read()
- body = json.loads(content_bytes)
- except:
- raise SynapseError(400, "Invalid JSON")
+ body = parse_json_object_from_request(request)
max_id = yield self.store.add_account_data_for_user(
user_id, account_data_type, body
@@ -86,14 +80,7 @@ class RoomAccountDataServlet(RestServlet):
if user_id != requester.user.to_string():
raise AuthError(403, "Cannot add account data for other users.")
- try:
- content_bytes = request.content.read()
- body = json.loads(content_bytes)
- except:
- raise SynapseError(400, "Invalid JSON")
-
- if not isinstance(body, dict):
- raise ValueError("Expected a JSON object")
+ body = parse_json_object_from_request(request)
max_id = yield self.store.add_account_data_to_room(
user_id, room_id, account_data_type, body
diff --git a/synapse/rest/client/v2_alpha/auth.py b/synapse/rest/client/v2_alpha/auth.py
index ff71c40b43..78181b7b18 100644
--- a/synapse/rest/client/v2_alpha/auth.py
+++ b/synapse/rest/client/v2_alpha/auth.py
@@ -18,6 +18,7 @@ from twisted.internet import defer
from synapse.api.constants import LoginType
from synapse.api.errors import SynapseError
from synapse.api.urls import CLIENT_V2_ALPHA_PREFIX
+from synapse.http.server import finish_request
from synapse.http.servlet import RestServlet
from ._base import client_v2_patterns
@@ -130,7 +131,7 @@ class AuthRestServlet(RestServlet):
request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),))
request.write(html_bytes)
- request.finish()
+ finish_request(request)
defer.returnValue(None)
else:
raise SynapseError(404, "Unknown auth stage type")
@@ -176,7 +177,7 @@ class AuthRestServlet(RestServlet):
request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),))
request.write(html_bytes)
- request.finish()
+ finish_request(request)
defer.returnValue(None)
else:
diff --git a/synapse/rest/client/v2_alpha/filter.py b/synapse/rest/client/v2_alpha/filter.py
index 7c94f6ec41..510f8b2c74 100644
--- a/synapse/rest/client/v2_alpha/filter.py
+++ b/synapse/rest/client/v2_alpha/filter.py
@@ -16,12 +16,11 @@
from twisted.internet import defer
from synapse.api.errors import AuthError, SynapseError
-from synapse.http.servlet import RestServlet
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.types import UserID
from ._base import client_v2_patterns
-import simplejson as json
import logging
@@ -84,12 +83,7 @@ class CreateFilterRestServlet(RestServlet):
if not self.hs.is_mine(target_user):
raise SynapseError(400, "Can only create filters for local users")
- try:
- content = json.loads(request.content.read())
-
- # TODO(paul): check for required keys and invalid keys
- except:
- raise SynapseError(400, "Invalid filter definition")
+ content = parse_json_object_from_request(request)
filter_id = yield self.filtering.add_user_filter(
user_localpart=target_user.localpart,
diff --git a/synapse/rest/client/v2_alpha/keys.py b/synapse/rest/client/v2_alpha/keys.py
index f989b08614..89ab39491c 100644
--- a/synapse/rest/client/v2_alpha/keys.py
+++ b/synapse/rest/client/v2_alpha/keys.py
@@ -15,16 +15,15 @@
from twisted.internet import defer
-from synapse.api.errors import SynapseError
-from synapse.http.servlet import RestServlet
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.types import UserID
from canonicaljson import encode_canonical_json
from ._base import client_v2_patterns
-import simplejson as json
import logging
+import simplejson as json
logger = logging.getLogger(__name__)
@@ -68,10 +67,9 @@ class KeyUploadServlet(RestServlet):
user_id = requester.user.to_string()
# TODO: Check that the device_id matches that in the authentication
# or derive the device_id from the authentication instead.
- try:
- body = json.loads(request.content.read())
- except:
- raise SynapseError(400, "Invalid key JSON")
+
+ body = parse_json_object_from_request(request)
+
time_now = self.clock.time_msec()
# TODO: Validate the JSON to make sure it has the right keys.
@@ -173,10 +171,7 @@ class KeyQueryServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, user_id, device_id):
yield self.auth.get_user_by_req(request)
- try:
- body = json.loads(request.content.read())
- except:
- raise SynapseError(400, "Invalid key JSON")
+ body = parse_json_object_from_request(request)
result = yield self.handle_request(body)
defer.returnValue(result)
@@ -272,10 +267,7 @@ class OneTimeKeyServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, user_id, device_id, algorithm):
yield self.auth.get_user_by_req(request)
- try:
- body = json.loads(request.content.read())
- except:
- raise SynapseError(400, "Invalid key JSON")
+ body = parse_json_object_from_request(request)
result = yield self.handle_request(body)
defer.returnValue(result)
diff --git a/synapse/rest/client/v2_alpha/receipts.py b/synapse/rest/client/v2_alpha/receipts.py
index eb4b369a3d..b831d8c95e 100644
--- a/synapse/rest/client/v2_alpha/receipts.py
+++ b/synapse/rest/client/v2_alpha/receipts.py
@@ -37,6 +37,7 @@ class ReceiptRestServlet(RestServlet):
self.hs = hs
self.auth = hs.get_auth()
self.receipts_handler = hs.get_handlers().receipts_handler
+ self.presence_handler = hs.get_handlers().presence_handler
@defer.inlineCallbacks
def on_POST(self, request, room_id, receipt_type, event_id):
@@ -45,6 +46,8 @@ class ReceiptRestServlet(RestServlet):
if receipt_type != "m.read":
raise SynapseError(400, "Receipt type must be 'm.read'")
+ yield self.presence_handler.bump_presence_active_time(requester.user)
+
yield self.receipts_handler.received_client_receipt(
room_id,
receipt_type,
diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py
index ec5c21fa1f..d32c06c882 100644
--- a/synapse/rest/client/v2_alpha/register.py
+++ b/synapse/rest/client/v2_alpha/register.py
@@ -17,9 +17,9 @@ from twisted.internet import defer
from synapse.api.constants import LoginType
from synapse.api.errors import SynapseError, Codes, UnrecognizedRequestError
-from synapse.http.servlet import RestServlet
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
-from ._base import client_v2_patterns, parse_json_dict_from_request
+from ._base import client_v2_patterns
import logging
import hmac
@@ -73,7 +73,7 @@ class RegisterRestServlet(RestServlet):
ret = yield self.onEmailTokenRequest(request)
defer.returnValue(ret)
- body = parse_json_dict_from_request(request)
+ body = parse_json_object_from_request(request)
# we do basic sanity checks here because the auth layer will store these
# in sessions. Pull out the username/password provided to us.
@@ -122,10 +122,22 @@ class RegisterRestServlet(RestServlet):
guest_access_token = body.get("guest_access_token", None)
+ session_id = self.auth_handler.get_session_id(body)
+ registered_user_id = None
+ if session_id:
+ # if we get a registered user id out of here, it means we previously
+ # registered a user for this session, so we could just return the
+ # user here. We carry on and go through the auth checks though,
+ # for paranoia.
+ registered_user_id = self.auth_handler.get_session_data(
+ session_id, "registered_user_id", None
+ )
+
if desired_username is not None:
yield self.registration_handler.check_username(
desired_username,
- guest_access_token=guest_access_token
+ guest_access_token=guest_access_token,
+ assigned_user_id=registered_user_id,
)
if self.hs.config.enable_registration_captcha:
@@ -139,7 +151,7 @@ class RegisterRestServlet(RestServlet):
[LoginType.EMAIL_IDENTITY]
]
- authed, result, params = yield self.auth_handler.check_auth(
+ authed, result, params, session_id = yield self.auth_handler.check_auth(
flows, body, self.hs.get_ip_from_request(request)
)
@@ -147,6 +159,22 @@ class RegisterRestServlet(RestServlet):
defer.returnValue((401, result))
return
+ if registered_user_id is not None:
+ logger.info(
+ "Already registered user ID %r for this session",
+ registered_user_id
+ )
+ access_token = yield self.auth_handler.issue_access_token(registered_user_id)
+ refresh_token = yield self.auth_handler.issue_refresh_token(
+ registered_user_id
+ )
+ defer.returnValue((200, {
+ "user_id": registered_user_id,
+ "access_token": access_token,
+ "home_server": self.hs.hostname,
+ "refresh_token": refresh_token,
+ }))
+
# NB: This may be from the auth handler and NOT from the POST
if 'password' not in params:
raise SynapseError(400, "Missing password.", Codes.MISSING_PARAM)
@@ -161,6 +189,12 @@ class RegisterRestServlet(RestServlet):
guest_access_token=guest_access_token,
)
+ # remember that we've now registered that user account, and with what
+ # user ID (since the user may not have specified)
+ self.auth_handler.set_session_data(
+ session_id, "registered_user_id", user_id
+ )
+
if result and LoginType.EMAIL_IDENTITY in result:
threepid = result[LoginType.EMAIL_IDENTITY]
@@ -187,7 +221,7 @@ class RegisterRestServlet(RestServlet):
else:
logger.info("bind_email not specified: not binding email")
- result = self._create_registration_details(user_id, token)
+ result = yield self._create_registration_details(user_id, token)
defer.returnValue((200, result))
def on_OPTIONS(self, _):
@@ -198,7 +232,7 @@ class RegisterRestServlet(RestServlet):
(user_id, token) = yield self.registration_handler.appservice_register(
username, as_token
)
- defer.returnValue(self._create_registration_details(user_id, token))
+ defer.returnValue((yield self._create_registration_details(user_id, token)))
@defer.inlineCallbacks
def _do_shared_secret_registration(self, username, password, mac):
@@ -225,18 +259,21 @@ class RegisterRestServlet(RestServlet):
(user_id, token) = yield self.registration_handler.register(
localpart=username, password=password
)
- defer.returnValue(self._create_registration_details(user_id, token))
+ defer.returnValue((yield self._create_registration_details(user_id, token)))
+ @defer.inlineCallbacks
def _create_registration_details(self, user_id, token):
- return {
+ refresh_token = yield self.auth_handler.issue_refresh_token(user_id)
+ defer.returnValue({
"user_id": user_id,
"access_token": token,
"home_server": self.hs.hostname,
- }
+ "refresh_token": refresh_token,
+ })
@defer.inlineCallbacks
def onEmailTokenRequest(self, request):
- body = parse_json_dict_from_request(request)
+ body = parse_json_object_from_request(request)
required = ['id_server', 'client_secret', 'email', 'send_attempt']
absent = []
diff --git a/synapse/rest/client/v2_alpha/sync.py b/synapse/rest/client/v2_alpha/sync.py
index accbc6cfac..de4a020ad4 100644
--- a/synapse/rest/client/v2_alpha/sync.py
+++ b/synapse/rest/client/v2_alpha/sync.py
@@ -25,6 +25,7 @@ from synapse.events.utils import (
)
from synapse.api.filtering import FilterCollection, DEFAULT_FILTER_COLLECTION
from synapse.api.errors import SynapseError
+from synapse.api.constants import PresenceState
from ._base import client_v2_patterns
import copy
@@ -82,6 +83,7 @@ class SyncRestServlet(RestServlet):
self.sync_handler = hs.get_handlers().sync_handler
self.clock = hs.get_clock()
self.filtering = hs.get_filtering()
+ self.presence_handler = hs.get_handlers().presence_handler
@defer.inlineCallbacks
def on_GET(self, request):
@@ -139,17 +141,19 @@ class SyncRestServlet(RestServlet):
else:
since_token = None
- if set_presence == "online":
- yield self.event_stream_handler.started_stream(user)
+ affect_presence = set_presence != PresenceState.OFFLINE
- try:
+ if affect_presence:
+ yield self.presence_handler.set_state(user, {"presence": set_presence})
+
+ context = yield self.presence_handler.user_syncing(
+ user.to_string(), affect_presence=affect_presence,
+ )
+ with context:
sync_result = yield self.sync_handler.wait_for_sync_for_user(
sync_config, since_token=since_token, timeout=timeout,
full_state=full_state
)
- finally:
- if set_presence == "online":
- self.event_stream_handler.stopped_stream(user)
time_now = self.clock.time_msec()
diff --git a/synapse/rest/client/v2_alpha/tags.py b/synapse/rest/client/v2_alpha/tags.py
index 79c436a8cf..dac8603b07 100644
--- a/synapse/rest/client/v2_alpha/tags.py
+++ b/synapse/rest/client/v2_alpha/tags.py
@@ -15,15 +15,13 @@
from ._base import client_v2_patterns
-from synapse.http.servlet import RestServlet
-from synapse.api.errors import AuthError, SynapseError
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
+from synapse.api.errors import AuthError
from twisted.internet import defer
import logging
-import simplejson as json
-
logger = logging.getLogger(__name__)
@@ -72,11 +70,7 @@ class TagServlet(RestServlet):
if user_id != requester.user.to_string():
raise AuthError(403, "Cannot add tags for other users.")
- try:
- content_bytes = request.content.read()
- body = json.loads(content_bytes)
- except:
- raise SynapseError(400, "Invalid tag JSON")
+ body = parse_json_object_from_request(request)
max_id = yield self.store.add_tag_to_room(user_id, room_id, tag, body)
diff --git a/synapse/rest/client/v2_alpha/tokenrefresh.py b/synapse/rest/client/v2_alpha/tokenrefresh.py
index 3553f6b040..a158c2209a 100644
--- a/synapse/rest/client/v2_alpha/tokenrefresh.py
+++ b/synapse/rest/client/v2_alpha/tokenrefresh.py
@@ -16,9 +16,9 @@
from twisted.internet import defer
from synapse.api.errors import AuthError, StoreError, SynapseError
-from synapse.http.servlet import RestServlet
+from synapse.http.servlet import RestServlet, parse_json_object_from_request
-from ._base import client_v2_patterns, parse_json_dict_from_request
+from ._base import client_v2_patterns
class TokenRefreshRestServlet(RestServlet):
@@ -35,7 +35,7 @@ class TokenRefreshRestServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request):
- body = parse_json_dict_from_request(request)
+ body = parse_json_object_from_request(request)
try:
old_refresh_token = body["refresh_token"]
auth_handler = self.hs.get_handlers().auth_handler
diff --git a/synapse/rest/key/v2/remote_key_resource.py b/synapse/rest/key/v2/remote_key_resource.py
index 81ef1f4702..9552016fec 100644
--- a/synapse/rest/key/v2/remote_key_resource.py
+++ b/synapse/rest/key/v2/remote_key_resource.py
@@ -13,7 +13,7 @@
# limitations under the License.
from synapse.http.server import request_handler, respond_with_json_bytes
-from synapse.http.servlet import parse_integer
+from synapse.http.servlet import parse_integer, parse_json_object_from_request
from synapse.api.errors import SynapseError, Codes
from twisted.web.resource import Resource
@@ -22,7 +22,6 @@ from twisted.internet import defer
from io import BytesIO
-import json
import logging
logger = logging.getLogger(__name__)
@@ -126,14 +125,7 @@ class RemoteKey(Resource):
@request_handler
@defer.inlineCallbacks
def async_render_POST(self, request):
- try:
- content = json.loads(request.content.read())
- if type(content) != dict:
- raise ValueError()
- except ValueError:
- raise SynapseError(
- 400, "Content must be JSON object.", errcode=Codes.NOT_JSON
- )
+ content = parse_json_object_from_request(request)
query = content["server_keys"]
diff --git a/synapse/rest/media/v0/content_repository.py b/synapse/rest/media/v0/content_repository.py
index dcf3eaee1f..d9fc045fc6 100644
--- a/synapse/rest/media/v0/content_repository.py
+++ b/synapse/rest/media/v0/content_repository.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from synapse.http.server import respond_with_json_bytes
+from synapse.http.server import respond_with_json_bytes, finish_request
from synapse.util.stringutils import random_string
from synapse.api.errors import (
@@ -144,7 +144,7 @@ class ContentRepoResource(resource.Resource):
# after the file has been sent, clean up and finish the request
def cbFinished(ignored):
f.close()
- request.finish()
+ finish_request(request)
d.addCallback(cbFinished)
else:
respond_with_json_bytes(
diff --git a/synapse/rest/media/v1/base_resource.py b/synapse/rest/media/v1/base_resource.py
index 58d56ec7a4..58ef91c0b8 100644
--- a/synapse/rest/media/v1/base_resource.py
+++ b/synapse/rest/media/v1/base_resource.py
@@ -16,7 +16,7 @@
from .thumbnailer import Thumbnailer
from synapse.http.matrixfederationclient import MatrixFederationHttpClient
-from synapse.http.server import respond_with_json
+from synapse.http.server import respond_with_json, finish_request
from synapse.util.stringutils import random_string
from synapse.api.errors import (
cs_error, Codes, SynapseError
@@ -238,7 +238,7 @@ class BaseMediaResource(Resource):
with open(file_path, "rb") as f:
yield FileSender().beginFileTransfer(f, request)
- request.finish()
+ finish_request(request)
else:
self._respond_404(request)
|