From c7023f21555a0adf0d8bb5040c817a8198bbf5a8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 24 Mar 2015 17:24:15 +0000 Subject: 1) Pushers are now associated with an access token 2) Change places where we mean unauthenticated to 401, not 403, in C/S v2: hack so it stays as 403 in v1 because web client relies on it. --- synapse/api/auth.py | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 64f605b962..d08faf23f1 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -40,6 +40,7 @@ class Auth(object): self.hs = hs self.store = hs.get_datastore() self.state = hs.get_state_handler() + self.TOKEN_NOT_FOUND_HTTP_STATUS = 401 def check(self, event, auth_events): """ Checks if this event is correctly authed. @@ -373,7 +374,9 @@ class Auth(object): defer.returnValue((user, ClientInfo(device_id, token_id))) except KeyError: - raise AuthError(403, "Missing access token.") + raise AuthError( + self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token." + ) @defer.inlineCallbacks def get_user_by_token(self, token): @@ -387,21 +390,20 @@ class Auth(object): Raises: AuthError if no user by that token exists or the token is invalid. """ - try: - ret = yield self.store.get_user_by_token(token) - if not ret: - raise StoreError(400, "Unknown token") - user_info = { - "admin": bool(ret.get("admin", False)), - "device_id": ret.get("device_id"), - "user": UserID.from_string(ret.get("name")), - "token_id": ret.get("token_id", None), - } + ret = yield self.store.get_user_by_token(token) + if not ret: + raise AuthError( + self.TOKEN_NOT_FOUND_HTTP_STATUS, "Unrecognised access token.", + errcode=Codes.UNKNOWN_TOKEN + ) + user_info = { + "admin": bool(ret.get("admin", False)), + "device_id": ret.get("device_id"), + "user": UserID.from_string(ret.get("name")), + "token_id": ret.get("token_id", None), + } - defer.returnValue(user_info) - except StoreError: - raise AuthError(403, "Unrecognised access token.", - errcode=Codes.UNKNOWN_TOKEN) + defer.returnValue(user_info) @defer.inlineCallbacks def get_appservice_by_req(self, request): @@ -409,11 +411,16 @@ class Auth(object): token = request.args["access_token"][0] service = yield self.store.get_app_service_by_token(token) if not service: - raise AuthError(403, "Unrecognised access token.", - errcode=Codes.UNKNOWN_TOKEN) + raise AuthError( + self.TOKEN_NOT_FOUND_HTTP_STATUS, + "Unrecognised access token.", + errcode=Codes.UNKNOWN_TOKEN + ) defer.returnValue(service) except KeyError: - raise AuthError(403, "Missing access token.") + raise AuthError( + self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token." + ) def is_server_admin(self, user): return self.store.is_server_admin(user) -- cgit 1.5.1 From 9aa0224cdf6ae9243903090d0a264e684f557da2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 24 Mar 2015 17:25:59 +0000 Subject: unused import --- synapse/api/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index d08faf23f1..0bf35109cd 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -18,7 +18,7 @@ from twisted.internet import defer from synapse.api.constants import EventTypes, Membership, JoinRules -from synapse.api.errors import AuthError, StoreError, Codes, SynapseError +from synapse.api.errors import AuthError, Codes, SynapseError from synapse.util.logutils import log_function from synapse.util.async import run_on_reactor from synapse.types import UserID, ClientInfo -- cgit 1.5.1 From 59bf16eddcb793705ee6bc243a2158824f7e05c8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 30 Mar 2015 18:13:10 +0100 Subject: New registration for C/S API v2. Only ReCAPTCHA working currently. --- synapse/api/constants.py | 2 + synapse/config/captcha.py | 7 ++- synapse/handlers/auth.py | 90 +++++++++++++++++++++++++++----- synapse/handlers/register.py | 11 +++- synapse/http/client.py | 2 + synapse/rest/client/v2_alpha/__init__.py | 4 +- synapse/rest/client/v2_alpha/_base.py | 6 +++ synapse/rest/client/v2_alpha/register.py | 86 ++++++++++++++++++++++++++++++ 8 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 synapse/rest/client/v2_alpha/register.py (limited to 'synapse/api') diff --git a/synapse/api/constants.py b/synapse/api/constants.py index b16bf4247d..3e0ce170a4 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -62,6 +62,8 @@ class LoginType(object): APPLICATION_SERVICE = u"m.login.application_service" SHARED_SECRET = u"org.matrix.login.shared_secret" + HIDDEN_TYPES = [APPLICATION_SERVICE, SHARED_SECRET] + class EventTypes(object): Member = "m.room.member" diff --git a/synapse/config/captcha.py b/synapse/config/captcha.py index 7e21c7414d..07fbfadc0f 100644 --- a/synapse/config/captcha.py +++ b/synapse/config/captcha.py @@ -20,6 +20,7 @@ class CaptchaConfig(Config): def __init__(self, args): super(CaptchaConfig, self).__init__(args) self.recaptcha_private_key = args.recaptcha_private_key + self.recaptcha_public_key = args.recaptcha_public_key self.enable_registration_captcha = args.enable_registration_captcha self.captcha_ip_origin_is_x_forwarded = ( args.captcha_ip_origin_is_x_forwarded @@ -30,9 +31,13 @@ class CaptchaConfig(Config): def add_arguments(cls, parser): super(CaptchaConfig, cls).add_arguments(parser) group = parser.add_argument_group("recaptcha") + group.add_argument( + "--recaptcha-public-key", type=str, default="YOUR_PUBLIC_KEY", + help="This Home Server's ReCAPTCHA public key." + ) group.add_argument( "--recaptcha-private-key", type=str, default="YOUR_PRIVATE_KEY", - help="The matching private key for the web client's public key." + help="This Home Server's ReCAPTCHA private key." ) group.add_argument( "--enable-registration-captcha", type=bool, default=False, diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index e4a73da9a7..ec625f4ea8 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -19,9 +19,12 @@ from ._base import BaseHandler from synapse.api.constants import LoginType from synapse.types import UserID from synapse.api.errors import LoginError, Codes +from synapse.http.client import SimpleHttpClient +from twisted.web.client import PartialDownloadError import logging import bcrypt +import simplejson logger = logging.getLogger(__name__) @@ -33,7 +36,7 @@ class AuthHandler(BaseHandler): super(AuthHandler, self).__init__(hs) @defer.inlineCallbacks - def check_auth(self, flows, clientdict): + def check_auth(self, flows, clientdict, clientip=None): """ Takes a dictionary sent by the client in the login / registration protocol and handles the login flow. @@ -50,11 +53,12 @@ class AuthHandler(BaseHandler): login request and should be passed back to the client. """ types = { - LoginType.PASSWORD: self.check_password_auth + LoginType.PASSWORD: self.check_password_auth, + LoginType.RECAPTCHA: self.check_recaptcha, } - if 'auth' not in clientdict: - defer.returnValue((False, auth_dict_for_flows(flows))) + if not clientdict or 'auth' not in clientdict: + defer.returnValue((False, self.auth_dict_for_flows(flows))) authdict = clientdict['auth'] @@ -67,7 +71,7 @@ class AuthHandler(BaseHandler): raise LoginError(400, "", Codes.MISSING_PARAM) if authdict['type'] not in types: raise LoginError(400, "", Codes.UNRECOGNIZED) - result = yield types[authdict['type']](authdict) + result = yield types[authdict['type']](authdict, clientip) if result: creds[authdict['type']] = result @@ -76,12 +80,12 @@ class AuthHandler(BaseHandler): logger.info("Auth completed with creds: %r", creds) defer.returnValue((True, creds)) - ret = auth_dict_for_flows(flows) + ret = self.auth_dict_for_flows(flows) ret['completed'] = creds.keys() defer.returnValue((False, ret)) @defer.inlineCallbacks - def check_password_auth(self, authdict): + def check_password_auth(self, authdict, _): if "user" not in authdict or "password" not in authdict: raise LoginError(400, "", Codes.MISSING_PARAM) @@ -93,17 +97,77 @@ class AuthHandler(BaseHandler): user_info = yield self.store.get_user_by_id(user_id=user) if not user_info: logger.warn("Attempted to login as %s but they do not exist", user) - raise LoginError(403, "", errcode=Codes.FORBIDDEN) + raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) stored_hash = user_info[0]["password_hash"] if bcrypt.checkpw(password, stored_hash): defer.returnValue(user) else: logger.warn("Failed password login for user %s", user) - raise LoginError(403, "", errcode=Codes.FORBIDDEN) + raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) + @defer.inlineCallbacks + def check_recaptcha(self, authdict, clientip): + try: + user_response = authdict["response"] + except KeyError: + # Client tried to provide captcha but didn't give the parameter: + # bad request. + raise LoginError( + 400, "Captcha response is required", + errcode=Codes.CAPTCHA_NEEDED + ) + + logger.info( + "Submitting recaptcha response %s with remoteip %s", + user_response, clientip + ) + + # TODO: get this from the homeserver rather than creating a new one for + # each request + try: + client = SimpleHttpClient(self.hs) + data = yield client.post_urlencoded_get_json( + "https://www.google.com/recaptcha/api/siteverify", + args={ + 'secret': self.hs.config.recaptcha_private_key, + 'response': user_response, + 'remoteip': clientip, + } + ) + except PartialDownloadError as pde: + # Twisted is silly + data = pde.response + resp_body = simplejson.loads(data) + if 'success' in resp_body and resp_body['success']: + defer.returnValue(True) + raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) + + def get_params_recaptcha(self): + return {"public_key": self.hs.config.recaptcha_public_key} + + def auth_dict_for_flows(self, flows): + public_flows = [] + for f in flows: + hidden = False + for stagetype in f: + if stagetype in LoginType.HIDDEN_TYPES: + hidden = True + if not hidden: + public_flows.append(f) + + get_params = { + LoginType.RECAPTCHA: self.get_params_recaptcha, + } + + params = {} + + for f in public_flows: + for stage in f: + if stage in get_params and stage not in params: + params[stage] = get_params[stage]() -def auth_dict_for_flows(flows): - return { - "flows": {"stages": f for f in flows} - } + return { + "flows": [{"stages": f} for f in public_flows], + "params": params + } \ No newline at end of file diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index c25e321099..542759a827 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -157,7 +157,11 @@ class RegistrationHandler(BaseHandler): @defer.inlineCallbacks def check_recaptcha(self, ip, private_key, challenge, response): - """Checks a recaptcha is correct.""" + """ + Checks a recaptcha is correct. + + Used only by c/s api v1 + """ captcha_response = yield self._validate_captcha( ip, @@ -282,6 +286,8 @@ class RegistrationHandler(BaseHandler): def _validate_captcha(self, ip_addr, private_key, challenge, response): """Validates the captcha provided. + Used only by c/s api v1 + Returns: dict: Containing 'valid'(bool) and 'error_url'(str) if invalid. @@ -299,6 +305,9 @@ class RegistrationHandler(BaseHandler): @defer.inlineCallbacks def _submit_captcha(self, ip_addr, private_key, challenge, response): + """ + Used only by c/s api v1 + """ # TODO: get this from the homeserver rather than creating a new one for # each request client = CaptchaServerHttpClient(self.hs) diff --git a/synapse/http/client.py b/synapse/http/client.py index 2ae1c4d3a4..e8a5dedab4 100644 --- a/synapse/http/client.py +++ b/synapse/http/client.py @@ -200,6 +200,8 @@ class CaptchaServerHttpClient(SimpleHttpClient): """ Separate HTTP client for talking to google's captcha servers Only slightly special because accepts partial download responses + + used only by c/s api v1 """ @defer.inlineCallbacks diff --git a/synapse/rest/client/v2_alpha/__init__.py b/synapse/rest/client/v2_alpha/__init__.py index 041f538e20..98189ead26 100644 --- a/synapse/rest/client/v2_alpha/__init__.py +++ b/synapse/rest/client/v2_alpha/__init__.py @@ -16,7 +16,8 @@ from . import ( sync, filter, - password + password, + register ) from synapse.http.server import JsonResource @@ -34,3 +35,4 @@ class ClientV2AlphaRestResource(JsonResource): sync.register_servlets(hs, client_resource) filter.register_servlets(hs, client_resource) password.register_servlets(hs, client_resource) + register.register_servlets(hs, client_resource) diff --git a/synapse/rest/client/v2_alpha/_base.py b/synapse/rest/client/v2_alpha/_base.py index c772cc986f..db2c9b244a 100644 --- a/synapse/rest/client/v2_alpha/_base.py +++ b/synapse/rest/client/v2_alpha/_base.py @@ -40,6 +40,12 @@ def client_v2_pattern(path_regex): return re.compile("^" + CLIENT_V2_ALPHA_PREFIX + path_regex) +def parse_request_allow_empty(request): + content = request.content.read() + if content == None or content == '': + return None + return simplejson.loads(content) + def parse_json_dict_from_request(request): try: content = simplejson.loads(request.content.read()) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py new file mode 100644 index 0000000000..84da010c29 --- /dev/null +++ b/synapse/rest/client/v2_alpha/register.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 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.constants import LoginType +from synapse.api.errors import LoginError, SynapseError, Codes +from synapse.http.servlet import RestServlet + +from ._base import client_v2_pattern, parse_request_allow_empty + +import logging + + +logger = logging.getLogger(__name__) + + +class RegisterRestServlet(RestServlet): + PATTERN = client_v2_pattern("/register") + + def __init__(self, hs): + super(RegisterRestServlet, self).__init__() + self.hs = hs + self.auth = hs.get_auth() + self.auth_handler = hs.get_handlers().auth_handler + self.registration_handler = hs.get_handlers().registration_handler + + @defer.inlineCallbacks + def on_POST(self, request): + body = parse_request_allow_empty(request) + + authed, result = yield self.auth_handler.check_auth([ + [LoginType.RECAPTCHA], + [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], + [LoginType.APPLICATION_SERVICE] + ], body) + + if not authed: + defer.returnValue((401, result)) + + is_application_server = LoginType.APPLICATION_SERVICE in result + is_using_shared_secret = LoginType.SHARED_SECRET in result + + can_register = ( + not self.hs.config.disable_registration + or is_application_server + or is_using_shared_secret + ) + if not can_register: + raise SynapseError(403, "Registration has been disabled") + + if 'username' not in body or 'password' not in body: + raise SynapseError(400, "", Codes.MISSING_PARAM) + desired_username = body['username'] + new_password = body['password'] + + (user_id, token) = yield self.registration_handler.register( + localpart=desired_username, + password=new_password + ) + result = { + "user_id": user_id, + "access_token": token, + "home_server": self.hs.hostname, + } + + defer.returnValue((200, result)) + + def on_OPTIONS(self, _): + return 200, {} + + +def register_servlets(hs, http_server): + RegisterRestServlet(hs).register(http_server) \ No newline at end of file -- cgit 1.5.1 From 70a84f17f39bbc5c8a68541874ca4767871f2b79 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 2 Apr 2015 17:01:29 +0100 Subject: Add shared secret auth into register v2 and switch the script over. --- register_new_matrix_user | 5 +-- synapse/api/constants.py | 4 +- synapse/rest/client/v2_alpha/register.py | 69 +++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 15 deletions(-) (limited to 'synapse/api') diff --git a/register_new_matrix_user b/register_new_matrix_user index daddadc302..f833d2a4db 100755 --- a/register_new_matrix_user +++ b/register_new_matrix_user @@ -33,10 +33,9 @@ def request_registration(user, password, server_location, shared_secret): ).hexdigest() data = { - "user": user, + "username": user, "password": password, "mac": mac, - "type": "org.matrix.login.shared_secret", } server_location = server_location.rstrip("/") @@ -44,7 +43,7 @@ def request_registration(user, password, server_location, shared_secret): print "Sending registration request..." req = urllib2.Request( - "%s/_matrix/client/api/v1/register" % (server_location,), + "%s/_matrix/client/v2_alpha/register" % (server_location,), data=json.dumps(data), headers={'Content-Type': 'application/json'} ) diff --git a/synapse/api/constants.py b/synapse/api/constants.py index 3e0ce170a4..f825c1a58b 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -60,9 +60,11 @@ class LoginType(object): EMAIL_IDENTITY = u"m.login.email.identity" RECAPTCHA = u"m.login.recaptcha" APPLICATION_SERVICE = u"m.login.application_service" + + # Only for C/S API v1 SHARED_SECRET = u"org.matrix.login.shared_secret" - HIDDEN_TYPES = [APPLICATION_SERVICE, SHARED_SECRET] + HIDDEN_TYPES = [APPLICATION_SERVICE] class EventTypes(object): diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 537918ea27..a69b45f362 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -22,6 +22,19 @@ from synapse.http.servlet import RestServlet from ._base import client_v2_pattern, parse_request_allow_empty import logging +import hmac +from hashlib import sha1 +from synapse.util.async import run_on_reactor + + +# We ought to be using hmac.compare_digest() but on older pythons it doesn't +# exist. It's a _really minor_ security flaw to use plain string comparison +# because the timing attack is so obscured by all the other code here it's +# unlikely to make much difference +if hasattr(hmac, "compare_digest"): + compare_digest = hmac.compare_digest +else: + compare_digest = lambda a, b: a == b logger = logging.getLogger(__name__) @@ -39,19 +52,30 @@ class RegisterRestServlet(RestServlet): @defer.inlineCallbacks def on_POST(self, request): - body = parse_request_allow_empty(request) + yield run_on_reactor() - authed, result = yield self.auth_handler.check_auth([ - [LoginType.RECAPTCHA], - [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], - [LoginType.APPLICATION_SERVICE] - ], body, self.hs.get_ip_from_request(request)) - - if not authed: - defer.returnValue((401, result)) + body = parse_request_allow_empty(request) - is_application_server = LoginType.APPLICATION_SERVICE in result - is_using_shared_secret = LoginType.SHARED_SECRET in result + is_using_shared_secret = False + is_application_server = False + + if 'mac' in body: + # Check registration-specific shared secret auth + if 'username' not in body: + raise SynapseError(400, "", Codes.MISSING_PARAM) + self._check_shared_secret_auth( + body['username'], body['mac'] + ) + is_using_shared_secret = True + else: + authed, result = yield self.auth_handler.check_auth([ + [LoginType.RECAPTCHA], + [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], + [LoginType.APPLICATION_SERVICE] + ], body, self.hs.get_ip_from_request(request)) + + if not authed: + defer.returnValue((401, result)) can_register = ( not self.hs.config.disable_registration @@ -81,6 +105,29 @@ class RegisterRestServlet(RestServlet): def on_OPTIONS(self, _): return 200, {} + def _check_shared_secret_auth(self, username, mac): + if not self.hs.config.registration_shared_secret: + raise SynapseError(400, "Shared secret registration is not enabled") + + user = username.encode("utf-8") + + # str() because otherwise hmac complains that 'unicode' does not + # have the buffer interface + got_mac = str(mac) + + want_mac = hmac.new( + key=self.hs.config.registration_shared_secret, + msg=user, + digestmod=sha1, + ).hexdigest() + + if compare_digest(want_mac, got_mac): + return True + else: + raise SynapseError( + 403, "HMAC incorrect", + ) + def register_servlets(hs, http_server): RegisterRestServlet(hs).register(http_server) -- cgit 1.5.1 From 4eb6d66b45356efcc87089cb52ca6f51c98cd798 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 2 Apr 2015 17:51:19 +0100 Subject: Add app service auth back in to v2 register --- synapse/api/constants.py | 4 +--- synapse/rest/client/v2_alpha/register.py | 9 +++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/constants.py b/synapse/api/constants.py index f825c1a58b..d29c2dde01 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -59,13 +59,11 @@ class LoginType(object): EMAIL_URL = u"m.login.email.url" EMAIL_IDENTITY = u"m.login.email.identity" RECAPTCHA = u"m.login.recaptcha" - APPLICATION_SERVICE = u"m.login.application_service" # Only for C/S API v1 + APPLICATION_SERVICE = u"m.login.application_service" SHARED_SECRET = u"org.matrix.login.shared_secret" - HIDDEN_TYPES = [APPLICATION_SERVICE] - class EventTypes(object): Member = "m.room.member" diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index a69b45f362..72319a3bb2 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -59,7 +59,13 @@ class RegisterRestServlet(RestServlet): is_using_shared_secret = False is_application_server = False - if 'mac' in body: + service = None + if 'access_token' in request.args: + service = yield self.auth.get_appservice_by_req(request) + + if service: + is_application_server = True + elif 'mac' in body: # Check registration-specific shared secret auth if 'username' not in body: raise SynapseError(400, "", Codes.MISSING_PARAM) @@ -71,7 +77,6 @@ class RegisterRestServlet(RestServlet): authed, result = yield self.auth_handler.check_auth([ [LoginType.RECAPTCHA], [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], - [LoginType.APPLICATION_SERVICE] ], body, self.hs.get_ip_from_request(request)) if not authed: -- cgit 1.5.1 From 766bd8e88077cbeabffc353d9735a3af190abe61 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Apr 2015 17:14:25 +0100 Subject: Dummy login so we can do the first POST request to get login flows without it just succeeding --- synapse/api/constants.py | 1 + synapse/handlers/auth.py | 6 ++++++ synapse/handlers/identity.py | 6 +++--- synapse/rest/client/v2_alpha/register.py | 18 ++++++++++++++---- 4 files changed, 24 insertions(+), 7 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/constants.py b/synapse/api/constants.py index d29c2dde01..d8a18ee87b 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -59,6 +59,7 @@ class LoginType(object): EMAIL_URL = u"m.login.email.url" EMAIL_IDENTITY = u"m.login.email.identity" RECAPTCHA = u"m.login.recaptcha" + DUMMY = u"m.login.dummy" # Only for C/S API v1 APPLICATION_SERVICE = u"m.login.application_service" diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 2cc54707a2..87866f298d 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -42,6 +42,7 @@ class AuthHandler(BaseHandler): LoginType.PASSWORD: self._check_password_auth, LoginType.RECAPTCHA: self._check_recaptcha, LoginType.EMAIL_IDENTITY: self._check_email_identity, + LoginType.DUMMY: self._check_dummy_auth, } self.sessions = {} @@ -202,6 +203,11 @@ class AuthHandler(BaseHandler): defer.returnValue(threepid) + @defer.inlineCallbacks + def _check_dummy_auth(self, authdict, _): + yield run_on_reactor() + defer.returnValue(True) + def _get_params_recaptcha(self): return {"public_key": self.hs.config.recaptcha_public_key} diff --git a/synapse/handlers/identity.py b/synapse/handlers/identity.py index 671d366e40..19896ce90d 100644 --- a/synapse/handlers/identity.py +++ b/synapse/handlers/identity.py @@ -42,8 +42,8 @@ class IdentityHandler(BaseHandler): # each request http_client = SimpleHttpClient(self.hs) # XXX: make this configurable! - #trustedIdServers = ['matrix.org', 'localhost:8090'] - trustedIdServers = ['matrix.org'] + trustedIdServers = ['matrix.org', 'localhost:8090'] + #trustedIdServers = ['matrix.org'] if not creds['idServer'] in trustedIdServers: logger.warn('%s is not a trusted ID server: rejecting 3pid ' + 'credentials', creds['idServer']) @@ -52,7 +52,7 @@ class IdentityHandler(BaseHandler): data = {} try: data = yield http_client.get_json( - "https://%s%s" % ( + "http://%s%s" % ( creds['idServer'], "/_matrix/identity/api/v1/3pid/getValidated3pid" ), diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index d7a20fc964..ee99b74fd6 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -63,6 +63,17 @@ class RegisterRestServlet(RestServlet): if 'access_token' in request.args: service = yield self.auth.get_appservice_by_req(request) + if self.hs.config.enable_registration_captcha: + flows = [ + [LoginType.RECAPTCHA], + [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA] + ] + else: + flows = [ + [LoginType.DUMMY], + [LoginType.EMAIL_IDENTITY] + ] + if service: is_application_server = True elif 'mac' in body: @@ -74,10 +85,9 @@ class RegisterRestServlet(RestServlet): ) is_using_shared_secret = True else: - authed, result, params = yield self.auth_handler.check_auth([ - [LoginType.RECAPTCHA], - [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], - ], body, self.hs.get_ip_from_request(request)) + authed, result, params = yield self.auth_handler.check_auth( + flows, body, self.hs.get_ip_from_request(request) + ) if not authed: defer.returnValue((401, result)) -- cgit 1.5.1 From bc41f0398f081177ebc8036280f935d8bcb7bca0 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 21 Apr 2015 20:56:08 +0100 Subject: Initial implementation of an 'invite' power_level --- synapse/api/auth.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 9a5058a364..84a46385bb 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -232,6 +232,13 @@ class Auth(object): elif target_in_room: # the target is already in the room. raise AuthError(403, "%s is already in the room." % target_user_id) + else: + invite_level = self._get_named_level(auth_events, "invite", 0) + + if user_level < invite_level: + raise AuthError( + 403, "You cannot invite user %s." % target_user_id + ) elif Membership.JOIN == membership: # Joins are valid iff caller == target and they were: # invited: They are accepting the invitation -- cgit 1.5.1 From 2808c040ef4070e6a91e2b210383393cc54f66b5 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 21 Apr 2015 21:13:14 +0100 Subject: Also remember to check 'invite' level for changes --- synapse/api/auth.py | 1 + 1 file changed, 1 insertion(+) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 84a46385bb..d8b7614791 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -592,6 +592,7 @@ class Auth(object): ("ban", []), ("redact", []), ("kick", []), + ("invite", []), ] old_list = current_state.content.get("users") -- cgit 1.5.1 From a2c10d37d7052a1ab6cf7188c3b4d763850e1561 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Apr 2015 13:23:44 +0100 Subject: Add an error code to 'missing token' response. --- synapse/api/auth.py | 3 ++- synapse/api/errors.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 3d2b45d217..11f76c06f7 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -372,7 +372,8 @@ class Auth(object): defer.returnValue((user, ClientInfo(device_id, token_id))) except KeyError: raise AuthError( - self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token." + self.TOKEN_NOT_FOUND_HTTP_STATUS, "Missing access token.", + errcode=Codes.MISSING_TOKEN ) @defer.inlineCallbacks diff --git a/synapse/api/errors.py b/synapse/api/errors.py index eddd889778..109547b3ce 100644 --- a/synapse/api/errors.py +++ b/synapse/api/errors.py @@ -31,6 +31,7 @@ class Codes(object): BAD_PAGINATION = "M_BAD_PAGINATION" UNKNOWN = "M_UNKNOWN" NOT_FOUND = "M_NOT_FOUND" + MISSING_TOKEN = "M_MISSING_TOKEN" UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN" LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED" CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED" -- cgit 1.5.1 From 03eb4adc6ead31b69af6a87b8d05ae7e0e965fd0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Apr 2015 18:20:17 +0100 Subject: Dedicated error code for failed 3pid auth verification --- synapse/api/errors.py | 1 + synapse/rest/client/v2_alpha/account.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'synapse/api') diff --git a/synapse/api/errors.py b/synapse/api/errors.py index 109547b3ce..e8b9ee533d 100644 --- a/synapse/api/errors.py +++ b/synapse/api/errors.py @@ -39,6 +39,7 @@ class Codes(object): MISSING_PARAM = "M_MISSING_PARAM", TOO_LARGE = "M_TOO_LARGE", EXCLUSIVE = "M_EXCLUSIVE" + THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED" class CodeMessageException(RuntimeError): diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index e33607b799..4d199bbbb8 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -126,7 +126,9 @@ class ThreepidRestServlet(RestServlet): threepid = yield self.identity_handler.threepid_from_creds(threePidCreds) if not threepid: - raise SynapseError(400, "Failed to auth 3pid") + raise SynapseError( + 400, "Failed to auth 3pid", Codes.THREEPID_AUTH_FAILED + ) for reqd in ['medium', 'address', 'validatedAt']: if reqd not in threepid: -- cgit 1.5.1