Exemple #1
0
    def test_consent_full_flow(self, internal_response, internal_request,
                               consent_verify_endpoint_regex, consent_registration_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)
        expected_ticket = "my_ticket"

        context = Context()
        state = State()
        context.state = state
        consent_module.save_state(internal_request, state)

        with responses.RequestsMock() as rsps:
            rsps.add(responses.GET, consent_verify_endpoint_regex, status=401)
            rsps.add(responses.GET, consent_registration_endpoint_regex, status=200,
                     body=expected_ticket)
            resp = consent_module.manage_consent(context, internal_response)

            self.assert_redirect(resp, expected_ticket)
            self.assert_registstration_req(rsps.calls[1].request,
                                           consent_config.CONSENT["sign_key"])

        with responses.RequestsMock() as rsps:
            # Now consent has been given, consent service returns 200 OK
            rsps.add(responses.GET, consent_verify_endpoint_regex, status=200,
                     body=json.dumps(FILTER))

            context = Context()
            context.state = state
            context, internal_response = consent_module._handle_consent_response(context)

        assert internal_response.get_attributes()["displayName"] == ["Test"]
        assert internal_response.get_attributes()["co"] == ["example"]
        assert "sn" not in internal_response.get_attributes()  # 'sn' should be filtered
Exemple #2
0
 def test_consent_registration(self):
     consent_config = SATOSAConfig(self.satosa_config)
     consent_module = ConsentModule(consent_config, lambda: None)
     jws = "A_JWS"
     responses.add(responses.GET, "{}/creq/{}".format(consent_config.CONSENT["rest_uri"], jws),
                   status=200, body="ticket")
     assert consent_module._consent_registration(jws) == "ticket"
Exemple #3
0
    def test_consent_not_given(self, internal_response, internal_request,
                               consent_verify_endpoint_regex, consent_registration_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)
        expected_ticket = "my_ticket"

        responses.add(responses.GET, consent_verify_endpoint_regex, status=401)
        responses.add(responses.GET, consent_registration_endpoint_regex, status=200,
                      body=expected_ticket)

        context = Context()
        state = State()
        context.state = state
        consent_module.save_state(internal_request, state)

        resp = consent_module.manage_consent(context, internal_response)

        self.assert_redirect(resp, expected_ticket)
        self.assert_registstration_req(responses.calls[1].request,
                                       consent_config.CONSENT["sign_key"])

        context = Context()
        context.state = state
        # Verify endpoint of consent service still gives 401 (no consent given)
        context, internal_response = consent_module._handle_consent_response(context)
        assert not internal_response.get_attributes()
Exemple #4
0
 def test_verify_consent(self):
     consent_config = SATOSAConfig(self.satosa_config)
     consent_module = ConsentModule(consent_config, lambda: None)
     consent_id = "1234"
     responses.add(responses.GET,
                   "{}/verify/{}".format(consent_config.CONSENT["rest_uri"], consent_id),
                   status=200, body=json.dumps(FILTER))
     assert consent_module._verify_consent(consent_id) == FILTER
Exemple #5
0
    def test_consent_registration_raises_on_http401(self):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, lambda: None)
        jws = "A_JWS"

        responses.add(responses.GET, "{}/creq/{}".format(consent_config.CONSENT["rest_uri"], jws),
                      status=401)
        with pytest.raises(AssertionError):
            consent_module._consent_registration(jws)
Exemple #6
0
    def test_verify_consent_false_on_http_400(self):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, lambda: None)

        consent_id = "1234"
        responses.add(responses.GET,
                      "{}/verify/{}".format(consent_config.CONSENT["rest_uri"], consent_id),
                      status=400)
        assert not consent_module._verify_consent(consent_id)
Exemple #7
0
    def test_verify_consent_false_on_http_400(self):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, lambda: None)

        consent_id = "1234"
        responses.add(responses.GET,
                      "{}/verify/{}".format(consent_config.CONSENT["rest_uri"],
                                            consent_id),
                      status=400)
        assert not consent_module._verify_consent(consent_id)
