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, )
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)
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)
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, ))
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")
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)
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)
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)
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)
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)
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)
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