def _async_render_POST(self, request):
        resp_bytes = parse_string(request, 'SAMLResponse', required=True)
        relay_state = parse_string(request, 'RelayState', required=True)

        try:
            saml2_auth = self._saml_client.parse_authn_request_response(
                resp_bytes,
                saml2.BINDING_HTTP_POST,
            )
        except Exception as e:
            logger.warning("Exception parsing SAML2 response", exc_info=1)
            raise CodeMessageException(
                400,
                "Unable to parse SAML2 response: %s" % (e, ),
            )

        if saml2_auth.not_signed:
            raise CodeMessageException(400, "SAML2 response was not signed")

        if "uid" not in saml2_auth.ava:
            raise CodeMessageException(400, "uid not in SAML2 response")

        username = saml2_auth.ava["uid"][0]

        displayName = saml2_auth.ava.get("displayName", [None])[0]
        return self._sso_auth_handler.on_successful_auth(
            username,
            request,
            relay_state,
            user_display_name=displayName,
        )
Exemple #2
0
    def get_raw(self, uri, args={}):
        """ Gets raw text from the given URI.

        Args:
            uri (str): The URI to request, not including query parameters
            args (dict): A dictionary used to create query strings, defaults to
                None.
                **Note**: The value of each key is assumed to be an iterable
                and *not* a string.
        Returns:
            Deferred: Succeeds when we get *any* 2xx HTTP response, with the
            HTTP body at text.
        Raises:
            On a non-2xx HTTP response. The response body will be used as the
            error message.
        """
        if len(args):
            query_bytes = urllib.urlencode(args, True)
            uri = "%s?%s" % (uri, query_bytes)

        response = yield self.request(
            "GET",
            uri.encode("ascii"),
            headers=Headers({
                b"User-Agent": [self.user_agent],
            })
        )

        body = yield preserve_context_over_fn(readBody, response)

        if 200 <= response.code < 300:
            defer.returnValue(body)
        else:
            raise CodeMessageException(response.code, body)
Exemple #3
0
    def make_homeserver(self, reactor, clock):
        hs_config = self.default_config()

        # some of the tests rely on us having a user consent version
        hs_config.setdefault("user_consent", {}).update(
            {
                "version": "test_consent_version",
                "template_dir": ".",
            }
        )
        hs_config["max_mau_value"] = 50
        hs_config["limit_usage_by_mau"] = True

        # Don't attempt to reach out over federation.
        self.mock_federation_client = Mock()
        self.mock_federation_client.make_query.side_effect = CodeMessageException(
            500, ""
        )

        hs = self.setup_test_homeserver(
            config=hs_config, federation_client=self.mock_federation_client
        )

        load_legacy_spam_checkers(hs)

        module_api = hs.get_module_api()
        for module, config in hs.config.modules.loaded_modules:
            module(config=config, api=module_api)

        return hs
 def _exceptionFromFailedRequest(self, response, body):
     try:
         jsonBody = json.loads(body)
         errcode = jsonBody['errcode']
         error = jsonBody['error']
         return MatrixCodeMessageException(response.code, error, errcode)
     except (ValueError, KeyError):
         return CodeMessageException(response.code, body)
Exemple #5
0
 def get_remote_user_id(self, saml_response: saml2.response.AuthnResponse,
                        client_redirect_url: str):
     """Extracts the remote user id from the SAML response"""
     if self._config.use_name_id_for_remote_uid:
         name_id = saml_response.name_id
         if not name_id:
             logger.warning("SAML2 response lacks a NameID field")
             raise CodeMessageException(400,
                                        "'NameID' not in SAML2 response")
         return name_id.text
     else:
         try:
             return saml_response.ava[UID_ATTRIBUTE_NAME][0]
         except KeyError:
             logger.warning("SAML2 response lacks a '%s' attribute",
                            UID_ATTRIBUTE_NAME)
             raise CodeMessageException(
                 400, "'%s' not in SAML2 response" % (UID_ATTRIBUTE_NAME, ))
