def _async_render_GET(self, request): limit = parse_integer(request, "limit", 100) timeout = parse_integer(request, "timeout", 10 * 1000) request.setHeader(b"Content-Type", b"application/json") request_streams = { name: parse_integer(request, name) for names in STREAM_NAMES for name in names } request_streams["streams"] = parse_string(request, "streams") def replicate(): return self.replicate(request_streams, limit) writer = yield self.notifier.wait_for_replication(replicate, timeout) result = writer.finish() for stream_name, stream_content in result.items(): logger.info( "Replicating %d rows of %s from %s -> %s", len(stream_content["rows"]), stream_name, request_streams.get(stream_name), stream_content["position"], ) request.write(json.dumps(result, ensure_ascii=False)) finish_request(request)
def on_GET(self, request, stagetype): yield if stagetype == LoginType.RECAPTCHA: if ('session' not in request.args or len(request.args['session']) == 0): raise SynapseError(400, "No session supplied") session = request.args["session"][0] html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/auth/%s/fallback/web" % ( CLIENT_V2_ALPHA_PREFIX, LoginType.RECAPTCHA ), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Server", self.hs.version_string) request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) request.write(html_bytes) finish_request(request) defer.returnValue(None) else: raise SynapseError(404, "Unknown auth stage type")
async def on_GET(self, request, stagetype): session = parse_string(request, "session") if not session: raise SynapseError(400, "No session supplied") if stagetype == LoginType.RECAPTCHA: html = RECAPTCHA_TEMPLATE % { "session": session, "myurl": "%s/r0/auth/%s/fallback/web" % (CLIENT_API_PREFIX, LoginType.RECAPTCHA), "sitekey": self.hs.config.recaptcha_public_key, } elif stagetype == LoginType.TERMS: html = TERMS_TEMPLATE % { "session": session, "terms_url": "%s_matrix/consent?v=%s" % (self.hs.config.public_baseurl, self.hs.config.user_consent_version), "myurl": "%s/r0/auth/%s/fallback/web" % (CLIENT_API_PREFIX, LoginType.TERMS), } elif stagetype == LoginType.SSO: # Display a confirmation page which prompts the user to # re-authenticate with their SSO provider. if self._cas_enabled: # Generate a request to CAS that redirects back to an endpoint # to verify the successful authentication. sso_redirect_url = self._cas_handler.get_redirect_url( {"session": session}, ) elif self._saml_enabled: client_redirect_url = "" sso_redirect_url = self._saml_handler.handle_redirect_request( client_redirect_url, session) else: raise SynapseError(400, "Homeserver not configured for SSO.") html = await self.auth_handler.start_sso_ui_auth( sso_redirect_url, session) else: raise SynapseError(404, "Unknown auth stage type") # Render the HTML and return. html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request) return None
def _async_render_GET(self, request): limit = parse_integer(request, "limit", 100) timeout = parse_integer(request, "timeout", 10 * 1000) request.setHeader(b"Content-Type", b"application/json") request_streams = { name: parse_integer(request, name) for names in STREAM_NAMES for name in names } request_streams["streams"] = parse_string(request, "streams") def replicate(): return self.replicate(request_streams, limit) result = yield self.notifier.wait_for_replication(replicate, timeout) for stream_name, stream_content in result.items(): logger.info( "Replicating %d rows of %s from %s -> %s", len(stream_content["rows"]), stream_name, request_streams.get(stream_name), stream_content["position"], ) request.write(json.dumps(result, ensure_ascii=False)) finish_request(request)
def respond_with_responder(request, responder, media_type, file_size, upload_name=None): """Responds to the request with given responder. If responder is None then returns 404. Args: request (twisted.web.http.Request) responder (Responder|None) media_type (str): The media/content type. file_size (int|None): Size in bytes of the media. If not known it should be None upload_name (str|None): The name of the requested file, if any. """ if not responder: respond_404(request) return logger.debug("Responding to media request with responder %s") add_file_headers(request, media_type, file_size, upload_name) try: with responder: yield responder.write_to_consumer(request) except Exception as e: # The majority of the time this will be due to the client having gone # away. Unfortunately, Twisted simply throws a generic exception at us # in that case. logger.warning("Failed to write to consumer: %s %s", type(e), e) # Unregister the producer, if it has one, so Twisted doesn't complain if request.producer: request.unregisterProducer() finish_request(request)
async def complete_sso_ui_auth( self, registered_user_id: str, session_id: str, request: SynapseRequest, ): """Having figured out a mxid for this user, complete the HTTP request Args: registered_user_id: The registered user ID to complete SSO login for. request: The request to complete. client_redirect_url: The URL to which to redirect the user at the end of the process. """ # Mark the stage of the authentication as successful. # Save the user who authenticated with SSO, this will be used to ensure # that the account be modified is also the person who logged in. await self.store.mark_ui_auth_stage_complete(session_id, LoginType.SSO, registered_user_id) # Render the HTML and return. html_bytes = self._sso_auth_success_template.encode("utf-8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request)
async def complete_sso_login( self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str, ): """Having figured out a mxid for this user, complete the HTTP request Args: registered_user_id: The registered user ID to complete SSO login for. request: The request to complete. client_redirect_url: The URL to which to redirect the user at the end of the process. """ # If the account has been deactivated, do not proceed with the login # flow. deactivated = await self.store.get_user_deactivated_status( registered_user_id) if deactivated: html_bytes = self._sso_account_deactivated_template.encode("utf-8") request.setResponseCode(403) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request) return self._complete_sso_login(registered_user_id, request, client_redirect_url)
def on_GET(self, request, stagetype): yield if stagetype == LoginType.RECAPTCHA: if ('session' not in request.args or len(request.args['session']) == 0): raise SynapseError(400, "No session supplied") session = request.args["session"][0] html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/auth/%s/fallback/web" % (CLIENT_V2_ALPHA_PREFIX, LoginType.RECAPTCHA), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request) defer.returnValue(None) else: raise SynapseError(404, "Unknown auth stage type")
def respond_with_responder(request, responder, media_type, file_size, upload_name=None): """Responds to the request with given responder. If responder is None then returns 404. Args: request (twisted.web.http.Request) responder (Responder|None) media_type (str): The media/content type. file_size (int|None): Size in bytes of the media. If not known it should be None upload_name (str|None): The name of the requested file, if any. """ if not responder: respond_404(request) return logger.debug("Responding to media request with responder %s", responder) add_file_headers(request, media_type, file_size, upload_name) try: with responder: yield responder.write_to_consumer(request) except Exception as e: # The majority of the time this will be due to the client having gone # away. Unfortunately, Twisted simply throws a generic exception at us # in that case. logger.warning("Failed to write to consumer: %s %s", type(e), e) # Unregister the producer, if it has one, so Twisted doesn't complain if request.producer: request.unregisterProducer() finish_request(request)
def on_POST(self, request): saml2_auth = None try: conf = config.SPConfig() conf.load_file(self.sp_config) SP = Saml2Client(conf) saml2_auth = SP.parse_authn_request_response( request.args['SAMLResponse'][0], BINDING_HTTP_POST) except Exception as e: # Not authenticated logger.exception(e) if saml2_auth and saml2_auth.status_ok() and not saml2_auth.not_signed: username = saml2_auth.name_id.text handler = self.handlers.registration_handler (user_id, token) = yield handler.register_saml2(username) # Forward to the RelayState callback along with ava if 'RelayState' in request.args: request.redirect(urllib.unquote( request.args['RelayState'][0]) + '?status=authenticated&access_token=' + token + '&user_id=' + user_id + '&ava=' + urllib.quote(json.dumps(saml2_auth.ava))) finish_request(request) defer.returnValue(None) defer.returnValue((200, {"status": "authenticated", "user_id": user_id, "token": token, "ava": saml2_auth.ava})) elif 'RelayState' in request.args: request.redirect(urllib.unquote( request.args['RelayState'][0]) + '?status=not_authenticated') finish_request(request) defer.returnValue(None) defer.returnValue((200, {"status": "not_authenticated"}))
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(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 = self.macaroon_gen.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 on_POST(self, request): saml2_auth = None try: conf = config.SPConfig() conf.load_file(self.sp_config) SP = Saml2Client(conf) saml2_auth = SP.parse_authn_request_response( request.args['SAMLResponse'][0], BINDING_HTTP_POST) except Exception as e: # Not authenticated logger.exception(e) if saml2_auth and saml2_auth.status_ok() and not saml2_auth.not_signed: username = saml2_auth.name_id.text handler = self.handlers.registration_handler (user_id, token) = yield handler.register_saml2(username) # Forward to the RelayState callback along with ava if 'RelayState' in request.args: request.redirect(urllib.parse.unquote( request.args['RelayState'][0]) + '?status=authenticated&access_token=' + token + '&user_id=' + user_id + '&ava=' + urllib.quote(json.dumps(saml2_auth.ava))) finish_request(request) defer.returnValue(None) defer.returnValue((200, {"status": "authenticated", "user_id": user_id, "token": token, "ava": saml2_auth.ava})) elif 'RelayState' in request.args: request.redirect(urllib.parse.unquote( request.args['RelayState'][0]) + '?status=not_authenticated') finish_request(request) defer.returnValue(None) defer.returnValue((200, {"status": "not_authenticated"}))
def on_GET(self, request): requester = yield self.auth.get_user_by_req(request, rights="delete_pusher") user = requester.user app_id = parse_string(request, "app_id", required=True) pushkey = parse_string(request, "pushkey", required=True) try: yield self.pusher_pool.remove_pusher( app_id=app_id, pushkey=pushkey, user_id=user.to_string(), ) except StoreError as se: if se.code != 404: # This is fine: they're already unsubscribed raise self.notifier.on_new_replication_data() request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % ( len(PushersRemoveRestServlet.SUCCESS_HTML), )) request.write(PushersRemoveRestServlet.SUCCESS_HTML) finish_request(request) defer.returnValue(None)
def on_GET(self, request): requester = yield self.auth.get_user_by_req(request, rights="delete_pusher") user = requester.user app_id = parse_string(request, "app_id", required=True) pushkey = parse_string(request, "pushkey", required=True) try: yield self.pusher_pool.remove_pusher(app_id=app_id, pushkey=pushkey, user_id=user.to_string()) except StoreError as se: if se.code != 404: # This is fine: they're already unsubscribed raise self.notifier.on_new_replication_data() request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader( b"Content-Length", b"%d" % (len(PushersRemoveRestServlet.SUCCESS_HTML), )) request.write(PushersRemoveRestServlet.SUCCESS_HTML) finish_request(request) return None
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(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 = self.macaroon_gen.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)
async def _async_render_GET(self, request: SynapseRequest) -> None: client_redirect_url = parse_string( request, "redirectUrl", required=True, encoding="utf-8" ) idp = parse_string(request, "idp", required=False) # if we need to pick an IdP, do so if not idp: return await self._serve_id_picker(request, client_redirect_url) # otherwise, redirect to the IdP's redirect URI providers = self._sso_handler.get_identity_providers() auth_provider = providers.get(idp) if not auth_provider: logger.info("Unknown idp %r", idp) self._sso_handler.render_error( request, "unknown_idp", "Unknown identity provider ID" ) return sso_url = await auth_provider.handle_redirect_request( request, client_redirect_url.encode("utf8") ) logger.info("Redirecting to %s", sso_url) request.redirect(sso_url) finish_request(request)
def _render_error(self, request, error: str, error_description: Optional[str] = None) -> None: """Renders the error template and respond with it. This is used to show errors to the user. The template of this page can be found under ``synapse/res/templates/sso_error.html``. Args: request: The incoming request from the browser. We'll respond with an HTML page describing the error. error: A technical identifier for this error. Those include well-known OAuth2/OIDC error types like invalid_request or access_denied. error_description: A human-readable description of the error. """ html_bytes = self._error_template.render( error=error, error_description=error_description).encode("utf-8") request.setResponseCode(400) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%i" % len(html_bytes)) request.write(html_bytes) finish_request(request)
def on_GET(self, request): args = request.args if b"redirectUrl" not in args: return 400, "Redirect URL not specified for SSO auth" client_redirect_url = args[b"redirectUrl"][0] sso_url = self.get_sso_url(client_redirect_url) request.redirect(sso_url) finish_request(request)
def on_GET(self, request): args = request.args if "redirectUrl" not in args: return (400, "Redirect URL not specified for CAS auth") client_redirect_url_param = urllib.urlencode({"redirectUrl": args["redirectUrl"][0]}) hs_redirect_url = self.cas_service_url + "/_matrix/client/api/v1/login/cas/ticket" service_param = urllib.urlencode({"service": "%s?%s" % (hs_redirect_url, client_redirect_url_param)}) request.redirect("%s?%s" % (self.cas_server_url, service_param)) finish_request(request)
async def handle_redirect_request( self, request: SynapseRequest, client_redirect_url: bytes ) -> None: """Handle an incoming request to /login/sso/redirect It redirects the browser to the authorization endpoint with a few parameters: - ``client_id``: the client ID set in ``oidc_config.client_id`` - ``response_type``: ``code`` - ``redirect_uri``: the callback URL ; ``{base url}/_synapse/oidc/callback`` - ``scope``: the list of scopes set in ``oidc_config.scopes`` - ``state``: a random string - ``nonce``: a random string In addition to redirecting the client, we are setting a cookie with a signed macaroon token containing the state, the nonce and the client_redirect_url params. Those are then checked when the client comes back from the provider. Args: request: the incoming request from the browser. We'll respond to it with a redirect and a cookie. client_redirect_url: the URL that we should redirect the client to when everything is done """ state = generate_token() nonce = generate_token() cookie = self._generate_oidc_session_token( state=state, nonce=nonce, client_redirect_url=client_redirect_url.decode(), ) request.addCookie( SESSION_COOKIE_NAME, cookie, path="/_synapse/oidc", max_age="3600", httpOnly=True, sameSite="lax", ) metadata = await self.load_metadata() authorization_endpoint = metadata.get("authorization_endpoint") uri = prepare_grant_uri( authorization_endpoint, client_id=self._client_auth.client_id, response_type="code", redirect_uri=self._callback_url, scope=self._scopes, state=state, nonce=nonce, ) request.redirect(uri) finish_request(request)
async def on_GET(self, request: SynapseRequest): client_redirect_url = parse_string(request, "redirectUrl", required=True, encoding=None) sso_url = await self._sso_handler.handle_redirect_request( request, client_redirect_url) logger.info("Redirecting to %s", sso_url) request.redirect(sso_url) finish_request(request)
def _render_template(self, request, template_name, **template_args): # get_template checks for ".." so we don't need to worry too much # about path traversal here. template_html = self._jinja_env.get_template( path.join(TEMPLATE_LANGUAGE, template_name)) html_bytes = template_html.render(**template_args).encode("utf8") request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%i" % len(html_bytes)) request.write(html_bytes) finish_request(request)
async def _async_render_GET(self, request): # We're not expecting any GET request on that resource if everything goes right, # but some IdPs sometimes end up responding with a 302 redirect on this endpoint. # In this case, just tell the user that something went wrong and they should # try to authenticate again. request.setResponseCode(400) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(self._error_html_content), )) request.write(self._error_html_content.encode("utf8")) finish_request(request)
def _render_template(self, request, template_name, **template_args): # get_template checks for ".." so we don't need to worry too much # about path traversal here. template_html = self._jinja_env.get_template( path.join(TEMPLATE_LANGUAGE, template_name) ) html_bytes = template_html.render(**template_args).encode("utf8") request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%i" % len(html_bytes)) request.write(html_bytes) finish_request(request)
def on_GET(self, request, medium): if medium != "email": raise SynapseError( 400, "This medium is currently not supported for password resets" ) if self.config.email_password_reset_behaviour == "off": if self.config.password_resets_were_disabled_due_to_email_config: logger.warn( "User password resets have been disabled due to lack of email config" ) raise SynapseError( 400, "Email-based password resets have been disabled on this server" ) sid = parse_string(request, "sid") client_secret = parse_string(request, "client_secret") token = parse_string(request, "token") # Attempt to validate a 3PID sesssion try: # Mark the session as valid next_link = yield self.datastore.validate_threepid_session( sid, client_secret, token, self.clock.time_msec() ) # Perform a 302 redirect if next_link is set if next_link: if next_link.startswith("file:///"): logger.warn( "Not redirecting to next_link as it is a local file: address" ) else: request.setResponseCode(302) request.setHeader("Location", next_link) finish_request(request) defer.returnValue(None) # Otherwise show the success template html = self.config.email_password_reset_success_html_content request.setResponseCode(200) except ThreepidValidationError as e: # Show a failure page with a reason html = self.load_jinja2_template( self.config.email_template_dir, self.config.email_password_reset_failure_template, template_vars={"failure_reason": e.msg}, ) request.setResponseCode(e.code) request.write(html.encode("utf-8")) finish_request(request) defer.returnValue(None)
async def on_GET(self, request): if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.local_threepid_handling_disabled_due_to_email_config: logger.warning( "Adding emails have been disabled due to lack of an email config" ) raise SynapseError( 400, "Adding an email to your account is disabled on this server" ) elif self.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE: raise SynapseError( 400, "This homeserver is not validating threepids. Use an identity server " "instead.", ) sid = parse_string(request, "sid", required=True) token = parse_string(request, "token", required=True) client_secret = parse_string(request, "client_secret", required=True) assert_valid_client_secret(client_secret) # Attempt to validate a 3PID session try: # Mark the session as valid next_link = await self.store.validate_threepid_session( sid, client_secret, token, self.clock.time_msec() ) # Perform a 302 redirect if next_link is set if next_link: if next_link.startswith("file:///"): logger.warning( "Not redirecting to next_link as it is a local file: address" ) else: request.setResponseCode(302) request.setHeader("Location", next_link) finish_request(request) return None # Otherwise show the success template html = self.config.email_add_threepid_template_success_html_content request.setResponseCode(200) except ThreepidValidationError as e: request.setResponseCode(e.code) # Show a failure page with a reason template_vars = {"failure_reason": e.msg} html = self.failure_email_template.render(**template_vars) request.write(html.encode("utf-8")) finish_request(request)
def on_GET(self, request): args = request.args if "redirectUrl" not in args: return (400, "Redirect URL not specified for CAS auth") client_redirect_url_param = urllib.urlencode( {"redirectUrl": args["redirectUrl"][0]}) hs_redirect_url = self.cas_service_url + "/_matrix/client/api/v1/login/cas/ticket" service_param = urllib.urlencode({ "service": "%s?%s" % (hs_redirect_url, client_redirect_url_param) }) request.redirect("%s/login?%s" % (self.cas_server_url, service_param)) finish_request(request)
async def on_GET(self, request, medium): # We currently only handle threepid token submissions for email if medium != "email": raise SynapseError( 400, "This medium is currently not supported for password resets" ) if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.local_threepid_handling_disabled_due_to_email_config: logger.warning( "Password reset emails have been disabled due to lack of an email config" ) raise SynapseError( 400, "Email-based password resets are disabled on this server" ) sid = parse_string(request, "sid", required=True) token = parse_string(request, "token", required=True) client_secret = parse_string(request, "client_secret", required=True) assert_valid_client_secret(client_secret) # Attempt to validate a 3PID session try: # Mark the session as valid next_link = await self.store.validate_threepid_session( sid, client_secret, token, self.clock.time_msec() ) # Perform a 302 redirect if next_link is set if next_link: if next_link.startswith("file:///"): logger.warning( "Not redirecting to next_link as it is a local file: address" ) else: request.setResponseCode(302) request.setHeader("Location", next_link) finish_request(request) return None # Otherwise show the success template html = self.config.email_password_reset_template_success_html request.setResponseCode(200) except ThreepidValidationError as e: request.setResponseCode(e.code) # Show a failure page with a reason template_vars = {"failure_reason": e.msg} html = self.failure_email_template.render(**template_vars) request.write(html.encode("utf-8")) finish_request(request)
def complete_sso_login( self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str, ): """Having figured out a mxid for this user, complete the HTTP request Args: registered_user_id: The registered user ID to complete SSO login for. request: The request to complete. client_redirect_url: The URL to which to redirect the user at the end of the process. """ # Create a login token login_token = self.macaroon_gen.generate_short_term_login_token( registered_user_id ) # Append the login token to the original redirect URL (i.e. with its query # parameters kept intact) to build the URL to which the template needs to # redirect the users once they have clicked on the confirmation link. redirect_url = self.add_query_param_to_url( client_redirect_url, "loginToken", login_token ) # if the client is whitelisted, we can redirect straight to it if client_redirect_url.startswith(self._whitelisted_sso_clients): request.redirect(redirect_url) finish_request(request) return # Otherwise, serve the redirect confirmation page. # Remove the query parameters from the redirect URL to get a shorter version of # it. This is only to display a human-readable URL in the template, but not the # URL we redirect users to. redirect_url_no_params = client_redirect_url.split("?")[0] html = self._sso_redirect_confirm_template.render( display_url=redirect_url_no_params, redirect_url=redirect_url, server_name=self._server_name, ).encode("utf-8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html),)) request.write(html) finish_request(request)
def on_GET(self, request): args = request.args if b"redirectUrl" not in args: return (400, "Redirect URL not specified for CAS auth") client_redirect_url_param = urllib.parse.urlencode({ b"redirectUrl": args[b"redirectUrl"][0] }).encode('ascii') hs_redirect_url = (self.cas_service_url + b"/_matrix/client/api/v1/login/cas/ticket") service_param = urllib.parse.urlencode({ b"service": b"%s?%s" % (hs_redirect_url, client_redirect_url_param) }).encode('ascii') request.redirect(b"%s/login?%s" % (self.cas_server_url, service_param)) finish_request(request)
def on_GET(self, request): if b"token" not in request.args: raise SynapseError(400, "Missing renewal token") renewal_token = request.args[b"token"][0] yield self.account_activity_handler.renew_account(renewal_token.decode('utf8')) request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % ( len(AccountValidityRenewServlet.SUCCESS_HTML), )) request.write(AccountValidityRenewServlet.SUCCESS_HTML) finish_request(request) defer.returnValue(None)
async def on_GET(self, request, medium): if medium != "email": raise SynapseError( 400, "This medium is currently not supported for registration" ) if self.config.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.local_threepid_handling_disabled_due_to_email_config: logger.warning( "User registration via email has been disabled due to lack of email config" ) raise SynapseError( 400, "Email-based registration is disabled on this server" ) sid = parse_string(request, "sid", required=True) client_secret = parse_string(request, "client_secret", required=True) token = parse_string(request, "token", required=True) # Attempt to validate a 3PID session try: # Mark the session as valid next_link = await self.store.validate_threepid_session( sid, client_secret, token, self.clock.time_msec() ) # Perform a 302 redirect if next_link is set if next_link: if next_link.startswith("file:///"): logger.warning( "Not redirecting to next_link as it is a local file: address" ) else: request.setResponseCode(302) request.setHeader("Location", next_link) finish_request(request) return None # Otherwise show the success template html = self.config.email_registration_template_success_html_content status_code = 200 except ThreepidValidationError as e: status_code = e.code # Show a failure page with a reason template_vars = {"failure_reason": e.msg} html = self._failure_email_template.render(**template_vars) respond_with_html(request, status_code, html)
def on_GET(self, request): args = request.args if b"redirectUrl" not in args: return (400, "Redirect URL not specified for CAS auth") client_redirect_url_param = urllib.parse.urlencode({ b"redirectUrl": args[b"redirectUrl"][0] }).encode('ascii') hs_redirect_url = (self.cas_service_url + b"/_matrix/client/r0/login/cas/ticket") service_param = urllib.parse.urlencode({ b"service": b"%s?%s" % (hs_redirect_url, client_redirect_url_param) }).encode('ascii') request.redirect(b"%s/login?%s" % (self.cas_server_url, service_param)) finish_request(request)
def on_GET(self, request): if b"token" not in request.args: raise SynapseError(400, "Missing renewal token") renewal_token = request.args[b"token"][0] yield self.account_activity_handler.renew_account( renewal_token.decode("utf8")) request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader( b"Content-Length", b"%d" % (len(AccountValidityRenewServlet.SUCCESS_HTML), )) request.write(AccountValidityRenewServlet.SUCCESS_HTML) finish_request(request) defer.returnValue(None)
def complete_sso_login(self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str): """Having figured out a mxid for this user, complete the HTTP request Args: registered_user_id: request: client_redirect_url: """ login_token = self._macaroon_gen.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 on_GET(self, request, stagetype): session = parse_string(request, "session") if not session: raise SynapseError(400, "No session supplied") if stagetype == LoginType.RECAPTCHA: html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/auth/%s/fallback/web" % (CLIENT_V2_ALPHA_PREFIX, LoginType.RECAPTCHA), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request) return None elif stagetype == LoginType.TERMS: html = TERMS_TEMPLATE % { 'session': session, 'terms_url': "%s_matrix/consent?v=%s" % ( self.hs.config.public_baseurl, self.hs.config.user_consent_version, ), 'myurl': "%s/auth/%s/fallback/web" % (CLIENT_V2_ALPHA_PREFIX, LoginType.TERMS), } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request) return None else: raise SynapseError(404, "Unknown auth stage type")
def on_successful_auth( self, username, request, client_redirect_url, user_display_name=None, ): """Called once the user has successfully authenticated with the SSO. Registers the user if necessary, and then returns a redirect (with a login token) to the client. Args: username (unicode|bytes): the remote user id. We'll map this onto something sane for a MXID localpath. request (SynapseRequest): the incoming request from the browser. We'll respond to it with a redirect. client_redirect_url (unicode): the redirect_url the client gave us when it first started the process. user_display_name (unicode|None): if set, and we have to register a new user, we will set their displayname to this. Returns: Deferred[none]: Completes once we have handled the request. """ localpart = map_username_to_mxid_localpart(username) user_id = UserID(localpart, self._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( localpart=localpart, generate_token=False, default_display_name=user_display_name, ) ) login_token = self._macaroon_gen.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 _complete_sso_login( self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str, ): """ The synchronous portion of complete_sso_login. This exists purely for backwards compatibility of synapse.module_api.ModuleApi. """ # Create a login token login_token = self.macaroon_gen.generate_short_term_login_token( registered_user_id) # Append the login token to the original redirect URL (i.e. with its query # parameters kept intact) to build the URL to which the template needs to # redirect the users once they have clicked on the confirmation link. redirect_url = self.add_query_param_to_url(client_redirect_url, "loginToken", login_token) # if the client is whitelisted, we can redirect straight to it if client_redirect_url.startswith(self._whitelisted_sso_clients): request.redirect(redirect_url) finish_request(request) return # Otherwise, serve the redirect confirmation page. # Remove the query parameters from the redirect URL to get a shorter version of # it. This is only to display a human-readable URL in the template, but not the # URL we redirect users to. redirect_url_no_params = client_redirect_url.split("?")[0] html_bytes = self._sso_redirect_confirm_template.render( display_url=redirect_url_no_params, redirect_url=redirect_url, server_name=self._server_name, ).encode("utf-8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request)
def on_POST(self, request, stagetype): yield if stagetype == "m.login.recaptcha": if ('g-recaptcha-response' not in request.args or len(request.args['g-recaptcha-response'])) == 0: raise SynapseError(400, "No captcha response supplied") if ('session' not in request.args or len(request.args['session'])) == 0: raise SynapseError(400, "No session supplied") session = request.args['session'][0] authdict = { 'response': request.args['g-recaptcha-response'][0], 'session': session, } success = yield self.auth_handler.add_oob_auth( LoginType.RECAPTCHA, authdict, self.hs.get_ip_from_request(request) ) if success: html = SUCCESS_TEMPLATE else: html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/auth/%s/fallback/web" % ( CLIENT_V2_ALPHA_PREFIX, LoginType.RECAPTCHA ), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Server", self.hs.version_string) request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) request.write(html_bytes) finish_request(request) defer.returnValue(None) else: raise SynapseError(404, "Unknown auth stage type")
def on_successful_auth( self, username, request, client_redirect_url, user_display_name=None, ): """Called once the user has successfully authenticated with the SSO. Registers the user if necessary, and then returns a redirect (with a login token) to the client. Args: username (unicode|bytes): the remote user id. We'll map this onto something sane for a MXID localpath. request (SynapseRequest): the incoming request from the browser. We'll respond to it with a redirect. client_redirect_url (unicode): the redirect_url the client gave us when it first started the process. user_display_name (unicode|None): if set, and we have to register a new user, we will set their displayname to this. Returns: Deferred[none]: Completes once we have handled the request. """ localpart = map_username_to_mxid_localpart(username) user_id = UserID(localpart, self._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( localpart=localpart, generate_token=False, default_display_name=user_display_name, )) login_token = self._macaroon_gen.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 on_POST(self, request, stagetype): yield if stagetype == "m.login.recaptcha": if ('g-recaptcha-response' not in request.args or len(request.args['g-recaptcha-response'])) == 0: raise SynapseError(400, "No captcha response supplied") if ('session' not in request.args or len(request.args['session'])) == 0: raise SynapseError(400, "No session supplied") session = request.args['session'][0] authdict = { 'response': request.args['g-recaptcha-response'][0], 'session': session, } success = yield self.auth_handler.add_oob_auth( LoginType.RECAPTCHA, authdict, self.hs.get_ip_from_request(request)) if success: html = SUCCESS_TEMPLATE else: html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/auth/%s/fallback/web" % (CLIENT_V2_ALPHA_PREFIX, LoginType.RECAPTCHA), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Server", self.hs.version_string) request.setHeader(b"Content-Length", b"%d" % (len(html_bytes), )) request.write(html_bytes) finish_request(request) defer.returnValue(None) else: raise SynapseError(404, "Unknown auth stage type")
def respond_with_file(request, media_type, file_path, file_size=None, upload_name=None): logger.debug("Responding with %r", file_path) if os.path.isfile(file_path): if file_size is None: stat = os.stat(file_path) file_size = stat.st_size add_file_headers(request, media_type, file_size, upload_name) with open(file_path, "rb") as f: yield logcontext.make_deferred_yieldable( FileSender().beginFileTransfer(f, request) ) finish_request(request) else: respond_404(request)
def _respond_with_file(self, request, media_type, file_path, file_size=None, upload_name=None): logger.debug("Responding with %r", file_path) if os.path.isfile(file_path): request.setHeader(b"Content-Type", media_type.encode("UTF-8")) if upload_name: if is_ascii(upload_name): request.setHeader( b"Content-Disposition", b"inline; filename=%s" % ( urllib.quote(upload_name.encode("utf-8")), ), ) else: request.setHeader( b"Content-Disposition", b"inline; filename*=utf-8''%s" % ( urllib.quote(upload_name.encode("utf-8")), ), ) # cache for at least a day. # XXX: we might want to turn this off for data we don't want to # recommend caching as it's sensitive or private - or at least # select private. don't bother setting Expires as all our # clients are smart enough to be happy with Cache-Control request.setHeader( b"Cache-Control", b"public,max-age=86400,s-maxage=86400" ) if file_size is None: stat = os.stat(file_path) file_size = stat.st_size request.setHeader( b"Content-Length", b"%d" % (file_size,) ) with open(file_path, "rb") as f: yield FileSender().beginFileTransfer(f, request) finish_request(request) else: self._respond_404(request)
def on_GET(self, request, stagetype): session = parse_string(request, "session") if not session: raise SynapseError(400, "No session supplied") if stagetype == LoginType.RECAPTCHA: html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/r0/auth/%s/fallback/web" % ( CLIENT_API_PREFIX, LoginType.RECAPTCHA ), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) request.write(html_bytes) finish_request(request) return None elif stagetype == LoginType.TERMS: html = TERMS_TEMPLATE % { 'session': session, 'terms_url': "%s_matrix/consent?v=%s" % ( self.hs.config.public_baseurl, self.hs.config.user_consent_version, ), 'myurl': "%s/r0/auth/%s/fallback/web" % ( CLIENT_API_PREFIX, LoginType.TERMS ), } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) request.write(html_bytes) finish_request(request) return None else: raise SynapseError(404, "Unknown auth stage type")
def respond_with_responder(request, responder, media_type, file_size, upload_name=None): """Responds to the request with given responder. If responder is None then returns 404. Args: request (twisted.web.http.Request) responder (Responder|None) media_type (str): The media/content type. file_size (int|None): Size in bytes of the media. If not known it should be None upload_name (str|None): The name of the requested file, if any. """ if not responder: respond_404(request) return logger.debug("Responding to media request with responder %s") add_file_headers(request, media_type, file_size, upload_name) with responder: yield responder.write_to_consumer(request) finish_request(request)
def cbFinished(ignored): f.close() finish_request(request)
def finish(self): self.request.write(json.dumps(self.streams, ensure_ascii=False)) finish_request(self.request)
def on_POST(self, request, stagetype): session = parse_string(request, "session") if not session: raise SynapseError(400, "No session supplied") if stagetype == LoginType.RECAPTCHA: response = parse_string(request, "g-recaptcha-response") if not response: raise SynapseError(400, "No captcha response supplied") authdict = { 'response': response, 'session': session, } success = yield self.auth_handler.add_oob_auth( LoginType.RECAPTCHA, authdict, self.hs.get_ip_from_request(request) ) if success: html = SUCCESS_TEMPLATE else: html = RECAPTCHA_TEMPLATE % { 'session': session, 'myurl': "%s/r0/auth/%s/fallback/web" % ( CLIENT_API_PREFIX, LoginType.RECAPTCHA ), 'sitekey': self.hs.config.recaptcha_public_key, } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) request.write(html_bytes) finish_request(request) defer.returnValue(None) elif stagetype == LoginType.TERMS: if ('session' not in request.args or len(request.args['session'])) == 0: raise SynapseError(400, "No session supplied") session = request.args['session'][0] authdict = {'session': session} success = yield self.auth_handler.add_oob_auth( LoginType.TERMS, authdict, self.hs.get_ip_from_request(request) ) if success: html = SUCCESS_TEMPLATE else: html = TERMS_TEMPLATE % { 'session': session, 'terms_url': "%s_matrix/consent?v=%s" % ( self.hs.config.public_baseurl, self.hs.config.user_consent_version, ), 'myurl': "%s/r0/auth/%s/fallback/web" % ( CLIENT_API_PREFIX, LoginType.TERMS ), } html_bytes = html.encode("utf8") request.setResponseCode(200) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) request.write(html_bytes) finish_request(request) defer.returnValue(None) else: raise SynapseError(404, "Unknown auth stage type")