Exemple #8
0
    def test_consent_registration_raises_on_http401(self):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, lambda: None)
        jws = "A_JWS"

        responses.add(responses.GET,
                      "{}/creq/{}".format(consent_config.CONSENT["rest_uri"],
                                          jws),
                      status=401)
        with pytest.raises(AssertionError):
            consent_module._consent_registration(jws)
Exemple #9
0
    def test_consent_prev_given(self, internal_response, internal_request,
                                consent_verify_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)

        responses.add(responses.GET, consent_verify_endpoint_regex, status=200,
                      body=json.dumps(FILTER))

        context = Context()
        state = State()
        context.state = state
        consent_module.save_state(internal_request, state)
        context, internal_response = consent_module.manage_consent(context, internal_response)
        assert context
        assert "displayName" in internal_response.get_attributes()
Exemple #10
0
    def test_consent_handles_connection_error(self, internal_response, internal_request,
                                              consent_verify_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)

        state = State()
        context = Context()
        context.state = state
        consent_module.save_state(internal_request, state)
        with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps:
            rsps.add(responses.GET,
                     consent_verify_endpoint_regex,
                     body=requests.ConnectionError("No connection"))
            context, internal_response = consent_module.manage_consent(context, internal_response)

        assert context
        assert not internal_response.get_attributes()
Exemple #11
0
    def test_consent_prev_given(self, internal_response, internal_request,
                                consent_verify_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)

        responses.add(responses.GET,
                      consent_verify_endpoint_regex,
                      status=200,
                      body=json.dumps(FILTER))

        context = Context()
        state = State()
        context.state = state
        consent_module.save_state(internal_request, state)
        context, internal_response = consent_module.manage_consent(
            context, internal_response)
        assert context
        assert "displayName" in internal_response.get_attributes()
Exemple #12
0
    def test_consent_handles_connection_error(self, internal_response,
                                              internal_request,
                                              consent_verify_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)

        state = State()
        context = Context()
        context.state = state
        consent_module.save_state(internal_request, state)
        with responses.RequestsMock(
                assert_all_requests_are_fired=True) as rsps:
            rsps.add(responses.GET,
                     consent_verify_endpoint_regex,
                     body=requests.ConnectionError("No connection"))
            context, internal_response = consent_module.manage_consent(
                context, internal_response)

        assert context
        assert not internal_response.get_attributes()
Exemple #13
0
    def test_consent_not_given(self, internal_response, internal_request,
                               consent_verify_endpoint_regex,
                               consent_registration_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)
        expected_ticket = "my_ticket"

        responses.add(responses.GET, consent_verify_endpoint_regex, status=401)
        responses.add(responses.GET,
                      consent_registration_endpoint_regex,
                      status=200,
                      body=expected_ticket)

        context = Context()
        state = State()
        context.state = state
        consent_module.save_state(internal_request, state)

        resp = consent_module.manage_consent(context, internal_response)

        self.assert_redirect(resp, expected_ticket)
        self.assert_registstration_req(responses.calls[1].request,
                                       consent_config.CONSENT["sign_key"])

        context = Context()
        context.state = state
        # Verify endpoint of consent service still gives 401 (no consent given)
        context, internal_response = consent_module._handle_consent_response(
            context)
        assert not internal_response.get_attributes()
Exemple #14
0
    def __init__(self, config):
        """
        Creates a satosa proxy base

        :type config: satosa.satosa_config.SATOSAConfig

        :param config: satosa proxy config
        """
        if config is None:
            raise ValueError("Missing configuration")

        self.config = config
        LOGGER.info("Loading backend modules...")
        backends = load_backends(self.config, self._auth_resp_callback_func,
                                 self.config.INTERNAL_ATTRIBUTES)
        LOGGER.info("Loading frontend modules...")
        frontends = load_frontends(self.config, self._auth_req_callback_func,
                                   self.config.INTERNAL_ATTRIBUTES)
        self.consent_module = ConsentModule(config,
                                            self._consent_resp_callback_func)
        self.account_linking_module = AccountLinkingModule(
            config, self._account_linking_callback_func)
        # TODO register consent_module endpoints to module_router. Just add to backend list?
        if self.consent_module.enabled:
            backends["consent"] = self.consent_module
        if self.account_linking_module.enabled:
            backends["account_linking"] = self.account_linking_module

        LOGGER.info("Loading micro services...")
        self.request_micro_services = None
        self.response_micro_services = None
        if "MICRO_SERVICES" in self.config:
            self.request_micro_services, self.response_micro_services = load_micro_services(
                self.config.PLUGIN_PATH, self.config.MICRO_SERVICES,
                self.config.INTERNAL_ATTRIBUTES)
        self.module_router = ModuleRouter(frontends, backends)
