def handle_cas_response(self, request, cas_response_body, client_redirect_url): user, attributes = self.parse_cas_response(cas_response_body) for required_attribute, required_value in self.cas_required_attributes.items( ): # If required attribute was not in CAS Response - Forbidden if required_attribute not in attributes: raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED) # Also need to check value if required_value is not None: actual_value = attributes[required_attribute] # If required attribute value does not match expected - Forbidden if required_value != actual_value: raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED) return self._sso_auth_handler.on_successful_auth( user, request, client_redirect_url, )
async def _do_jwt_login(self, login_submission: JsonDict) -> Dict[str, str]: token = login_submission.get("token", None) if token is None: raise LoginError(403, "Token field for JWT is missing", errcode=Codes.FORBIDDEN) import jwt try: payload = jwt.decode( token, self.jwt_secret, algorithms=[self.jwt_algorithm], issuer=self.jwt_issuer, audience=self.jwt_audiences, ) except jwt.PyJWTError 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, ) user = payload.get("sub", 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) return result
def login(self, user, password): """Login as the specified user with the specified password. Args: user (str): The user ID. password (str): The password. Returns: The newly allocated access token. Raises: StoreError if there was a problem storing the token. LoginError if there was an authentication problem. """ # TODO do this better, it can't go in __init__ else it cyclic loops if not hasattr(self, "reg_handler"): self.reg_handler = self.hs.get_handlers().registration_handler # pull out the hash for this user if they exist 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) stored_hash = user_info[0]["password_hash"] if bcrypt.checkpw(password, stored_hash): # generate an access token and store it. token = self.reg_handler._generate_token(user) logger.info("Adding token %s for user %s", token, user) yield self.store.add_access_token_to_user(user, token) defer.returnValue(token) else: logger.warn("Failed password login for user %s", user) raise LoginError(403, "", errcode=Codes.FORBIDDEN)
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 handle_cas_response(self, request, cas_response_body, client_redirect_url): user, attributes = self.parse_cas_response(cas_response_body) for required_attribute, required_value in self.cas_required_attributes.items( ): # If required attribute was not in CAS Response - Forbidden if required_attribute not in attributes: raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED) # Also need to check value if required_value is not None: actual_value = attributes[required_attribute] # If required attribute value does not match expected - Forbidden if required_value != actual_value: raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED) user_id = UserID.create(user, self.hs.hostname).to_string() auth_handler = self.auth_handler registered_user_id = yield auth_handler.check_user_exists(user_id) if not registered_user_id: registered_user_id, _ = ( yield self.handlers.registration_handler.register(localpart=user)) login_token = auth_handler.generate_short_term_login_token( registered_user_id) redirect_url = self.add_login_token_to_redirect_url( client_redirect_url, login_token) request.redirect(redirect_url) finish_request(request)
def _check_threepid(self, medium, authdict): yield run_on_reactor() 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, )) threepid = yield identity_handler.threepid_from_creds(threepid_creds) 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'] defer.returnValue(threepid)
async 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() result = await self._complete_login(user_id, login_submission, create_non_existant_users=True) return result
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() registered_user_id = yield self.auth_handler.check_user_exists(user_id) if not registered_user_id: registered_user_id = yield self.registration_handler.register_user( localpart=user) result = yield self._register_device_with_callback( registered_user_id, login_submission) return result
def parse_cas_response(self, cas_response_body): user = None attributes = {} try: root = ET.fromstring(cas_response_body) if not root.tag.endswith("serviceResponse"): raise Exception("root of CAS response is not serviceResponse") success = root[0].tag.endswith("authenticationSuccess") for child in root[0]: if child.tag.endswith("user"): user = child.text if child.tag.endswith("attributes"): for attribute in child: # ElementTree library expands the namespace in # attribute tags to the full URL of the namespace. # We don't care about namespace here and it will always # be encased in curly braces, so we remove them. tag = attribute.tag if "}" in tag: tag = tag.split("}")[1] attributes[tag] = attribute.text if user is None: raise Exception("CAS response does not contain user") except Exception: logger.error("Error parsing CAS response", exc_info=1) raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED) if not success: raise LoginError(401, "Unsuccessful CAS response", errcode=Codes.UNAUTHORIZED) return user, attributes
def parse_cas_response(self, cas_response_body): root = ET.fromstring(cas_response_body) if not root.tag.endswith("serviceResponse"): raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED) if not root[0].tag.endswith("authenticationSuccess"): raise LoginError(401, "Unsuccessful CAS response", errcode=Codes.UNAUTHORIZED) for child in root[0]: if child.tag.endswith("user"): user = child.text if child.tag.endswith("attributes"): attributes = {} for attribute in child: # ElementTree library expands the namespace in attribute tags # to the full URL of the namespace. # See (https://docs.python.org/2/library/xml.etree.elementtree.html) # We don't care about namespace here and it will always be encased in # curly braces, so we remove them. if "}" in attribute.tag: attributes[attribute.tag.split("}") [1]] = attribute.text else: attributes[attribute.tag] = attribute.text if user is None or attributes is None: raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED) return (user, attributes)
async def on_POST(self, request: SynapseRequest): self._address_ratelimiter.ratelimit(request.getClientIP()) params = parse_json_object_from_request(request) logger.info("----lookup bind--------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) 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 openid = await self.hs.get_datastore( ).get_external_id_for_user_provider( bind_type, str(user_id), ) if openid is None: raise LoginError(400, "openid not bind", errcode=Codes.OPENID_NOT_BIND) return 200, {}
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) if payload.get("iss") == "itsyouonline": user = payload.get("username", None) else: 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 = yield self._register_device(registered_user_id, login_submission) access_token = yield auth_handler.get_access_token_for_user_id( registered_user_id, device_id, ) result = { "user_id": registered_user_id, "access_token": access_token, "home_server": self.hs.hostname, } else: # TODO: we should probably check that the register isn't going # to fonx/change our user_id before registering the device device_id = yield self._register_device(user_id, login_submission) user_id, access_token = ( yield self.handlers.registration_handler.register(localpart=user)) result = { "user_id": user_id, # may have changed "access_token": access_token, "home_server": self.hs.hostname, } defer.returnValue((200, result))
async def _do_ver_code_email_login( self, login_submission: JsonDict) -> Dict[str, str]: email = login_submission.get("email", None) if email is None: raise LoginError(410, "Email field for ver_code_email is missing", errcode=Codes.FORBIDDEN) # verify email and send to email user_id = await self.hs.get_datastore().get_user_id_by_threepid( "email", email) if user_id is None: raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND) ver_code = login_submission.get("ver_code", None) if ver_code is None: raise LoginError(410, "ver_code field for ver_code_email is missing", errcode=Codes.FORBIDDEN) # ver_code_service_host = "192.168.0.4" # ver_code_service_port = "8080" # ver_code_service_validation_api = "/api/services/auth/v1/code/validation" params = {"value": email, "type": "email", "code": ver_code} try: ver_code_res = await self.http_client.post_json_get_json( self.hs.config.auth_baseurl + self.hs.config.auth_code_validation, params, ) logger.info("email ver_code_res: %s" % (str(ver_code_res))) if ver_code_res["code"] != 200: raise LoginError(412, "ver_code invalid", errcode=Codes.FORBIDDEN) except HttpResponseException as e: logger.info("Proxied validation vercode failed: %r", e) raise e.to_synapse_error() except RequestTimedOutError: raise SynapseError( 500, "Timed out contacting extral server:ver_code_send_service") # lookup cache_ver_code from redis by email # self.hs.get_redis # ver_code == cache_ver_code # call IS for verify email ver_code # identity_handler = self.hs.get_identity_handler() # result = await identity_handler.request_validate_threepid_ver_code(email, ver_code) result = await self._complete_login(user_id, login_submission, create_non_existent_users=True) return result
def _check_threepid(self, medium, authdict, password_servlet=False, **kwargs): 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,)) if ( not password_servlet or self.hs.config.email_password_reset_behaviour == "remote" ): threepid = yield identity_handler.threepid_from_creds(threepid_creds) elif self.hs.config.email_password_reset_behaviour == "local": row = yield self.store.get_threepid_validation_session( medium, threepid_creds["client_secret"], sid=threepid_creds["sid"], validated=True, ) threepid = ( { "medium": row["medium"], "address": row["address"], "validated_at": row["validated_at"], } if row else None ) if row: # Valid threepid returned, delete from the db yield self.store.delete_threepid_session(threepid_creds["sid"]) else: raise SynapseError( 400, "Password resets are not enabled on this homeserver" ) 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"] defer.returnValue(threepid)
async def on_POST(self, request: SynapseRequest): self._address_ratelimiter.ratelimit(request.getClientIP()) params = parse_json_object_from_request(request) medium = params["medium"] address = params["address"] if medium is None: raise LoginError(410, "medium field for get_ver_code is missing", errcode=Codes.FORBIDDEN) if address is None: raise LoginError(410, "address field for get_ver_code is missing", errcode=Codes.FORBIDDEN) if medium not in ("email", "msisdn"): raise LoginError(411, "no support medium", errcode=Codes.FORBIDDEN) # verify medium and send to email existing_user_id = await self.hs.get_datastore( ).get_user_id_by_threepid(medium, address) if existing_user_id is None: if medium == "msisdn": raise SynapseError(400, "msisdn not bind", Codes.TEMPORARY_NOT_BIND_MSISDN) else: raise SynapseError(400, "email not bind", Codes.EMAIL_NOT_BIND) # call IS for verify email ver_code # identity_handler = self.hs.get_identity_handler() # result = await identity_handler.request_get_threepid_ver_code(self.hs.config.account_threepid_delegate_email, "email", email) # logger.info("result:%s" % (str(result))) # self.get_ver_code_cache.setdefault(email, result["verCode"]) # ver_code_service_host = "192.168.15.4" # ver_code_service_port = "8080" # ver_code_service_send_api = "/api/services/auth/v1/code" sendSmsType = medium if sendSmsType == "msisdn": sendSmsType = "mobile" params = {"value": address, "type": sendSmsType} try: result = await self.http_client.post_json_get_json( self.hs.config.auth_baseurl + self.hs.config.auth_get_vercode, params, ) logger.info("result: %s" % (str(result))) if result["code"] != 200: raise SynapseError(500, result["message"]) except HttpResponseException as e: logger.info("Proxied getvercode failed: %r", e) raise e.to_synapse_error() except RequestTimedOutError: raise SynapseError( 500, "Timed out contacting extral server:ver_code_send_service") return 200, {}
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() device_id = login_submission.get("device_id") initial_display_name = login_submission.get( "initial_device_display_name") auth_handler = self.auth_handler registered_user_id = yield auth_handler.check_user_exists(user_id) if registered_user_id: 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.registration_handler.register( localpart=user)) device_id, access_token = yield self.registration_handler.register_device( 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 check_auth(self, authdict: dict, clientip: str) -> Any: if "token" not in authdict: raise LoginError(400, "Missing registration token", Codes.MISSING_PARAM) if not isinstance(authdict["token"], str): raise LoginError( 400, "Registration token must be a string", Codes.INVALID_PARAM ) if "session" not in authdict: raise LoginError(400, "Missing UIA session", Codes.MISSING_PARAM) # Get these here to avoid cyclic dependencies from synapse.handlers.ui_auth import UIAuthSessionDataConstants auth_handler = self.hs.get_auth_handler() session = authdict["session"] token = authdict["token"] # If the LoginType.REGISTRATION_TOKEN stage has already been completed, # return early to avoid incrementing `pending` again. stored_token = await auth_handler.get_session_data( session, UIAuthSessionDataConstants.REGISTRATION_TOKEN ) if stored_token: if token != stored_token: raise LoginError( 400, "Registration token has changed", Codes.INVALID_PARAM ) else: return token if await self.store.registration_token_is_valid(token): # Increment pending counter, so that if token has limited uses it # can't be used up by someone else in the meantime. await self.store.set_registration_token_pending(token) # Store the token in the UIA session, so that once registration # is complete `completed` can be incremented. await auth_handler.set_session_data( session, UIAuthSessionDataConstants.REGISTRATION_TOKEN, token, ) # The token will be stored as the result of the authentication stage # in ui_auth_sessions_credentials. This allows the pending counter # for tokens to be decremented when expired sessions are deleted. return token else: raise LoginError( 401, "Invalid registration token", errcode=Codes.UNAUTHORIZED )
def _check_password_auth(self, authdict, _): if "user" not in authdict or "password" not in authdict: raise LoginError(400, "", Codes.MISSING_PARAM) user_id = authdict["user"] password = authdict["password"] if not user_id.startswith('@'): user_id = UserID.create(user_id, self.hs.hostname).to_string() if not (yield self._check_password(user_id, password)): logger.warn("Failed password login for user %s", user_id) raise LoginError(403, "", errcode=Codes.FORBIDDEN) defer.returnValue(user_id)
async def _do_sso_ldap_login(self, login_submission: JsonDict) -> Dict[str, str]: login_type = login_submission.get("type", None) username = login_submission.get("user", None) if username is None: raise LoginError(410, "user field for ldap login is missing", errcode=Codes.FORBIDDEN) password = login_submission.get("password", None) # logger.debug("----------------------password:%s" % (password,)) if password is None: raise LoginError(410, "password field for ldap login is missing", errcode=Codes.FORBIDDEN) if username.startswith("@"): qualified_user_id = username else: qualified_user_id = UserID(username, self.hs.hostname).to_string() # verify bind_type and openid user_id = await self.auth_handler.check_user_exists(qualified_user_id) logger.info("----------------------exists user_id:%s" % (user_id, )) if user_id is None: raise SynapseError(400, "user not exists", Codes.INVALID_USERNAME) params = { "account": username, "password": password, } try: ldap_ver_res = await self.http_client.post_json_get_json( self.hs.config.auth_baseurl + self.hs.config.auth_sso_ldap_validation, params, ) logger.info("ldap verification result: %s" % (str(ldap_ver_res))) if ldap_ver_res["code"] != 200: raise SynapseError(500, ldap_ver_res["message"]) except HttpResponseException as e: logger.info("Proxied ldap verification failed: %r", e) raise e.to_synapse_error() except RequestTimedOutError: raise SynapseError( 500, "Timed out contacting extral server:ldap verification") result = await self._complete_login(user_id, login_submission, create_non_existent_users=True) return result
async def _do_appservice_login(self, login_submission: JsonDict, appservice: ApplicationService): 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())
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 _check_password(self, user_id, password): """Authenticate a user against the LDAP and local databases. user_id is checked case insensitively against the local database, 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 login fails """ for provider in self.password_providers: is_valid = yield provider.check_password(user_id, password) if is_valid: defer.returnValue(user_id) canonical_user_id = yield self._check_local_password(user_id, password) if canonical_user_id: defer.returnValue(canonical_user_id) # unknown username or invalid password. 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 on_POST(self, request): body = parse_json_object_from_request(request) 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)) user_id = None requester = 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) else: logger.error("Auth succeeded but no known type!", result.keys()) raise SynapseError(500, "", Codes.UNKNOWN) # FIXME: Theoretically there is a race here wherein user resets password # using threepid. yield self.store.user_delete_access_tokens(user_id) yield self.store.user_delete_threepids(user_id) yield self.store.user_set_password_hash(user_id, None) defer.returnValue((200, {}))
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))
def login_with_password(self, user_id, password): """ Authenticates the user with their username and password. Used only by the v1 login API. Args: user_id (str): User ID password (str): Password Returns: A tuple of: The user's ID. The access token for the user's session. The refresh token for the user's session. Raises: StoreError if there was a problem storing the token. LoginError if there was an authentication problem. """ if not (yield self._check_password(user_id, password)): logger.warn("Failed password login for user %s", user_id) raise LoginError(403, "", errcode=Codes.FORBIDDEN) logger.info("Logging in user %s", user_id) access_token = yield self.issue_access_token(user_id) refresh_token = yield self.issue_refresh_token(user_id) defer.returnValue((user_id, access_token, refresh_token))
async def _do_ver_code_msisdn_login( self, login_submission: JsonDict) -> Dict[str, str]: msisdn = login_submission.get("msisdn", None) if msisdn is None: raise LoginError(410, "msisdn field for ver_code_login is missing", errcode=Codes.FORBIDDEN) # verify email and send to email user_id = await self.hs.get_datastore().get_user_id_by_threepid( "msisdn", msisdn) if user_id is None: raise SynapseError(400, "msisdn not bind", Codes.TEMPORARY_NOT_BIND_MSISDN) ver_code = login_submission.get("ver_code", None) if ver_code is None: raise LoginError(411, "ver_code field for ver_code_login is missing", errcode=Codes.FORBIDDEN) # ver_code_service_host = "192.168.0.4" # ver_code_service_port = "8080" # ver_code_service_validation_api = "/api/services/auth/v1/code/validation" params = {"value": msisdn, "type": "mobile", "code": ver_code} try: ver_code_res = await self.http_client.post_json_get_json( self.hs.config.auth_baseurl + self.hs.config.auth_code_validation, params, ) logger.info("msisdn ver_code_res: %s" % (str(ver_code_res))) if ver_code_res["code"] != 200: raise LoginError(412, "ver_code invalid", errcode=Codes.FORBIDDEN) except HttpResponseException as e: logger.info("Proxied validation vercode failed: %r", e) raise e.to_synapse_error() except RequestTimedOutError: raise SynapseError( 500, "Timed out contacting extral server:ver_code_send_service") result = await self._complete_login(user_id, login_submission, create_non_existent_users=True) return result
async def add_oob_auth(self, stagetype: str, authdict: Dict[str, Any], clientip: str) -> bool: """ Adds the result of out-of-band authentication into an existing auth session. Currently used for adding the result of fallback auth. """ if stagetype not in self.checkers: raise LoginError(400, "", Codes.MISSING_PARAM) if "session" not in authdict: raise LoginError(400, "", Codes.MISSING_PARAM) result = await self.checkers[stagetype].check_auth(authdict, clientip) if result: await self.store.mark_ui_auth_stage_complete( authdict["session"], stagetype, result) return True return False
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.create(user, self.hs.hostname).to_string() auth_handler = self.auth_handler user_exists = yield auth_handler.does_user_exist(user_id) if user_exists: user_id, access_token, refresh_token = ( yield auth_handler.get_login_tuple_for_user_id(user_id)) result = { "user_id": user_id, # may have changed "access_token": access_token, "refresh_token": refresh_token, "home_server": self.hs.hostname, } else: user_id, access_token = ( yield self.handlers.registration_handler.register(localpart=user)) result = { "user_id": user_id, # may have changed "access_token": access_token, "home_server": self.hs.hostname, } defer.returnValue((200, result))
async def check_auth(self, authdict: dict, clientip: str) -> Any: 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: assert self._secret is not None resp_body = await self._http_client.post_urlencoded_get_json( self._url, args={ "secret": self._secret, "response": user_response, "remoteip": clientip, }, ) except PartialDownloadError as pde: # Twisted is silly data = pde.response resp_body = json_decoder.decode(data.decode("utf-8")) if "success" in resp_body: # Note that we do NOT check the hostname here: we explicitly # intend the CAPTCHA to be presented by whatever client the # user is using, we just care that they have completed a CAPTCHA. logger.info( "%s reCAPTCHA from hostname %s", "Successful" if resp_body["success"] else "Failed", resp_body.get("hostname"), ) if resp_body["success"]: return True raise LoginError(401, "Captcha authentication failed", errcode=Codes.UNAUTHORIZED)
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 = self.hs.get_simple_http_client() resp_body = yield client.post_urlencoded_get_json( self.hs.config.recaptcha_siteverify_api, 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: # Note that we do NOT check the hostname here: we explicitly # intend the CAPTCHA to be presented by whatever client the # user is using, we just care that they have completed a CAPTCHA. logger.info( "%s reCAPTCHA from hostname %s", "Successful" if resp_body['success'] else "Failed", resp_body.get('hostname') ) if resp_body['success']: defer.returnValue(True) raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)