Exemple #6
0
    def on_POST(self, request):
        params = _parse_json(request)
        try:
            as_token = params["as_token"]
            if not isinstance(as_token, basestring):
                raise ValueError
        except (KeyError, ValueError):
            raise SynapseError(400, "Missing required key: as_token(str)")

        yield self.handler.unregister(as_token)

        raise CodeMessageException(500, "Not implemented")
Exemple #7
0
    def put_json(self, uri, json_body, args={}, headers=None):
        """ Puts some json to the given URI.

        Args:
            uri (str): The URI to request, not including query parameters
            json_body (dict): The JSON to put in the HTTP body,
            args (dict): A dictionary used to create query strings, defaults to
                None.
                **Note**: The value of each key is assumed to be an iterable
                and *not* a string.
            headers (dict[str, List[str]]|None): If not None, a map from
               header name to a list of values for that header
        Returns:
            Deferred: Succeeds when we get *any* 2xx HTTP response, with the
            HTTP body as JSON.
        Raises:
            On a non-2xx HTTP response.
        """
        if len(args):
            query_bytes = urllib.urlencode(args, True)
            uri = "%s?%s" % (uri, query_bytes)

        json_str = encode_canonical_json(json_body)

        actual_headers = {
            b"Content-Type": [b"application/json"],
            b"User-Agent": [self.user_agent],
        }
        if headers:
            actual_headers.update(headers)

        response = yield self.request(
            "PUT",
            uri.encode("ascii"),
            headers=Headers(actual_headers),
            bodyProducer=FileBodyProducer(StringIO(json_str))
        )

        body = yield make_deferred_yieldable(readBody(response))

        if 200 <= response.code < 300:
            defer.returnValue(json.loads(body))
        else:
            # NB: This is explicitly not json.loads(body)'d because the contract
            # of CodeMessageException is a *string* message. Callers can always
            # load it into JSON if they want.
            raise CodeMessageException(response.code, body)
Exemple #8
0
    def put_json(self, uri, json_body, args={}):
        """ Puts some json to the given URI.

        Args:
            uri (str): The URI to request, not including query parameters
            json_body (dict): The JSON to put in the HTTP body,
            args (dict): A dictionary used to create query strings, defaults to
                None.
                **Note**: The value of each key is assumed to be an iterable
                and *not* a string.
        Returns:
            Deferred: Succeeds when we get *any* 2xx HTTP response, with the
            HTTP body as JSON.
        Raises:
            On a non-2xx HTTP response.
        """
        if len(args):
            query_bytes = urllib.urlencode(args, True)
            uri = "%s?%s" % (uri, query_bytes)

        json_str = json.dumps(json_body)

        response = yield self.agent.request(
            "PUT",
            uri.encode("ascii"),
            headers=Headers({
                b"User-Agent": [self.version_string],
                "Content-Type": ["application/json"]
            }),
            bodyProducer=FileBodyProducer(StringIO(json_str))
        )

        body = yield readBody(response)

        if 200 <= response.code < 300:
            defer.returnValue(json.loads(body))
        else:
            # NB: This is explicitly not json.loads(body)'d because the contract
            # of CodeMessageException is a *string* message. Callers can always
            # load it into JSON if they want.
            raise CodeMessageException(response.code, body)
Exemple #9
0
    def get_raw(self, uri, args={}, headers=None):
        """ Gets raw text from the given URI.

        Args:
            uri (str): The URI to request, not including query parameters
            args (dict): A dictionary used to create query strings, defaults to
                None.
                **Note**: The value of each key is assumed to be an iterable
                and *not* a string.
            headers (dict[str, List[str]]|None): If not None, a map from
               header name to a list of values for that header
        Returns:
            Deferred: Succeeds when we get *any* 2xx HTTP response, with the
            HTTP body at text.
        Raises:
            On a non-2xx HTTP response. The response body will be used as the
            error message.
        """
        if len(args):
            query_bytes = urllib.urlencode(args, True)
            uri = "%s?%s" % (uri, query_bytes)

        actual_headers = {
            b"User-Agent": [self.user_agent],
        }
        if headers:
            actual_headers.update(headers)

        response = yield self.request(
            "GET",
            uri.encode("ascii"),
            headers=Headers(actual_headers),
        )

        body = yield make_deferred_yieldable(readBody(response))

        if 200 <= response.code < 300:
            defer.returnValue(body)
        else:
            raise CodeMessageException(response.code, body)