Exemple #15
0
    def __init__(self, config):
        """
        Creates a satosa proxy base

        :type config: satosa.satosa_config.SATOSAConfig

        :param config: satosa proxy config
        """
        if config is None:
            raise ValueError("Missing configuration")

        self.config = config
        LOGGER.info("Loading backend modules...")
        backends = load_backends(self.config, self._auth_resp_callback_func,
                                 self.config.INTERNAL_ATTRIBUTES)
        LOGGER.info("Loading frontend modules...")
        frontends = load_frontends(self.config, self._auth_req_callback_func,
                                   self.config.INTERNAL_ATTRIBUTES)
        self.consent_module = ConsentModule(config, self._consent_resp_callback_func)
        self.account_linking_module = AccountLinkingModule(config,
                                                           self._account_linking_callback_func)
        # TODO register consent_module endpoints to module_router. Just add to backend list?
        if self.consent_module.enabled:
            backends["consent"] = self.consent_module
        if self.account_linking_module.enabled:
            backends["account_linking"] = self.account_linking_module

        LOGGER.info("Loading micro services...")
        self.request_micro_services = None
        self.response_micro_services = None
        if "MICRO_SERVICES" in self.config:
            self.request_micro_services, self.response_micro_services = load_micro_services(
                self.config.PLUGIN_PATH,
                self.config.MICRO_SERVICES,
                self.config.INTERNAL_ATTRIBUTES)
        self.module_router = ModuleRouter(frontends, backends)
Exemple #16
0
    def test_consent_full_flow(self, internal_response, internal_request,
                               consent_verify_endpoint_regex,
                               consent_registration_endpoint_regex):
        consent_config = SATOSAConfig(self.satosa_config)
        consent_module = ConsentModule(consent_config, identity_callback)
        expected_ticket = "my_ticket"

        context = Context()
        state = State()
        context.state = state
        consent_module.save_state(internal_request, state)

        with responses.RequestsMock() as rsps:
            rsps.add(responses.GET, consent_verify_endpoint_regex, status=401)
            rsps.add(responses.GET,
                     consent_registration_endpoint_regex,
                     status=200,
                     body=expected_ticket)
            resp = consent_module.manage_consent(context, internal_response)

            self.assert_redirect(resp, expected_ticket)
            self.assert_registstration_req(rsps.calls[1].request,
                                           consent_config.CONSENT["sign_key"])

        with responses.RequestsMock() as rsps:
            # Now consent has been given, consent service returns 200 OK
            rsps.add(responses.GET,
                     consent_verify_endpoint_regex,
                     status=200,
                     body=json.dumps(FILTER))

            context = Context()
            context.state = state
            context, internal_response = consent_module._handle_consent_response(
                context)

        assert internal_response.get_attributes()["displayName"] == ["Test"]
        assert internal_response.get_attributes()["co"] == ["example"]
        assert "sn" not in internal_response.get_attributes(
        )  # 'sn' should be filtered
