Example #1
0
    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)
Example #2
0
    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")
Example #3
0
    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
Example #4
0
    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)
Example #5
0
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)
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
    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")
Example #9
0
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)
Example #10
0
 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"}))
Example #11
0
    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)
Example #12
0
 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"}))
Example #13
0
    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)
Example #14
0
    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
Example #15
0
    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)
Example #16
0
    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)
Example #17
0
    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)
Example #18
0
 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)
Example #19
0
 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)
Example #20
0
    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)
Example #21
0
 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)
Example #22
0
    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)
Example #24
0
    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)
Example #25
0
    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)
Example #26
0
    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)
Example #27
0
 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)
Example #28
0
    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)
Example #29
0
    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)
Example #30
0
 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)
Example #31
0
    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)
Example #32
0
    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)
Example #33
0
 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)
Example #34
0
    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)
Example #35
0
    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)
Example #36
0
    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")
Example #37
0
    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)
Example #38
0
    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)
Example #39
0
    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")
Example #40
0
    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)
Example #41
0
    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")
Example #42
0
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)
Example #43
0
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)
Example #44
0
    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)
Example #45
0
    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")
Example #46
0
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)
Example #47
0
 def cbFinished(ignored):
     f.close()
     finish_request(request)
Example #48
0
 def finish(self):
     self.request.write(json.dumps(self.streams, ensure_ascii=False))
     finish_request(self.request)
Example #49
0
    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")