Exemple #10
0
    def get_json(self, uri, args={}):
        """ Gets some json from the given URI.

        Args:
            uri (str): The URI to request, not including query parameters
            args (dict): A dictionary used to create query strings, defaults to
                None.
                **Note**: The value of each key is assumed to be an iterable
                and *not* a string.
        Returns:
            Deferred: Succeeds when we get *any* 2xx HTTP response, with the
            HTTP body as JSON.
        Raises:
            On a non-2xx HTTP response. The response body will be used as the
            error message.
        """
        if len(args):
            query_bytes = urllib.urlencode(args, True)
            uri = "%s?%s" % (uri, query_bytes)

        response = yield self.request(
            "GET",
            uri.encode("ascii"),
            headers=Headers({
                b"User-Agent": [self.version_string],
            })
        )

        body = yield readBody(response)

        if 200 <= response.code < 300:
            defer.returnValue(json.loads(body))
        else:
            # NB: This is explicitly not json.loads(body)'d because the contract
            # of CodeMessageException is a *string* message. Callers can always
            # load it into JSON if they want.
            raise CodeMessageException(response.code, body)
Exemple #11
0
    def _create_request(self, destination, method, path_bytes,
                        body_callback, headers_dict={}, param_bytes=b"",
                        query_bytes=b"", retry_on_dns_fail=True):
        """ Creates and sends a request to the given url
        """
        headers_dict[b"User-Agent"] = [b"Synapse"]
        headers_dict[b"Host"] = [destination]

        url_bytes = urlparse.urlunparse(
            ("", "", path_bytes, param_bytes, query_bytes, "",)
        )

        logger.info("Sending request to %s: %s %s",
                    destination, method, url_bytes)

        logger.debug(
            "Types: %s",
            [
                type(destination), type(method), type(path_bytes),
                type(param_bytes),
                type(query_bytes)
            ]
        )

        # XXX: Would be much nicer to retry only at the transaction-layer
        # (once we have reliable transactions in place)
        retries_left = 5

        endpoint = self._getEndpoint(reactor, destination)

        while True:
            producer = None
            if body_callback:
                producer = body_callback(method, url_bytes, headers_dict)

            try:
                with PreserveLoggingContext():
                    response = yield self.agent.request(
                        destination,
                        endpoint,
                        method,
                        path_bytes,
                        param_bytes,
                        query_bytes,
                        Headers(headers_dict),
                        producer
                    )

                logger.debug("Got response to %s", method)
                break
            except Exception as e:
                if not retry_on_dns_fail and isinstance(e, DNSLookupError):
                    logger.warn(
                        "DNS Lookup failed to %s with %s",
                        destination,
                        e
                    )
                    raise SynapseError(400, "Domain specified not found.")

                logger.warn(
                    "Sending request failed to %s: %s %s : %s",
                    destination,
                    method,
                    url_bytes,
                    e
                )
                _print_ex(e)

                if retries_left:
                    yield sleep(2 ** (5 - retries_left))
                    retries_left -= 1
                else:
                    raise

        logger.info(
            "Received response %d %s for %s: %s %s",
            response.code,
            response.phrase,
            destination,
            method,
            url_bytes
        )

        if 200 <= response.code < 300:
            # We need to update the transactions table to say it was sent?
            pass
        else:
            # :'(
            # Update transactions table?
            raise CodeMessageException(
                response.code, response.phrase
            )

        defer.returnValue(response)