Exemple #17
0
class SATOSABase(object):
    """
    Base class for a satosa proxy server.
    Does not contain any server parts.
    """

    STATE_KEY = "SATOSA_REQUESTOR"

    def __init__(self, config):
        """
        Creates a satosa proxy base

        :type config: satosa.satosa_config.SATOSAConfig

        :param config: satosa proxy config
        """
        if config is None:
            raise ValueError("Missing configuration")

        self.config = config
        LOGGER.info("Loading backend modules...")
        backends = load_backends(self.config, self._auth_resp_callback_func,
                                 self.config.INTERNAL_ATTRIBUTES)
        LOGGER.info("Loading frontend modules...")
        frontends = load_frontends(self.config, self._auth_req_callback_func,
                                   self.config.INTERNAL_ATTRIBUTES)
        self.consent_module = ConsentModule(config, self._consent_resp_callback_func)
        self.account_linking_module = AccountLinkingModule(config,
                                                           self._account_linking_callback_func)
        # TODO register consent_module endpoints to module_router. Just add to backend list?
        if self.consent_module.enabled:
            backends["consent"] = self.consent_module
        if self.account_linking_module.enabled:
            backends["account_linking"] = self.account_linking_module

        LOGGER.info("Loading micro services...")
        self.request_micro_services = None
        self.response_micro_services = None
        if "MICRO_SERVICES" in self.config:
            self.request_micro_services, self.response_micro_services = load_micro_services(
                self.config.PLUGIN_PATH,
                self.config.MICRO_SERVICES,
                self.config.INTERNAL_ATTRIBUTES)
        self.module_router = ModuleRouter(frontends, backends)

    def _auth_req_callback_func(self, context, internal_request):
        """
        This function is called by a frontend module when an authorization request has been
        processed.

        :type context: satosa.context.Context
        :type internal_request: satosa.internal_data.InternalRequest
        :rtype: satosa.response.Response

        :param context: The request context
        :param internal_request: request processed by the frontend

        :return: response
        """
        state = context.state
        state.add(SATOSABase.STATE_KEY, internal_request.requestor)
        satosa_logging(LOGGER, logging.INFO,
                       "Requesting provider: {}".format(internal_request.requestor), state)
        context.request = None
        backend = self.module_router.backend_routing(context)
        self.consent_module.save_state(internal_request, state)
        UserIdHasher.save_state(internal_request, state)
        if self.request_micro_services:
            internal_request = self.request_micro_services.process_service_queue(context,
                                                                                 internal_request)
        return backend.start_auth(context, internal_request)

    def _auth_resp_callback_func(self, context, internal_response):
        """
        This function is called by a backend module when the authorization is complete.

        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype: satosa.response.Response

        :param context: The request context
        :param internal_response: The authentication response
        :return: response
        """

        context.request = None
        internal_response.to_requestor = context.state.get(SATOSABase.STATE_KEY)
        user_id_attr = self.config.INTERNAL_ATTRIBUTES.get("user_id_from_attr", [])
        if user_id_attr:
            internal_response.set_user_id_from_attr(user_id_attr)
        # Hash the user id
        user_id = UserIdHasher.hash_data(self.config.USER_ID_HASH_SALT,
                                         internal_response.get_user_id())
        internal_response.set_user_id(user_id)

        if self.response_micro_services:
            internal_response = \
                self.response_micro_services.process_service_queue(context, internal_response)
        return self.account_linking_module.manage_al(context, internal_response)

    def _account_linking_callback_func(self, context, internal_response):
        """
        This function is called by the account linking module when the linking step is done

        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype: satosa.response.Response

        :param context: The response context
        :param internal_response: The authentication response
        :return: response
        """
        user_id = UserIdHasher.hash_id(self.config.USER_ID_HASH_SALT,
                                       internal_response.get_user_id(),
                                       internal_response.to_requestor,
                                       context.state)
        internal_response.set_user_id(user_id)
        internal_response.set_user_id_hash_type(UserIdHasher.hash_type(context.state))
        user_id_to_attr = self.config.INTERNAL_ATTRIBUTES.get("user_id_to_attr", None)
        if user_id_to_attr:
            attributes = internal_response.get_attributes()
            attributes[user_id_to_attr] = internal_response.get_user_id()
            internal_response.add_attributes(attributes)

        # Hash all attributes specified in INTERNAL_ATTRIBUTES["hash]
        hash_attributes = self.config.INTERNAL_ATTRIBUTES.get("hash", [])
        internal_attributes = internal_response.get_attributes()
        for attribute in hash_attributes:
            internal_attributes[attribute] = UserIdHasher.hash_data(self.config.USER_ID_HASH_SALT,
                                                                    internal_attributes[attribute])

        return self.consent_module.manage_consent(context, internal_response)

    def _consent_resp_callback_func(self, context, internal_response):
        """
        This function is called by the consent module when the consent step is done

        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype: satosa.response.Response

        :param context: The response context
        :param internal_response: The authentication response
        :return: response
        """
        context.request = None
        context.state.set_delete_state()
        frontend = self.module_router.frontend_routing(context)
        return frontend.handle_authn_response(context, internal_response)

    def _handle_satosa_authentication_error(self, error):
        """
        Sends a response to the requestor about the error

        :type error: satosa.exception.SATOSAAuthenticationError
        :rtype: satosa.response.Response

        :param error: The exception
        :return: response
        """
        context = Context()
        context.state = error.state
        frontend = self.module_router.frontend_routing(context)
        return frontend.handle_backend_error(error)

    def _run_bound_endpoint(self, context, spec):
        """

        :type context: satosa.context.Context
        :type spec: ((satosa.context.Context, Any) -> satosa.response.Response, Any) |
        (satosa.context.Context) -> satosa.response.Response

        :param context: The request context
        :param spec: bound endpoint function
        :return: response
        """
        try:
            if isinstance(spec, tuple):
                return spec[0](context, *spec[1:])
            else:
                return spec(context)
        except SATOSAAuthenticationError as error:
            error.error_id = uuid4().urn
            msg = "ERROR_ID [{err_id}]\nSTATE:\n{state}".format(err_id=error.error_id,
                                                                state=json.dumps(
                                                                    error.state.state_dict,
                                                                    indent=4))
            satosa_logging(LOGGER, logging.ERROR, msg, error.state, exc_info=True)
            return self._handle_satosa_authentication_error(error)

    def _load_state(self, context):
        """
        Load a state to the context

        :type context: satosa.context.Context
        :param context: Session context
        """
        try:
            state = cookie_to_state(context.cookie, self.config.COOKIE_STATE_NAME,
                                    self.config.STATE_ENCRYPTION_KEY)
        except SATOSAStateError:
            state = State()
        context.state = state

    def _save_state(self, resp, context):
        """
        Saves a state from context to cookie

        :type resp: satosa.response.Response
        :type context: satosa.context.Context

        :param resp: The response
        :param context: Session context
        """
        if context.state.should_delete():
            # Save empty state with a max age of 0
            cookie = state_to_cookie(State(),
                                     self.config.COOKIE_STATE_NAME,
                                     "/",
                                     self.config.STATE_ENCRYPTION_KEY,
                                     0)
        else:
            cookie = state_to_cookie(context.state,
                                     self.config.COOKIE_STATE_NAME,
                                     "/",
                                     self.config.STATE_ENCRYPTION_KEY)

        if isinstance(resp, Response):
            resp.add_cookie(cookie)
        else:
            try:
                resp.headers.append(tuple(cookie.output().split(": ", 1)))
            except:
                satosa_logging(LOGGER, logging.WARN,
                               "can't add cookie to response '%s'" % resp.__class__, context.state)
                pass

    def run(self, context):
        """
        Runs the satosa proxy with the given context.

        :type context: satosa.context.Context
        :rtype: satosa.response.Response

        :param context: The request context
        :return: response
        """
        try:
            self._load_state(context)
            spec = self.module_router.endpoint_routing(context)
            resp = self._run_bound_endpoint(context, spec)
            self._save_state(resp, context)
        except SATOSAError:
            satosa_logging(LOGGER, logging.ERROR, "Uncaught SATOSA error", context.state,
                           exc_info=True)
            raise
        except Exception as err:
            satosa_logging(LOGGER, logging.ERROR, "Uncaught exception", context.state,
                           exc_info=True)
            raise SATOSAUnknownError("Unknown error") from err
        return resp
