def _check_local_password(self, user_id, password): """Authenticate a user against the local password database. user_id is checked case insensitively, but will throw if there are multiple inexact matches. Args: user_id (str): complete @user:id Returns: (str) the canonical_user_id Raises: LoginError if the password was incorrect """ user_id, password_hash = yield self._find_user_id_and_pwd_hash(user_id) result = self.validate_hash(password, password_hash) if not result: logger.warn("Failed password login for user %s", user_id) raise LoginError(403, "", errcode=Codes.FORBIDDEN) defer.returnValue(user_id)
def on_POST(self, request): yield run_on_reactor() body = parse_json_object_from_request(request) authed, result, params, _ = yield self.auth_handler.check_auth( [[LoginType.PASSWORD], [LoginType.EMAIL_IDENTITY]], body, self.hs.get_ip_from_request(request)) if not authed: defer.returnValue((401, result)) user_id = None if LoginType.PASSWORD in result: # if using password, they should also be logged in requester = yield self.auth.get_user_by_req(request) user_id = requester.user.to_string() if user_id != result[LoginType.PASSWORD]: raise LoginError(400, "", Codes.UNKNOWN) elif LoginType.EMAIL_IDENTITY in result: threepid = result[LoginType.EMAIL_IDENTITY] if 'medium' not in threepid or 'address' not in threepid: raise SynapseError(500, "Malformed threepid") # if using email, we must know about the email they're authing with! threepid_user_id = yield self.hs.get_datastore( ).get_user_id_by_threepid(threepid['medium'], threepid['address']) if not threepid_user_id: raise SynapseError(404, "Email address not found", Codes.NOT_FOUND) user_id = threepid_user_id else: logger.error("Auth succeeded but no known type!", result.keys()) raise SynapseError(500, "", Codes.UNKNOWN) if 'new_password' not in params: raise SynapseError(400, "", Codes.MISSING_PARAM) new_password = params['new_password'] yield self.auth_handler.set_password(user_id, new_password, requester) defer.returnValue((200, {}))
def on_POST(self, request): body = parse_json_object_from_request(request) # if the caller provides an access token, it ought to be valid. requester = None if has_access_token(request): requester = yield self.auth.get_user_by_req( request, ) # type: synapse.types.Requester # allow ASes to dectivate their own users if requester and requester.app_service: yield self.auth_handler.deactivate_account( requester.user.to_string() ) defer.returnValue((200, {})) authed, result, params, _ = yield self.auth_handler.check_auth([ [LoginType.PASSWORD], ], body, self.hs.get_ip_from_request(request)) if not authed: defer.returnValue((401, result)) if LoginType.PASSWORD in result: user_id = result[LoginType.PASSWORD] # if using password, they should also be logged in if requester is None: raise SynapseError( 400, "Deactivate account requires an access_token", errcode=Codes.MISSING_TOKEN ) if requester.user.to_string() != user_id: raise LoginError(400, "", Codes.UNKNOWN) else: logger.error("Auth succeeded but no known type!", result.keys()) raise SynapseError(500, "", Codes.UNKNOWN) yield self.auth_handler.deactivate_account(user_id) defer.returnValue((200, {}))
async def _do_appservice_login( self, login_submission: JsonDict, appservice: ApplicationService, should_issue_refresh_token: bool = False, ) -> LoginResponse: identifier = login_submission.get("identifier") logger.info("Got appservice login request with identifier: %r", identifier) if not isinstance(identifier, dict): raise SynapseError(400, "Invalid identifier in login submission", Codes.INVALID_PARAM) # this login flow only supports identifiers of type "m.id.user". if identifier.get("type") != "m.id.user": raise SynapseError(400, "Unknown login identifier type", Codes.INVALID_PARAM) user = identifier.get("user") if not isinstance(user, str): raise SynapseError(400, "Invalid user in identifier", Codes.INVALID_PARAM) if user.startswith("@"): qualified_user_id = user else: qualified_user_id = UserID(user, self.hs.hostname).to_string() if not appservice.is_interested_in_user(qualified_user_id): raise LoginError(403, "Invalid access_token", errcode=Codes.FORBIDDEN) return await self._complete_login( qualified_user_id, login_submission, ratelimit=appservice.is_rate_limited(), should_issue_refresh_token=should_issue_refresh_token, )
async def on_POST(self, request: SynapseRequest): self._address_ratelimiter.ratelimit(request.getClientIP()) params = parse_json_object_from_request(request) bind_type = params["bind_type"] if bind_type is None: raise LoginError(410, "bind_type field for bind openid is missing", errcode=Codes.FORBIDDEN) requester = await self.auth.get_user_by_req(request) logger.info('------requester: %s' % (requester, )) user_id = requester.user logger.info('------user: %s' % (user_id, )) #complete unbind await self.hs.get_datastore( ).delete_external_id_with_user_and_provider( bind_type, str(user_id), ) return 200, {}
def do_password_login(self, login_submission): if 'medium' in login_submission and 'address' in login_submission: address = login_submission['address'] if login_submission['medium'] == 'email': # For emails, transform the address to lowercase. # We store all email addreses as lowercase in the DB. # (See add_threepid in synapse/handlers/auth.py) address = address.lower() user_id = yield self.hs.get_datastore().get_user_id_by_threepid( login_submission['medium'], address) if not user_id: raise LoginError(403, "", errcode=Codes.FORBIDDEN) else: user_id = login_submission['user'] if not user_id.startswith('@'): user_id = UserID.create(user_id, self.hs.hostname).to_string() auth_handler = self.auth_handler user_id = yield auth_handler.validate_password_login( user_id=user_id, password=login_submission["password"], ) device_id = yield self._register_device(user_id, login_submission) access_token = yield auth_handler.get_access_token_for_user_id( user_id, device_id, login_submission.get("initial_device_display_name"), ) result = { "user_id": user_id, # may have changed "access_token": access_token, "home_server": self.hs.hostname, "device_id": device_id, } defer.returnValue((200, result))
def do_password_login(self, login_submission): if 'medium' in login_submission and 'address' in login_submission: user_id = yield self.hs.get_datastore().get_user_id_by_threepid( login_submission['medium'], login_submission['address']) if not user_id: raise LoginError(403, "", errcode=Codes.FORBIDDEN) else: user_id = login_submission['user'] if not user_id.startswith('@'): user_id = UserID.create(user_id, self.hs.hostname).to_string() auth_handler = self.handlers.auth_handler user_id, access_token, refresh_token = yield auth_handler.login_with_password( user_id=user_id, password=login_submission["password"]) result = { "user_id": user_id, # may have changed "access_token": access_token, "refresh_token": refresh_token, "home_server": self.hs.hostname, } defer.returnValue((200, result))
def do_password_login(self, login_submission): if 'medium' in login_submission and 'address' in login_submission: user_id = yield self.hs.get_datastore().get_user_id_by_threepid( login_submission['medium'], login_submission['address'] ) if not user_id: raise LoginError(403, "", errcode=Codes.FORBIDDEN) else: user_id = login_submission['user'] if not user_id.startswith('@'): user_id = UserID.create( user_id, self.hs.hostname ).to_string() auth_handler = self.auth_handler user_id = yield auth_handler.validate_password_login( user_id=user_id, password=login_submission["password"], ) device_id = yield self._register_device(user_id, login_submission) access_token, refresh_token = ( yield auth_handler.get_login_tuple_for_user_id( user_id, device_id, login_submission.get("initial_device_display_name") ) ) result = { "user_id": user_id, # may have changed "access_token": access_token, "refresh_token": refresh_token, "home_server": self.hs.hostname, "device_id": device_id, } defer.returnValue((200, result))
async def _complete_login( self, user_id: str, login_submission: JsonDict, callback: Optional[Callable[[LoginResponse], Awaitable[None]]] = None, create_non_existent_users: bool = False, ratelimit: bool = True, auth_provider_id: Optional[str] = None, should_issue_refresh_token: bool = False, auth_provider_session_id: Optional[str] = None, ) -> LoginResponse: """Called when we've successfully authed the user and now need to actually login them in (e.g. create devices). This gets called on all successful logins. Applies the ratelimiting for successful login attempts against an account. Args: user_id: ID of the user to register. login_submission: Dictionary of login information. callback: Callback function to run after login. create_non_existent_users: Whether to create the user if they don't exist. Defaults to False. ratelimit: Whether to ratelimit the login request. auth_provider_id: The SSO IdP the user used, if any. should_issue_refresh_token: True if this login should issue a refresh token alongside the access token. auth_provider_session_id: The session ID got during login from the SSO IdP. Returns: result: Dictionary of account information after successful login. """ # Before we actually log them in we check if they've already logged in # too often. This happens here rather than before as we don't # necessarily know the user before now. if ratelimit: await self._account_ratelimiter.ratelimit(None, user_id.lower()) if create_non_existent_users: canonical_uid = await self.auth_handler.check_user_exists(user_id) if not canonical_uid: canonical_uid = await self.registration_handler.register_user( localpart=UserID.from_string(user_id).localpart) user_id = canonical_uid device_id = login_submission.get("device_id") # If device_id is present, check that device_id is not longer than a reasonable 512 characters if device_id and len(device_id) > 512: raise LoginError( 400, "device_id cannot be longer than 512 characters.", errcode=Codes.INVALID_PARAM, ) initial_display_name = login_submission.get( "initial_device_display_name") ( device_id, access_token, valid_until_ms, refresh_token, ) = await self.registration_handler.register_device( user_id, device_id, initial_display_name, auth_provider_id=auth_provider_id, should_issue_refresh_token=should_issue_refresh_token, auth_provider_session_id=auth_provider_session_id, ) result = LoginResponse( user_id=user_id, access_token=access_token, home_server=self.hs.hostname, device_id=device_id, ) if valid_until_ms is not None: expires_in_ms = valid_until_ms - self.clock.time_msec() result["expires_in_ms"] = expires_in_ms if refresh_token is not None: result["refresh_token"] = refresh_token if callback is not None: await callback(result) return result
def _do_other_login(self, login_submission): """Handle non-token/saml/jwt logins Args: login_submission: Returns: (int, object): HTTP code/response """ # Log the request we got, but only certain fields to minimise the chance of # logging someone's password (even if they accidentally put it in the wrong # field) logger.info( "Got login request with identifier: %r, medium: %r, address: %r, user: %r", login_submission.get('identifier'), login_submission.get('medium'), login_submission.get('address'), login_submission.get('user'), ) login_submission_legacy_convert(login_submission) if "identifier" not in login_submission: raise SynapseError(400, "Missing param: identifier") identifier = login_submission["identifier"] if "type" not in identifier: raise SynapseError(400, "Login identifier has no type") # convert phone type identifiers to generic threepids if identifier["type"] == "m.id.phone": identifier = login_id_thirdparty_from_phone(identifier) # convert threepid identifiers to user IDs if identifier["type"] == "m.id.thirdparty": address = identifier.get('address') medium = identifier.get('medium') if medium is None or address is None: raise SynapseError(400, "Invalid thirdparty identifier") if medium == 'email': # For emails, transform the address to lowercase. # We store all email addreses as lowercase in the DB. # (See add_threepid in synapse/handlers/auth.py) address = address.lower() user_id = yield self.hs.get_datastore().get_user_id_by_threepid( medium, address, ) if not user_id: logger.warn( "unknown 3pid identifier medium %s, address %r", medium, address, ) raise LoginError(403, "", errcode=Codes.FORBIDDEN) identifier = { "type": "m.id.user", "user": user_id, } # by this point, the identifier should be an m.id.user: if it's anything # else, we haven't understood it. if identifier["type"] != "m.id.user": raise SynapseError(400, "Unknown login identifier type") if "user" not in identifier: raise SynapseError(400, "User identifier is missing 'user' key") auth_handler = self.auth_handler canonical_user_id, callback = yield auth_handler.validate_login( identifier["user"], login_submission, ) device_id = yield self._register_device( canonical_user_id, login_submission, ) access_token = yield auth_handler.get_access_token_for_user_id( canonical_user_id, device_id, ) result = { "user_id": canonical_user_id, "access_token": access_token, "home_server": self.hs.hostname, "device_id": device_id, } if callback is not None: yield callback(result) defer.returnValue((200, result))
async def async_render_POST(self, request: Request): session_id = request.getCookie(SESSION_COOKIE_NAME) if not session_id: _return_html_error(400, "missing session_id", request) return session_id = session_id.decode("ascii", errors="replace") session = get_mapping_session(session_id) if not session: logger.info("Session ID %s not found", session_id) _return_html_error(403, "Unknown session", request) return # we don't clear the session from the dict until the ID is successfully # registered, so the user can go round and have another go if need be. # # this means there's theoretically a race where a single user can register # two accounts. I'm going to assume that's not a dealbreaker. if b"username" not in request.args: _return_html_error(400, "missing username", request) return localpart = request.args[b"username"][0].decode("utf-8", errors="replace") if b"password" not in request.args: logger.info("Registering username %s", localpart) try: registered_user_id = await self._module_api.register_user( localpart=localpart, displayname=localpart) except SynapseError as e: logger.warning("Error during registration: %s", e) _return_html_error(e.code, e.msg, request) return else: password = request.args[b"password"][0].decode("utf-8", errors="replace") if localpart.startswith("@"): registered_user_id = localpart else: registered_user_id = UserID( localpart, self._module_api._hs.hostname).to_string() success = False try: passwd_response = await self._module_api._auth_handler._check_local_password( registered_user_id, password) if passwd_response != registered_user_id: raise LoginError(403, "Invalid password", errcode=Codes.FORBIDDEN) except Exception as e: logger.warning("Error checking credentials of %s: %s %s" % (localpart, type(e), e)) _return_html_error(e.code, e.msg, request) return await self._module_api.record_user_external_id("saml", session.remote_user_id, registered_user_id) del username_mapping_sessions[session_id] # delete the cookie request.addCookie( SESSION_COOKIE_NAME, b"", expires=b"Thu, 01 Jan 1970 00:00:00 GMT", path=b"/", ) await self._module_api.complete_sso_login_async( registered_user_id, request, session.client_redirect_url, )
def do_jwt_login(self, login_submission): token = login_submission.get("token", None) if token is None: raise LoginError(401, "Token field for JWT is missing", errcode=Codes.UNAUTHORIZED) import jwt from jwt.exceptions import InvalidTokenError try: payload = jwt.decode(token, self.jwt_secret, algorithms=[self.jwt_algorithm]) except jwt.ExpiredSignatureError: raise LoginError(401, "JWT expired", errcode=Codes.UNAUTHORIZED) except InvalidTokenError: raise LoginError(401, "Invalid JWT", errcode=Codes.UNAUTHORIZED) user = payload.get("sub", None) if user is None: raise LoginError(401, "Invalid JWT", errcode=Codes.UNAUTHORIZED) user_id = UserID(user, self.hs.hostname).to_string() auth_handler = self.auth_handler registered_user_id = yield auth_handler.check_user_exists(user_id) if registered_user_id: device_id = login_submission.get("device_id") initial_display_name = login_submission.get( "initial_device_display_name") device_id, access_token = yield self.registration_handler.register_device( registered_user_id, device_id, initial_display_name, ) result = { "user_id": registered_user_id, "access_token": access_token, "home_server": self.hs.hostname, } else: user_id, access_token = ( yield self.handlers.registration_handler.register(localpart=user)) device_id = login_submission.get("device_id") initial_display_name = login_submission.get( "initial_device_display_name") device_id, access_token = yield self.registration_handler.register_device( registered_user_id, device_id, initial_display_name, ) result = { "user_id": user_id, # may have changed "access_token": access_token, "home_server": self.hs.hostname, } defer.returnValue(result)
async def on_POST(self, request: SynapseRequest): self._address_ratelimiter.ratelimit(request.getClientIP()) params = parse_json_object_from_request(request) logger.info("------------param:%s" % (str(params))) bind_type = params["bind_type"] auth_code = params["auth_code"] if bind_type is None: raise LoginError(410, "bind_type field for bind openid is missing", errcode=Codes.FORBIDDEN) if auth_code is None: raise LoginError(410, "auth_code field for bind openid is missing", errcode=Codes.FORBIDDEN) requester = await self.auth.get_user_by_req(request) logger.info('------requester: %s' % (requester, )) user_id = requester.user logger.info('------user: %s' % (user_id, )) openid = await self.hs.get_datastore( ).get_external_id_for_user_provider(bind_type, str(user_id)) if openid is not None: raise LoginError(416, "openid is binded,not support reeapt bind", errcode=Codes.OPENID_BINDED) #first get openid from cache with auth_code if self._cache.__contains__(auth_code): remote_user_id = self._cache[auth_code] logger.info('--cache----remote_user_id: %s' % (remote_user_id, )) if remote_user_id is not None: # complete bind await self.hs.get_datastore().record_user_external_id( bind_type, remote_user_id, str(user_id), ) self._cache.pop(auth_code, None) return 200, {} else: logger.info('-no-cache-by-auth_code--') # first get openid from cache with auth_code app_type = '' if bind_type == 'alipay': app_type = 'zhifubao' elif bind_type == 'weixin': app_type = 'weixin' # Desttop handler, only weixin if params.__contains__("device_type"): device_type = params["device_type"] if device_type: app_type = 'weixin-' + device_type # call get_openid from authserver remote_user = await self.get_openid_by_code(app_type, auth_code) if remote_user["code"] == 502: #get openid from cache,if exists then complete bind raise SynapseError(502, remote_user["message"]) if remote_user["code"] != 200 & remote_user["code"] != 502: raise SynapseError(500, remote_user["message"]) remote_user_id = remote_user['obj'] if remote_user_id is None: raise LoginError(414, "auth_code invalid", errcode=Codes.INVALID_AUTH_CODE) logger.info("======bind_type: %s remote_user_id: %s user_id: %s" % (bind_type, remote_user_id, str(user_id))) #complete bind await self.hs.get_datastore().record_user_external_id( bind_type, remote_user_id, str(user_id), ) return 200, {}
def check_auth(self, flows, clientdict, clientip): """ Takes a dictionary sent by the client in the login / registration protocol and handles the login flow. As a side effect, this function fills in the 'creds' key on the user's session with a map, which maps each auth-type (str) to the relevant identity authenticated by that auth-type (mostly str, but for captcha, bool). Args: flows (list): A list of login flows. Each flow is an ordered list of strings representing auth-types. At least one full flow must be completed in order for auth to be successful. clientdict: The dictionary from the client root level, not the 'auth' key: this method prompts for auth if none is sent. clientip (str): The IP address of the client. Returns: A tuple of (authed, dict, dict, session_id) where authed is true if the client has successfully completed an auth flow. If it is true the first dict contains the authenticated credentials of each stage. If authed is false, the first dictionary is the server response to the login request and should be passed back to the client. In either case, the second dict contains the parameters for this request (which may have been given only in a previous call). session_id is the ID of this session, either passed in by the client or assigned by the call to check_auth """ authdict = None sid = None if clientdict and 'auth' in clientdict: authdict = clientdict['auth'] del clientdict['auth'] if 'session' in authdict: sid = authdict['session'] session = self._get_session_info(sid) if len(clientdict) > 0: # This was designed to allow the client to omit the parameters # and just supply the session in subsequent calls so it split # auth between devices by just sharing the session, (eg. so you # could continue registration from your phone having clicked the # email auth link on there). It's probably too open to abuse # because it lets unauthenticated clients store arbitrary objects # on a home server. # Revisit: Assumimg the REST APIs do sensible validation, the data # isn't arbintrary. session['clientdict'] = clientdict self._save_session(session) elif 'clientdict' in session: clientdict = session['clientdict'] if not authdict: defer.returnValue( (False, self._auth_dict_for_flows(flows, session), clientdict, session['id'])) if 'creds' not in session: session['creds'] = {} creds = session['creds'] # check auth type currently being presented errordict = {} if 'type' in authdict: login_type = authdict['type'] if login_type not in self.checkers: raise LoginError(400, "", Codes.UNRECOGNIZED) try: result = yield self.checkers[login_type](authdict, clientip) if result: creds[login_type] = result self._save_session(session) except LoginError, e: if login_type == LoginType.EMAIL_IDENTITY: # riot used to have a bug where it would request a new # validation token (thus sending a new email) each time it # got a 401 with a 'flows' field. # (https://github.com/vector-im/vector-web/issues/2447). # # Grandfather in the old behaviour for now to avoid # breaking old riot deployments. raise e # this step failed. Merge the error dict into the response # so that the client can have another go. errordict = e.error_dict()
def validate_login(self, username, login_submission): """Authenticates the user for the /login API Also used by the user-interactive auth flow to validate m.login.password auth types. Args: username (str): username supplied by the user login_submission (dict): the whole of the login submission (including 'type' and other relevant fields) Returns: Deferred[str, func]: canonical user id, and optional callback to be called once the access token and device id are issued Raises: StoreError if there was a problem accessing the database SynapseError if there was a problem with the request LoginError if there was an authentication problem. """ if username.startswith("@"): qualified_user_id = username else: qualified_user_id = UserID(username, self.hs.hostname).to_string() login_type = login_submission.get("type") known_login_type = False # special case to check for "password" for the check_password interface # for the auth providers password = login_submission.get("password") if login_type == LoginType.PASSWORD: if not self._password_enabled: raise SynapseError(400, "Password login has been disabled.") if not password: raise SynapseError(400, "Missing parameter: password") for provider in self.password_providers: if hasattr(provider, "check_password") and login_type == LoginType.PASSWORD: known_login_type = True is_valid = yield provider.check_password( qualified_user_id, password) if is_valid: return qualified_user_id, None if not hasattr(provider, "get_supported_login_types") or not hasattr( provider, "check_auth"): # this password provider doesn't understand custom login types continue supported_login_types = provider.get_supported_login_types() if login_type not in supported_login_types: # this password provider doesn't understand this login type continue known_login_type = True login_fields = supported_login_types[login_type] missing_fields = [] login_dict = {} for f in login_fields: if f not in login_submission: missing_fields.append(f) else: login_dict[f] = login_submission[f] if missing_fields: raise SynapseError( 400, "Missing parameters for login type %s: %s" % (login_type, missing_fields), ) result = yield provider.check_auth(username, login_type, login_dict) if result: if isinstance(result, str): result = (result, None) return result if login_type == LoginType.PASSWORD and self.hs.config.password_localdb_enabled: known_login_type = True canonical_user_id = yield self._check_local_password( qualified_user_id, password) if canonical_user_id: return canonical_user_id, None if not known_login_type: raise SynapseError(400, "Unknown login type %s" % login_type) # We raise a 403 here, but note that if we're doing user-interactive # login, it turns all LoginErrors into a 401 anyway. raise LoginError(403, "Invalid password", errcode=Codes.FORBIDDEN)
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. Args: flows: list of list of stages authdict: The dictionary from the client root level, not the 'auth' key: this method prompts for auth if none is sent. Returns: A tuple of authed, dict, dict where authed is true if the client has successfully completed an auth flow. If it is true, the first dict contains the authenticated credentials of each stage. If authed is false, the first dictionary is the server response to the login request and should be passed back to the client. In either case, the second dict contains the parameters for this request (which may have been given only in a previous call). """ authdict = None sid = None if clientdict and 'auth' in clientdict: authdict = clientdict['auth'] del clientdict['auth'] if 'session' in authdict: sid = authdict['session'] sess = self._get_session_info(sid) if len(clientdict) > 0: # This was designed to allow the client to omit the parameters # and just supply the session in subsequent calls so it split # auth between devices by just sharing the session, (eg. so you # could continue registration from your phone having clicked the # email auth link on there). It's probably too open to abuse # because it lets unauthenticated clients store arbitrary objects # on a home server. # sess['clientdict'] = clientdict # self._save_session(sess) pass elif 'clientdict' in sess: clientdict = sess['clientdict'] if not authdict: defer.returnValue( (False, self._auth_dict_for_flows(flows, sess), clientdict) ) if 'creds' not in sess: sess['creds'] = {} creds = sess['creds'] # check auth type currently being presented if 'type' in authdict: if authdict['type'] not in self.checkers: raise LoginError(400, "", Codes.UNRECOGNIZED) result = yield self.checkers[authdict['type']](authdict, clientip) if result: creds[authdict['type']] = result self._save_session(sess) for f in flows: if len(set(f) - set(creds.keys())) == 0: logger.info("Auth completed with creds: %r", creds) self._remove_session(sess) defer.returnValue((True, creds, clientdict)) ret = self._auth_dict_for_flows(flows, sess) ret['completed'] = creds.keys() defer.returnValue((False, ret, clientdict))
def _check_password(self, user_id, password, stored_hash): """Checks that user_id has passed password, raises LoginError if not.""" if not bcrypt.checkpw(password, stored_hash): logger.warn("Failed password login for user %s", user_id) raise LoginError(403, "", errcode=Codes.FORBIDDEN)
def do_password_login(self, login_submission): if "password" not in login_submission: raise SynapseError(400, "Missing parameter: password") login_submission_legacy_convert(login_submission) if "identifier" not in login_submission: raise SynapseError(400, "Missing param: identifier") identifier = login_submission["identifier"] if "type" not in identifier: raise SynapseError(400, "Login identifier has no type") # convert phone type identifiers to generic threepids if identifier["type"] == "m.id.phone": identifier = login_id_thirdparty_from_phone(identifier) # convert threepid identifiers to user IDs if identifier["type"] == "m.id.thirdparty": if 'medium' not in identifier or 'address' not in identifier: raise SynapseError(400, "Invalid thirdparty identifier") address = identifier['address'] if identifier['medium'] == 'email': # For emails, transform the address to lowercase. # We store all email addreses as lowercase in the DB. # (See add_threepid in synapse/handlers/auth.py) address = address.lower() user_id = yield self.hs.get_datastore().get_user_id_by_threepid( identifier['medium'], address) if not user_id: raise LoginError(403, "", errcode=Codes.FORBIDDEN) identifier = { "type": "m.id.user", "user": user_id, } # by this point, the identifier should be an m.id.user: if it's anything # else, we haven't understood it. if identifier["type"] != "m.id.user": raise SynapseError(400, "Unknown login identifier type") if "user" not in identifier: raise SynapseError(400, "User identifier is missing 'user' key") user_id = identifier["user"] if not user_id.startswith('@'): user_id = UserID(user_id, self.hs.hostname).to_string() auth_handler = self.auth_handler user_id = yield auth_handler.validate_password_login( user_id=user_id, password=login_submission["password"], ) device_id = yield self._register_device(user_id, login_submission) access_token = yield auth_handler.get_access_token_for_user_id( user_id, device_id, login_submission.get("initial_device_display_name"), ) result = { "user_id": user_id, # may have changed "access_token": access_token, "home_server": self.hs.hostname, "device_id": device_id, } defer.returnValue((200, result))
def do_auth0_login(self, login_submission): auth0_access_token = login_submission.get("access_token", None) auth0_id_token = login_submission.get("id_token", None) auth0_tenant_domain = self.hs.config.auth0_tenant_domain auth0_client_id = self.hs.config.auth0_client_id auth0_username = self.hs.config.auth0_username auth0_url = "https://" + auth0_tenant_domain + "/" auth0_jwks_url = auth0_url + ".well-known/jwks.json" if auth0_access_token is None: raise LoginError(401, "Auth0 access_token is required", errcode=Codes.UNAUTHORIZED) if auth0_id_token is None: raise LoginError(401, "Auth0 id_token is required", errcode=Codes.UNAUTHORIZED) try: auth0_jwks_json = urllib.request.urlopen(auth0_jwks_url) auth0_jwks = json.loads(auth0_jwks_json.read()) except Exception: raise LoginError(401, "Failed to retrieve JWKS from Auth0", errcode=Codes.UNAUTHORIZED) try: from jose import jwt jwks_key = None jwks_alg = None token_header = jwt.get_unverified_header(auth0_id_token) for key in auth0_jwks["keys"]: if key["kid"] == token_header["kid"]: # this key id was used to encrypt the token jwks_alg = key["alg"] jwks_key = { "kty": key["kty"], "kid": key["kid"], "use": key["use"], "n": key["n"], "e": key["e"], } if jwks_key is None: raise LoginError( 401, "Unable to locate JWKS 'kid' matching JWT token", errcode=Codes.UNAUTHORIZED) payload = jwt.decode( auth0_id_token, jwks_key, algorithms=jwks_alg, audience=auth0_client_id, issuer=auth0_url, access_token=auth0_access_token, ) except jwt.ExpiredSignatureError: raise LoginError(401, "Expired JWT", errcode=Codes.UNAUTHORIZED) except jwt.JWTClaimsError: raise LoginError(401, "Invalid JWT claims", errcode=Codes.UNAUTHORIZED) except jwt.JWTError: raise LoginError(401, "Invalid JWT", errcode=Codes.UNAUTHORIZED) token_user = payload.get(auth0_username, None) if token_user is None: raise LoginError( 401, "JWT does not contain a username. Make sure 'profile' is included in your JWT request scope.", errcode=Codes.UNAUTHORIZED) user = UserID(token_user, self.hs.hostname) user_id = user.to_string() registered_user_id = yield self.auth_handler.check_user_exists(user_id) if registered_user_id is None: # register a new user registered_user_id, _ = yield self.handlers.registration_handler.register( localpart=token_user, generate_token=False, ) requestor = create_requester(user_id) # set initial display name displayname = payload.get("name", None) if displayname is not None: yield self.profile_handler.set_displayname(user, requestor, displayname, by_admin=True) # set initial avatar url avatar_url = payload.get("picture") if avatar_url is not None: yield self.profile_handler.set_avatar_url(user, requestor, avatar_url, by_admin=True) # get an access token device_id = yield self._register_device(registered_user_id, login_submission) access_token = yield self.auth_handler.get_access_token_for_user_id( registered_user_id, device_id, ) result = { "access_token": access_token, "device_id": device_id, "home_server": self.hs.hostname, "user_id": registered_user_id, } defer.returnValue((200, result))
async def _do_other_login(self, login_submission): """Handle non-token/saml/jwt logins Args: login_submission: Returns: dict: HTTP response """ # Log the request we got, but only certain fields to minimise the chance of # logging someone's password (even if they accidentally put it in the wrong # field) logger.info( "Got login request with identifier: %r, medium: %r, address: %r, user: %r", login_submission.get("identifier"), login_submission.get("medium"), login_submission.get("address"), login_submission.get("user"), ) login_submission_legacy_convert(login_submission) if "identifier" not in login_submission: raise SynapseError(400, "Missing param: identifier") identifier = login_submission["identifier"] if "type" not in identifier: raise SynapseError(400, "Login identifier has no type") # convert phone type identifiers to generic threepids if identifier["type"] == "m.id.phone": identifier = login_id_thirdparty_from_phone(identifier) # convert threepid identifiers to user IDs if identifier["type"] == "m.id.thirdparty": address = identifier.get("address") medium = identifier.get("medium") if medium is None or address is None: raise SynapseError(400, "Invalid thirdparty identifier") if medium == "email": # For emails, transform the address to lowercase. # We store all email addreses as lowercase in the DB. # (See add_threepid in synapse/handlers/auth.py) address = address.lower() # We also apply account rate limiting using the 3PID as a key, as # otherwise using 3PID bypasses the ratelimiting based on user ID. self._failed_attempts_ratelimiter.ratelimit( (medium, address), time_now_s=self._clock.time(), rate_hz=self.hs.config.rc_login_failed_attempts.per_second, burst_count=self.hs.config.rc_login_failed_attempts. burst_count, update=False, ) # Check for login providers that support 3pid login types ( canonical_user_id, callback_3pid, ) = await self.auth_handler.check_password_provider_3pid( medium, address, login_submission["password"]) if canonical_user_id: # Authentication through password provider and 3pid succeeded result = await self._complete_login(canonical_user_id, login_submission, callback_3pid) return result # No password providers were able to handle this 3pid # Check local store user_id = await self.hs.get_datastore().get_user_id_by_threepid( medium, address) if not user_id: logger.warning("unknown 3pid identifier medium %s, address %r", medium, address) # We mark that we've failed to log in here, as # `check_password_provider_3pid` might have returned `None` due # to an incorrect password, rather than the account not # existing. # # If it returned None but the 3PID was bound then we won't hit # this code path, which is fine as then the per-user ratelimit # will kick in below. self._failed_attempts_ratelimiter.can_do_action( (medium, address), time_now_s=self._clock.time(), rate_hz=self.hs.config.rc_login_failed_attempts.per_second, burst_count=self.hs.config.rc_login_failed_attempts. burst_count, update=True, ) raise LoginError(403, "", errcode=Codes.FORBIDDEN) identifier = {"type": "m.id.user", "user": user_id} # by this point, the identifier should be an m.id.user: if it's anything # else, we haven't understood it. if identifier["type"] != "m.id.user": raise SynapseError(400, "Unknown login identifier type") if "user" not in identifier: raise SynapseError(400, "User identifier is missing 'user' key") if identifier["user"].startswith("@"): qualified_user_id = identifier["user"] else: qualified_user_id = UserID(identifier["user"], self.hs.hostname).to_string() # Check if we've hit the failed ratelimit (but don't update it) self._failed_attempts_ratelimiter.ratelimit( qualified_user_id.lower(), time_now_s=self._clock.time(), rate_hz=self.hs.config.rc_login_failed_attempts.per_second, burst_count=self.hs.config.rc_login_failed_attempts.burst_count, update=False, ) try: canonical_user_id, callback = await self.auth_handler.validate_login( identifier["user"], login_submission) except LoginError: # The user has failed to log in, so we need to update the rate # limiter. Using `can_do_action` avoids us raising a ratelimit # exception and masking the LoginError. The actual ratelimiting # should have happened above. self._failed_attempts_ratelimiter.can_do_action( qualified_user_id.lower(), time_now_s=self._clock.time(), rate_hz=self.hs.config.rc_login_failed_attempts.per_second, burst_count=self.hs.config.rc_login_failed_attempts. burst_count, update=True, ) raise result = await self._complete_login(canonical_user_id, login_submission, callback) return result
async def _do_jwt_login( self, login_submission: JsonDict, should_issue_refresh_token: bool = False) -> LoginResponse: token = login_submission.get("token", None) if token is None: raise LoginError(403, "Token field for JWT is missing", errcode=Codes.FORBIDDEN) from authlib.jose import JsonWebToken, JWTClaims from authlib.jose.errors import BadSignatureError, InvalidClaimError, JoseError jwt = JsonWebToken([self.jwt_algorithm]) claim_options = {} if self.jwt_issuer is not None: claim_options["iss"] = { "value": self.jwt_issuer, "essential": True } if self.jwt_audiences is not None: claim_options["aud"] = { "values": self.jwt_audiences, "essential": True } try: claims = jwt.decode( token, key=self.jwt_secret, claims_cls=JWTClaims, claims_options=claim_options, ) except BadSignatureError: # We handle this case separately to provide a better error message raise LoginError( 403, "JWT validation failed: Signature verification failed", errcode=Codes.FORBIDDEN, ) except JoseError as e: # A JWT error occurred, return some info back to the client. raise LoginError( 403, "JWT validation failed: %s" % (str(e), ), errcode=Codes.FORBIDDEN, ) try: claims.validate(leeway=120) # allows 2 min of clock skew # Enforce the old behavior which is rolled out in productive # servers: if the JWT contains an 'aud' claim but none is # configured, the login attempt will fail if claims.get("aud") is not None: if self.jwt_audiences is None or len(self.jwt_audiences) == 0: raise InvalidClaimError("aud") except JoseError as e: raise LoginError( 403, "JWT validation failed: %s" % (str(e), ), errcode=Codes.FORBIDDEN, ) user = claims.get(self.jwt_subject_claim, None) if user is None: raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN) user_id = UserID(user, self.hs.hostname).to_string() result = await self._complete_login( user_id, login_submission, create_non_existent_users=True, should_issue_refresh_token=should_issue_refresh_token, ) return result
def _check_threepid(self, medium, authdict): if "threepid_creds" not in authdict: raise LoginError(400, "Missing threepid_creds", Codes.MISSING_PARAM) threepid_creds = authdict["threepid_creds"] identity_handler = self.hs.get_handlers().identity_handler logger.info("Getting validated threepid. threepidcreds: %r", (threepid_creds, )) # msisdns are currently always ThreepidBehaviour.REMOTE if medium == "msisdn": if not self.hs.config.account_threepid_delegate_msisdn: raise SynapseError( 400, "Phone number verification is not enabled on this homeserver" ) threepid = yield identity_handler.threepid_from_creds( self.hs.config.account_threepid_delegate_msisdn, threepid_creds) elif medium == "email": if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE: assert self.hs.config.account_threepid_delegate_email threepid = yield identity_handler.threepid_from_creds( self.hs.config.account_threepid_delegate_email, threepid_creds) elif self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL: threepid = None row = yield self.store.get_threepid_validation_session( medium, threepid_creds["client_secret"], sid=threepid_creds["sid"], validated=True, ) if row: threepid = { "medium": row["medium"], "address": row["address"], "validated_at": row["validated_at"], } # Valid threepid returned, delete from the db yield self.store.delete_threepid_session( threepid_creds["sid"]) else: raise SynapseError( 400, "Email address verification is not enabled on this homeserver" ) else: # this can't happen! raise AssertionError("Unrecognized threepid medium: %s" % (medium, )) if not threepid: raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) if threepid["medium"] != medium: raise LoginError( 401, "Expecting threepid of type '%s', got '%s'" % (medium, threepid["medium"]), errcode=Codes.UNAUTHORIZED, ) threepid["threepid_creds"] = authdict["threepid_creds"] return threepid
async def on_POST(self, request: SynapseRequest): params = parse_json_object_from_request(request) logger.info("-----pushgateway-------param:%s" % (str(params))) # bind_type = params["bind_type"] # if bind_type is None: # raise LoginError( # 410, "bind_type field for bind openid is missing", errcode=Codes.FORBIDDEN # ) notification = params["notification"] if notification is None: raise LoginError(410, "notification field for push_gateway is missing", errcode=Codes.FORBIDDEN) devices = notification["devices"] if devices is None: raise LoginError(410, "devices field for push_gateway is missing", errcode=Codes.FORBIDDEN) # room_id = notification["room_id"] # if room_id is None: # raise LoginError( # 410, "room_id field for push_gateway is missing", errcode=Codes.FORBIDDEN # ) # event_id = notification["event_id"] # if event_id is None: # raise LoginError( # 410, "event_id field for push_gateway is missing", errcode=Codes.FORBIDDEN # ) #select recievers pushkey if len(devices) == 0: raise LoginError(410, "devices field for push_gateway is missing", errcode=Codes.FORBIDDEN) logger.info("devices len--------:%s" % (len(devices))) reg_ids = [] jg_ids = [] huawei_ids = [] xiaomi_ids = [] appids = [] unread_counts = notification['counts']['unread'] if unread_counts is None: unread_counts = 0 for d in devices: logger.info("-----device----%s" % str(d, )) reg_ids.append(d["pushkey"]) appids.append(d['app_id']) logger.info("reg_ids len--------:%s" % (len(reg_ids))) logger.info("call appids--------:%s" % (str(appids))) appid = str(appids[0]) push_params = {"ids": reg_ids} # if appid == 'android_huawei': # push_params = { # "ids": [ # {"id": reg_ids[0], "count": unread_counts} # ] # } if 'format' not in params or devices[0]['data'][ 'format'] != 'event_id_only': if 'content' in notification: content = notification['content'] if content is None: logger.warning('content is None') return msg_type = None msg_body = '' event_type = notification['type'] msg_type = event_type if event_type == 'm.call.invite': # msg_type = event_type offer = content['offer'] if offer is None: raise LoginError( 410, "offer field for push_gateway is missing", errcode=Codes.FORBIDDEN) sdp = offer['sdp'] if sdp is None: raise LoginError( 410, "sdp field for push_gateway is missing", errcode=Codes.FORBIDDEN) # video call if 'm=video' in sdp: msg_type = 'call.video' elif 'm=audio' in sdp and 'm=video' not in sdp: msg_type = 'call.audio' elif event_type == 'm.room.message': # else: if 'msgtype' not in content: logger.warning('msg_type is None') return msg_type = content['msgtype'] msg_body = content['body'] event_id = notification['event_id'] sender = notification['sender'] sender_display_name = notification['sender_display_name'] room_id = notification['room_id'] # logger.info("call room_id--------:%s" % (room_id,)) #look room info room = await self.store.get_room_with_stats(room_id) logger.info("call room--------:%s" % (str(room))) #check room is direct room direct_room_count = await self.store.count_direct_room_for_room_id( room_id) logger.info("call direct_room_count--------:%s" % (direct_room_count, )) group_type = 'group' #default room_name,direct room then it is sender_display_name group_name = room['name'] reciever = '' # single chat room if direct_room_count >= 1: logger.info("the currently room is direct_room--------") group_type = 'single' group_name = sender_display_name # select reciever matrixID reciever_pusher = await self.store.get_pushers_by_app_id_and_pushkey( appid, reg_ids[0]) logger.info( "-=-=-=reciever_pushers reciever_pusher--------:%s" % (reciever_pusher, )) for row in reciever_pusher: logger.info("-=-=-=reciever_pusher row--------:%s" % (row, )) reciever = row.user_name break push_params['msgType'] = msg_type push_params['body'] = msg_body push_params['fromUserName'] = sender_display_name push_params['groupType'] = group_type push_params['groupName'] = group_name push_params['matrixId'] = reciever push_params['contactMatrixId'] = sender push_params['unreadCount'] = unread_counts push_params['senderDisplayName'] = sender_display_name push_params['eventId'] = event_id else: logger.warning('params:content is missed') return logger.info(" push_params--------:%s" % (str(push_params))) logger.info("call appid(push_type)-------:%s" % (str(appid))) push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_jiguang if appid.startswith('android_'): push_type = appid.split('_', 1)[1] if push_type == 'huawei': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_huawei elif push_type == 'firebase': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_firebase elif push_type == 'xiaomi': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_xiaomi elif push_type == 'oppo': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_oppo elif push_type == 'vivo': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_vivo elif push_type == 'getui': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_getui else: push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_jiguang elif appid == 'ios_apns': # push_type = appid.split('_', 1)[1] push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_apns elif appid == 'ios_apns_sandbox': push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_apns_sandbox # else: # push_url = self.hs.config.push_baseurl + self.hs.config.push_gateway_jiguang logger.info("call push_url-------:%s" % (str(push_url))) try: result = await self.http_client.post_json_get_json( push_url, push_params, ) # logger.info("%s get openid from %s: %s" % (str(app_type), self.hs.config.auth_get_vercode, remote_user)) logger.info("result: %s" % (str(result))) except HttpResponseException as e: logger.info("Proxied push jiguang gateway failed: %r", e) raise e.to_synapse_error() except RequestTimedOutError: raise SynapseError( 500, "Timed out contacting extral server:push jiguang gateway") return 200, {}
def _do_other_login(self, login_submission): """Handle non-token/saml/jwt logins Args: login_submission: Returns: dict: HTTP response """ # Log the request we got, but only certain fields to minimise the chance of # logging someone's password (even if they accidentally put it in the wrong # field) logger.info( "Got login request with identifier: %r, medium: %r, address: %r, user: %r", login_submission.get("identifier"), login_submission.get("medium"), login_submission.get("address"), login_submission.get("user"), ) login_submission_legacy_convert(login_submission) if "identifier" not in login_submission: raise SynapseError(400, "Missing param: identifier") identifier = login_submission["identifier"] if "type" not in identifier: raise SynapseError(400, "Login identifier has no type") # convert phone type identifiers to generic threepids if identifier["type"] == "m.id.phone": identifier = login_id_thirdparty_from_phone(identifier) # convert threepid identifiers to user IDs if identifier["type"] == "m.id.thirdparty": address = identifier.get("address") medium = identifier.get("medium") if medium is None or address is None: raise SynapseError(400, "Invalid thirdparty identifier") if medium == "email": # For emails, transform the address to lowercase. # We store all email addreses as lowercase in the DB. # (See add_threepid in synapse/handlers/auth.py) address = address.lower() # Check for login providers that support 3pid login types canonical_user_id, callback_3pid = ( yield self.auth_handler.check_password_provider_3pid( medium, address, login_submission["password"])) if canonical_user_id: # Authentication through password provider and 3pid succeeded result = yield self._register_device_with_callback( canonical_user_id, login_submission, callback_3pid) return result # No password providers were able to handle this 3pid # Check local store user_id = yield self.hs.get_datastore().get_user_id_by_threepid( medium, address) if not user_id: logger.warn("unknown 3pid identifier medium %s, address %r", medium, address) raise LoginError(403, "", errcode=Codes.FORBIDDEN) identifier = {"type": "m.id.user", "user": user_id} # by this point, the identifier should be an m.id.user: if it's anything # else, we haven't understood it. if identifier["type"] != "m.id.user": raise SynapseError(400, "Unknown login identifier type") if "user" not in identifier: raise SynapseError(400, "User identifier is missing 'user' key") canonical_user_id, callback = yield self.auth_handler.validate_login( identifier["user"], login_submission) result = yield self._register_device_with_callback( canonical_user_id, login_submission, callback) return result
def check_auth(self, flows, clientdict, clientip): """ Takes a dictionary sent by the client in the login / registration protocol and handles the login flow. As a side effect, this function fills in the 'creds' key on the user's session with a map, which maps each auth-type (str) to the relevant identity authenticated by that auth-type (mostly str, but for captcha, bool). Args: flows (list): A list of login flows. Each flow is an ordered list of strings representing auth-types. At least one full flow must be completed in order for auth to be successful. clientdict: The dictionary from the client root level, not the 'auth' key: this method prompts for auth if none is sent. clientip (str): The IP address of the client. Returns: A tuple of (authed, dict, dict, session_id) where authed is true if the client has successfully completed an auth flow. If it is true the first dict contains the authenticated credentials of each stage. If authed is false, the first dictionary is the server response to the login request and should be passed back to the client. In either case, the second dict contains the parameters for this request (which may have been given only in a previous call). session_id is the ID of this session, either passed in by the client or assigned by the call to check_auth """ authdict = None sid = None if clientdict and 'auth' in clientdict: authdict = clientdict['auth'] del clientdict['auth'] if 'session' in authdict: sid = authdict['session'] session = self._get_session_info(sid) if len(clientdict) > 0: # This was designed to allow the client to omit the parameters # and just supply the session in subsequent calls so it split # auth between devices by just sharing the session, (eg. so you # could continue registration from your phone having clicked the # email auth link on there). It's probably too open to abuse # because it lets unauthenticated clients store arbitrary objects # on a home server. # Revisit: Assumimg the REST APIs do sensible validation, the data # isn't arbintrary. session['clientdict'] = clientdict self._save_session(session) elif 'clientdict' in session: clientdict = session['clientdict'] if not authdict: defer.returnValue( ( False, self._auth_dict_for_flows(flows, session), clientdict, session['id'] ) ) if 'creds' not in session: session['creds'] = {} creds = session['creds'] # check auth type currently being presented if 'type' in authdict: if authdict['type'] not in self.checkers: raise LoginError(400, "", Codes.UNRECOGNIZED) result = yield self.checkers[authdict['type']](authdict, clientip) if result: creds[authdict['type']] = result self._save_session(session) for f in flows: if len(set(f) - set(creds.keys())) == 0: logger.info("Auth completed with creds: %r", creds) defer.returnValue((True, creds, clientdict, session['id'])) ret = self._auth_dict_for_flows(flows, session) ret['completed'] = creds.keys() defer.returnValue((False, ret, clientdict, session['id']))