Exemple #12
0
    def _create_request(self,
                        destination,
                        method,
                        path_bytes,
                        param_bytes=b"",
                        query_bytes=b"",
                        producer=None,
                        headers_dict={},
                        retry_on_dns_fail=True,
                        on_send_callback=None):
        """ Creates and sends a request to the given url
        """
        headers_dict[b"User-Agent"] = [b"Synapse"]
        headers_dict[b"Host"] = [destination]

        logger.debug("Sending request to %s: %s %s;%s?%s", destination, method,
                     path_bytes, param_bytes, query_bytes)

        logger.debug("Types: %s", [
            type(destination),
            type(method),
            type(path_bytes),
            type(param_bytes),
            type(query_bytes)
        ])

        retries_left = 5

        # TODO: setup and pass in an ssl_context to enable TLS
        endpoint = self._getEndpoint(reactor, destination)

        while True:
            if on_send_callback:
                on_send_callback(destination, method, path_bytes, producer)

            try:
                response = yield self.agent.request(destination, endpoint,
                                                    method, path_bytes,
                                                    param_bytes, query_bytes,
                                                    Headers(headers_dict),
                                                    producer)

                logger.debug("Got response to %s", method)
                break
            except Exception as e:
                if not retry_on_dns_fail and isinstance(e, DNSLookupError):
                    logger.warn("DNS Lookup failed to %s with %s", destination,
                                e)
                    raise SynapseError(400, "Domain specified not found.")

                logger.exception("Got error in _create_request")
                _print_ex(e)

                if retries_left:
                    yield sleep(2**(5 - retries_left))
                    retries_left -= 1
                else:
                    raise

        if 200 <= response.code < 300:
            # We need to update the transactions table to say it was sent?
            pass
        else:
            # :'(
            # Update transactions table?
            logger.error("Got response %d %s", response.code, response.phrase)
            raise CodeMessageException(response.code, response.phrase)

        defer.returnValue(response)
Exemple #13
0
    def saml_response_to_user_attributes(
        self,
        saml_response: saml2.response.AuthnResponse,
        failures: int,
        client_redirect_url: str,
    ) -> dict:
        """Maps some text from a SAML response to attributes of a new user
        Args:
            saml_response: A SAML auth response object

            failures: How many times a call to this function with this
                saml_response has resulted in a failure

            client_redirect_url: where the client wants to redirect back to

        Returns:
            dict: A dict containing new user attributes. Possible keys:
                * mxid_localpart (str): Required. The localpart of the user's mxid
                * displayname (str): The displayname of the user
        """
        remote_user_id = self.get_remote_user_id(saml_response,
                                                 client_redirect_url)
        displayname = saml_response.ava.get(DISPLAYNAME_ATTRIBUTE_NAME,
                                            [None])[0]

        expire_old_sessions()

        # check the user's emails against our block list
        if EMAIL_ATTRIBUTE_NAME not in saml_response.ava:
            logger.warning(
                "SAML2 response lacks a '%s' attribute",
                EMAIL_ATTRIBUTE_NAME,
            )
            raise CodeMessageException(
                400, "'%s' not in SAML2 response" % (EMAIL_ATTRIBUTE_NAME, ))

        for email in saml_response.ava[EMAIL_ATTRIBUTE_NAME]:
            parts = email.rsplit("@", 1)
            if len(parts) != 2:
                logger.warning(
                    "Rejecting registration from remote user %s with unparsable email %s",
                    remote_user_id,
                    email,
                )
                raise CodeMessageException(403, "Forbidden")

            if parts[1].lower() in self._config.domain_block_list:
                logger.warning(
                    "Rejecting registration from remote user %s with blacklisted email %s",
                    remote_user_id,
                    email,
                )
                raise CodeMessageException(403, "Forbidden")

        # make up a cryptorandom session id
        session_id = "".join(
            self._random.choice(string.ascii_letters) for _ in range(16))

        now = int(time.time() * 1000)
        session = UsernameMappingSession(
            remote_user_id=remote_user_id,
            displayname=displayname,
            client_redirect_url=client_redirect_url,
            expiry_time_ms=now + MAPPING_SESSION_VALIDITY_PERIOD_MS,
        )

        username_mapping_sessions[session_id] = session
        logger.info("Recorded registration session id %s", session_id)

        # Redirect to the username picker
        e = RedirectException(b"/_matrix/saml2/pick_username/")
        e.cookies.append(b"%s=%s; path=/" % (
            SESSION_COOKIE_NAME,
            session_id.encode("ascii"),
        ))
        raise e