Exemple #18
0
class SATOSABase(object):
    """
    Base class for a satosa proxy server.
    Does not contain any server parts.
    """

    STATE_KEY = "SATOSA_REQUESTOR"

    def __init__(self, config):
        """
        Creates a satosa proxy base

        :type config: satosa.satosa_config.SATOSAConfig

        :param config: satosa proxy config
        """
        if config is None:
            raise ValueError("Missing configuration")

        self.config = config
        LOGGER.info("Loading backend modules...")
        backends = load_backends(self.config, self._auth_resp_callback_func,
                                 self.config.INTERNAL_ATTRIBUTES)
        LOGGER.info("Loading frontend modules...")
        frontends = load_frontends(self.config, self._auth_req_callback_func,
                                   self.config.INTERNAL_ATTRIBUTES)
        self.consent_module = ConsentModule(config,
                                            self._consent_resp_callback_func)
        self.account_linking_module = AccountLinkingModule(
            config, self._account_linking_callback_func)
        # TODO register consent_module endpoints to module_router. Just add to backend list?
        if self.consent_module.enabled:
            backends["consent"] = self.consent_module
        if self.account_linking_module.enabled:
            backends["account_linking"] = self.account_linking_module

        LOGGER.info("Loading micro services...")
        self.request_micro_services = None
        self.response_micro_services = None
        if "MICRO_SERVICES" in self.config:
            self.request_micro_services, self.response_micro_services = load_micro_services(
                self.config.PLUGIN_PATH, self.config.MICRO_SERVICES,
                self.config.INTERNAL_ATTRIBUTES)
        self.module_router = ModuleRouter(frontends, backends)

    def _auth_req_callback_func(self, context, internal_request):
        """
        This function is called by a frontend module when an authorization request has been
        processed.

        :type context: satosa.context.Context
        :type internal_request: satosa.internal_data.InternalRequest
        :rtype: satosa.response.Response

        :param context: The request context
        :param internal_request: request processed by the frontend

        :return: response
        """
        state = context.state
        state.add(SATOSABase.STATE_KEY, internal_request.requestor)
        satosa_logging(
            LOGGER, logging.INFO,
            "Requesting provider: {}".format(internal_request.requestor),
            state)
        context.request = None
        backend = self.module_router.backend_routing(context)
        self.consent_module.save_state(internal_request, state)
        UserIdHasher.save_state(internal_request, state)
        if self.request_micro_services:
            internal_request = self.request_micro_services.process_service_queue(
                context, internal_request)
        return backend.start_auth(context, internal_request)

    def _auth_resp_callback_func(self, context, internal_response):
        """
        This function is called by a backend module when the authorization is complete.

        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype: satosa.response.Response

        :param context: The request context
        :param internal_response: The authentication response
        :return: response
        """

        context.request = None
        internal_response.to_requestor = context.state.get(
            SATOSABase.STATE_KEY)
        user_id_attr = self.config.INTERNAL_ATTRIBUTES.get(
            "user_id_from_attr", [])
        if user_id_attr:
            internal_response.set_user_id_from_attr(user_id_attr)
        # Hash the user id
        user_id = UserIdHasher.hash_data(self.config.USER_ID_HASH_SALT,
                                         internal_response.get_user_id())
        internal_response.set_user_id(user_id)

        if self.response_micro_services:
            internal_response = \
                self.response_micro_services.process_service_queue(context, internal_response)
        return self.account_linking_module.manage_al(context,
                                                     internal_response)

    def _account_linking_callback_func(self, context, internal_response):
        """
        This function is called by the account linking module when the linking step is done

        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype: satosa.response.Response

        :param context: The response context
        :param internal_response: The authentication response
        :return: response
        """
        user_id = UserIdHasher.hash_id(self.config.USER_ID_HASH_SALT,
                                       internal_response.get_user_id(),
                                       internal_response.to_requestor,
                                       context.state)
        internal_response.set_user_id(user_id)
        internal_response.set_user_id_hash_type(
            UserIdHasher.hash_type(context.state))
        user_id_to_attr = self.config.INTERNAL_ATTRIBUTES.get(
            "user_id_to_attr", None)
        if user_id_to_attr:
            attributes = internal_response.get_attributes()
            attributes[user_id_to_attr] = internal_response.get_user_id()
            internal_response.add_attributes(attributes)

        # Hash all attributes specified in INTERNAL_ATTRIBUTES["hash]
        hash_attributes = self.config.INTERNAL_ATTRIBUTES.get("hash", [])
        internal_attributes = internal_response.get_attributes()
        for attribute in hash_attributes:
            internal_attributes[attribute] = UserIdHasher.hash_data(
                self.config.USER_ID_HASH_SALT, internal_attributes[attribute])

        return self.consent_module.manage_consent(context, internal_response)

    def _consent_resp_callback_func(self, context, internal_response):
        """
        This function is called by the consent module when the consent step is done

        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype: satosa.response.Response

        :param context: The response context
        :param internal_response: The authentication response
        :return: response
        """
        context.request = None
        context.state.set_delete_state()
        frontend = self.module_router.frontend_routing(context)
        return frontend.handle_authn_response(context, internal_response)

    def _handle_satosa_authentication_error(self, error):
        """
        Sends a response to the requestor about the error

        :type error: satosa.exception.SATOSAAuthenticationError
        :rtype: satosa.response.Response

        :param error: The exception
        :return: response
        """
        context = Context()
        context.state = error.state
        frontend = self.module_router.frontend_routing(context)
        return frontend.handle_backend_error(error)

    def _run_bound_endpoint(self, context, spec):
        """

        :type context: satosa.context.Context
        :type spec: ((satosa.context.Context, Any) -> satosa.response.Response, Any) |
        (satosa.context.Context) -> satosa.response.Response

        :param context: The request context
        :param spec: bound endpoint function
        :return: response
        """
        try:
            if isinstance(spec, tuple):
                return spec[0](context, *spec[1:])
            else:
                return spec(context)
        except SATOSAAuthenticationError as error:
            error.error_id = uuid4().urn
            msg = "ERROR_ID [{err_id}]\nSTATE:\n{state}".format(
                err_id=error.error_id,
                state=json.dumps(error.state.state_dict, indent=4))
            satosa_logging(LOGGER,
                           logging.ERROR,
                           msg,
                           error.state,
                           exc_info=True)
            return self._handle_satosa_authentication_error(error)

    def _load_state(self, context):
        """
        Load a state to the context

        :type context: satosa.context.Context
        :param context: Session context
        """
        try:
            state = cookie_to_state(context.cookie,
                                    self.config.COOKIE_STATE_NAME,
                                    self.config.STATE_ENCRYPTION_KEY)
        except SATOSAStateError:
            state = State()
        context.state = state

    def _save_state(self, resp, context):
        """
        Saves a state from context to cookie

        :type resp: satosa.response.Response
        :type context: satosa.context.Context

        :param resp: The response
        :param context: Session context
        """
        if context.state.should_delete():
            # Save empty state with a max age of 0
            cookie = state_to_cookie(State(), self.config.COOKIE_STATE_NAME,
                                     "/", self.config.STATE_ENCRYPTION_KEY, 0)
        else:
            cookie = state_to_cookie(context.state,
                                     self.config.COOKIE_STATE_NAME, "/",
                                     self.config.STATE_ENCRYPTION_KEY)

        if isinstance(resp, Response):
            resp.add_cookie(cookie)
        else:
            try:
                resp.headers.append(tuple(cookie.output().split(": ", 1)))
            except:
                satosa_logging(
                    LOGGER, logging.WARN,
                    "can't add cookie to response '%s'" % resp.__class__,
                    context.state)
                pass

    def run(self, context):
        """
        Runs the satosa proxy with the given context.

        :type context: satosa.context.Context
        :rtype: satosa.response.Response

        :param context: The request context
        :return: response
        """
        try:
            self._load_state(context)
            spec = self.module_router.endpoint_routing(context)
            resp = self._run_bound_endpoint(context, spec)
            self._save_state(resp, context)
        except SATOSAError:
            satosa_logging(LOGGER,
                           logging.ERROR,
                           "Uncaught SATOSA error",
                           context.state,
                           exc_info=True)
            raise
        except Exception as err:
            satosa_logging(LOGGER,
                           logging.ERROR,
                           "Uncaught exception",
                           context.state,
                           exc_info=True)
            raise SATOSAUnknownError("Unknown error") from err
        return resp
Exemple #19
0
 def test_disabled_consent(self, internal_response):
     self.consent_config["enable"] = False
     consent_config = SATOSAConfig(self.satosa_config)
     consent_module = ConsentModule(consent_config, identity_callback)
     assert not consent_module.enabled