def set(self, name_id, entity_id, info, not_on_or_after=0): """ Stores session information in the cache. Assumes that the name_id is unique within the context of the Service Provider. :param name_id: The subject identifier, a NameID instance :param entity_id: The identifier of the entity_id/receiver of an assertion :param info: The session info, the assertion is part of this :param not_on_or_after: A time after which the assertion is not valid. """ info = dict(info) if 'name_id' in info and not isinstance(info['name_id'], six.string_types): # make friendly to (JSON) serialization info['name_id'] = code(name_id) cni = code(name_id) if cni not in self._db: self._db[cni] = {} self._db[cni][entity_id] = (not_on_or_after, info) if self._sync: try: self._db.sync() except AttributeError: pass
def store_assertion(self, assertion, to_sign): self.assertion[assertion.id] = (assertion, to_sign) key = sha1(code(assertion.subject.name_id)).hexdigest() try: self.authn[key].append(assertion.authn_statement) except KeyError: self.authn[key] = [assertion.authn_statement]
def login_action(session_info, user): """ Upon successful login in the IdP, store login info in the session and redirect back to the app that asked for authn. :param session_info: the SAML session info :type session_info: dict :param user: the authenticated user :type user: eduid_userdb.User """ current_app.logger.info("User {!r} logging in.".format(user)) session['_saml2_session_name_id'] = code(session_info['name_id']) session['eduPersonPrincipalName'] = user.eppn session['user_eppn'] = user.eppn loa = get_loa(current_app.config.get('AVAILABLE_LOA'), session_info) session['eduPersonAssurance'] = loa session.persist() # redirect the user to the view where he came from relay_state = request.form.get('RelayState', '/') current_app.logger.debug('Redirecting to the RelayState: ' + relay_state) response = redirect(location=relay_state) session.set_cookie(response) current_app.logger.info('Redirecting user {!r} to {!r}'.format( user, relay_state)) return response
def get_assertions_by_subject(self, name_id=None, session_index=None, requested_context=None): """ :param name_id: One of name_id or key can be used to get the authn statement :param session_index: If match against a session index should be done :param requested_context: Authn statements should match a specific authn context :return: """ result = [] key = sha1(code(name_id)).hexdigest() for item in self.assertion.find({"name_id_key": key}): assertion = from_dict(item["assertion"], ONTS, True) if session_index or requested_context: for statement in assertion.authn_statement: if session_index: if statement.session_index == session_index: result.append(assertion) break if requested_context: if context_match(requested_context, statement.authn_context): result.append(assertion) break else: result.append(assertion) return result
def get_authn_statements(self, name_id=None, key="", session_index=None, requested_context=None): """ :param name_id: One of name_id or key can be used to get the authn statement :param key: :param session_index: :param requested_context: :return: """ result = [] key = sha1(code(name_id)).hexdigest() try: statements = [from_dict(t, ONTS, True) for t in self.authn[key]] except KeyError: logger.info("Unknown subject %s" % name_id) return [] for statement in statements: if session_index: if statement.session_index != session_index: continue if requested_context: if not context_match(requested_context, statement.authn_context): continue result.append(statement) return result
def test_extend_person(self): session_info = { "name_id": nid, "issuer": IDP_OTHER, "not_on_or_after": in_a_while(minutes=15), "ava": { "eduPersonEntitlement": "Anka" } } self.population.add_information_about_person(session_info) issuers = self.population.issuers_of_info(nid) assert _eq(issuers, [IDP_ONE, IDP_OTHER]) subjects = [code(c) for c in self.population.subjects()] assert subjects == [cnid] # Are any of the sources gone stale stales = self.population.stale_sources_for_person(nid) assert stales == [] # are any of the possible sources not used or gone stale possible = [IDP_ONE, IDP_OTHER] stales = self.population.stale_sources_for_person(nid, possible) assert stales == [] (identity, stale) = self.population.get_identity(nid) assert stale == [] assert identity == {'mail': '*****@*****.**', 'givenName': 'Anders', 'surName': 'Andersson', "eduPersonEntitlement": "Anka"} info = self.population.get_info_from(nid, IDP_OTHER) assert _eq(list(info.keys()), ["not_on_or_after", "name_id", "ava"]) assert info["name_id"] == nid assert info["ava"] == {"eduPersonEntitlement": "Anka"}
def get_authn_statements(self, name_id, session_index=None, requested_context=None): """ :param name_id: :param session_index: :param requested_context: :return: """ result = [] _id = code(name_id) key = sha1(_id.encode()).hexdigest() try: statements = self.authn[key] except KeyError: logger.info("Unknown subject %s" % name_id) return [] for statement in statements: if session_index: if statement.session_index != session_index: continue if requested_context: if not context_match(requested_context, statement[0].authn_context): continue result.append(statement) return result
def get_authn_statements(self, name_id, session_index=None, requested_context=None): """ :param name_id: :param session_index: :param requested_context: :return: """ result = [] key = sha1(code(name_id)).hexdigest() try: statements = self.authn[key] except KeyError: logger.info("Unknown subject %s" % name_id) return [] for statement in statements: if session_index: if statement.session_index != session_index: continue if requested_context: if not context_match(requested_context, statement.authn_context): continue result.append(statement) return result
def set(self, name_id, entity_id, info, *args, **kwargs): try: name_id = info['name_id'] except KeyError: pass else: info = dict(info) info['name_id'] = code(name_id) return super(IdentityCache, self).set(name_id, entity_id, info, *args, **kwargs)
def entities(self, name_id): """ Returns all the entities of assertions for a subject, disregarding whether the assertion still is valid or not. :param name_id: The subject identifier, a NameID instance :return: A possibly empty list of entity identifiers """ cni = code(name_id) return list(self._db[cni].keys())
def _construct_identity(self, session_info): cni = code(session_info["name_id"]) identity = { "login": cni, "password": "", 'repoze.who.userid': cni, "user": session_info["ava"], } logger.debug("Identity: %s" % identity) return identity
def _set_name_id(session, name_id): """ Store SAML2 name id info. :param session: The current session object :param name_id: saml2.saml.NameID object :return: None :type name_id: saml2.saml.NameID """ session['_saml2_session_name_id'] = code(name_id)
def store_assertion(self, assertion, to_sign): name_id = assertion.subject.name_id nkey = sha1(code(name_id)).hexdigest() doc = { "name_id_key": nkey, "assertion_id": assertion.id, "assertion": to_dict(assertion, ONTS.values(), True), "to_sign": to_sign } _ = self.assertion.insert(doc)
def _reauthn(reason, session_info, user): current_app.logger.info("Reauthenticating user {!r} for {!r}.".format( user, reason)) session['_saml2_session_name_id'] = code(session_info['name_id']) session[reason] = int(time()) session.persist() # redirect the user to the view where he came from relay_state = request.form.get('RelayState', '/') current_app.logger.debug('Redirecting to the RelayState: ' + relay_state) return redirect(location=relay_state)
def delete(self, name_id): """ :param name_id: The subject identifier, a NameID instance """ del self._db[code(name_id)] if self._sync: try: self._db.sync() except AttributeError: pass
def add_information_about_person(self, session_info): """If there already are information from this source in the cache this function will overwrite that information""" name_id = session_info["name_id"] # make friendly to (JSON) serialization session_info['name_id'] = code(name_id) issuer = session_info["issuer"] del session_info["issuer"] self.cache.set(name_id, issuer, session_info, session_info["not_on_or_after"]) return name_id
def store_authn_statement(self, authn_statement, name_id): """ :param authn_statement: :param name_id: :return: """ logger.debug("store authn about: %s" % name_id) nkey = sha1(code(name_id)).hexdigest() logger.debug("Store authn_statement under key: %s" % nkey) try: self.authn[nkey].append(authn_statement) except KeyError: self.authn[nkey] = [authn_statement] return nkey
def login_action(session_info, user): logger.info("User {!r} logging in (eduPersonPrincipalName: {!r})".format( user.user_id, user.eppn)) session['_saml2_session_name_id'] = code(session_info['name_id']) session['eduPersonPrincipalName'] = user.eppn loa = get_loa(current_app.config.get('AVAILABLE_LOA'), session_info) session['eduPersonAssurance'] = loa session.persist() # redirect the user to the view where he came from relay_state = request.form.get('RelayState', '/') logger.debug('Redirecting to the RelayState: ' + relay_state) response = redirect(location=relay_state) session.set_cookie(response) return response
def get(self, name_id, entity_id, check_not_on_or_after=True): """ Get session information about a subject gotten from a specified IdP/AA. :param name_id: The subject identifier, a NameID instance :param entity_id: The identifier of the entity_id :param check_not_on_or_after: if True it will check if this subject is still valid or if it is too old. Otherwise it will not check this. True by default. :return: The session information """ cni = code(name_id) (timestamp, info) = self._db[cni][entity_id] if check_not_on_or_after and time_util.after(timestamp): raise ToOld("past %s" % timestamp) return info or None
def store_authn_statement(self, authn_statement, name_id): """ :param authn_statement: :param name_id: :return: """ logger.debug("store authn about: %s" % name_id) nkey = sha1(code(name_id)).hexdigest() logger.debug("Store authn_statement under key: %s" % nkey) _as = to_dict(authn_statement, ONTS.values(), True) try: self.authn[nkey].append(_as) except KeyError: self.authn[nkey] = [_as] return nkey
def active(self, name_id, entity_id): """ Returns the status of assertions from a specific entity_id. :param name_id: The ID of the subject :param entity_id: The entity ID of the entity_id of the assertion :return: True or False depending on if the assertion is still valid or not. """ try: cni = code(name_id) (timestamp, info) = self._db[cni][entity_id] except KeyError: return False if not info: return False else: return time_util.not_on_or_after(timestamp)
def test_add_person(self): session_info = { "name_id": nid, "issuer": IDP_ONE, "not_on_or_after": in_a_while(minutes=15), "ava": { "givenName": "Anders", "surName": "Andersson", "mail": "*****@*****.**" } } self.population.add_information_about_person(session_info) issuers = self.population.issuers_of_info(nid) assert list(issuers) == [IDP_ONE] subjects = [code(c) for c in self.population.subjects()] assert subjects == [cnid] # Are any of the sources gone stale stales = self.population.stale_sources_for_person(nid) assert stales == [] # are any of the possible sources not used or gone stale possible = [IDP_ONE, IDP_OTHER] stales = self.population.stale_sources_for_person(nid, possible) assert stales == [IDP_OTHER] (identity, stale) = self.population.get_identity(nid) assert stale == [] assert identity == { 'mail': '*****@*****.**', 'givenName': 'Anders', 'surName': 'Andersson' } info = self.population.get_info_from(nid, IDP_ONE) assert sorted(list(info.keys())) == sorted( ["not_on_or_after", "name_id", "ava"]) assert info["name_id"] == nid assert info["ava"] == { 'mail': '*****@*****.**', 'givenName': 'Anders', 'surName': 'Andersson' }
def set(self, name_id, entity_id, info, not_on_or_after=0): """ Stores session information in the cache. Assumes that the name_id is unique within the context of the Service Provider. :param name_id: The subject identifier, a NameID instance :param entity_id: The identifier of the entity_id/receiver of an assertion :param info: The session info, the assertion is part of this :param not_on_or_after: A time after which the assertion is not valid. """ cni = code(name_id) if cni not in self._db: self._db[cni] = {} self._db[cni][entity_id] = (not_on_or_after, info) if self._sync: try: self._db.sync() except AttributeError: pass
def update_user_session(session_info, user): """ Store login info in the session :param session_info: the SAML session info :param user: the authenticated user :type session_info: dict :type user: eduid_userdb.User :return: None :rtype: None """ session['_saml2_session_name_id'] = code(session_info['name_id']) session['eduPersonPrincipalName'] = user.eppn session['user_eppn'] = user.eppn # TODO: Remove when we have deployed and IdP that sets user_eppn session['user_is_logged_in'] = True loa = get_loa(current_app.config.get('AVAILABLE_LOA'), session_info) session['eduPersonAssurance'] = loa session['eduidIdPCredentialsUsed'] = get_saml_attribute(session_info, 'eduidIdPCredentialsUsed')
def test_add_another_person(self): session_info = { "name_id": nida, "issuer": IDP_ONE, "not_on_or_after": in_a_while(minutes=15), "ava": { "givenName": "Bertil", "surName": "Bertilsson", "mail": "*****@*****.**" } } self.population.add_information_about_person(session_info) issuers = self.population.issuers_of_info(nida) assert issuers == [IDP_ONE] subjects = [code(c) for c in self.population.subjects()] assert _eq(subjects, [cnid, cnida]) stales = self.population.stale_sources_for_person(nida) assert stales == [] # are any of the possible sources not used or gone stale possible = [IDP_ONE, IDP_OTHER] stales = self.population.stale_sources_for_person(nida, possible) assert stales == [IDP_OTHER] (identity, stale) = self.population.get_identity(nida) assert stale == [] assert identity == { "givenName": "Bertil", "surName": "Bertilsson", "mail": "*****@*****.**" } info = self.population.get_info_from(nida, IDP_ONE) assert info.keys() == ["not_on_or_after", "name_id", "ava"] assert info["name_id"] == nida assert info["ava"] == { "givenName": "Bertil", "surName": "Bertilsson", "mail": "*****@*****.**" }
def test_add_another_person(self): session_info = { "name_id": nida, "issuer": IDP_ONE, "not_on_or_after": in_a_while(minutes=15), "ava": { "givenName": "Bertil", "surName": "Bertilsson", "mail": "*****@*****.**" } } self.population.add_information_about_person(session_info) issuers = self.population.issuers_of_info(nida) assert list(issuers) == [IDP_ONE] subjects = [code(c) for c in self.population.subjects()] assert _eq(subjects, [cnid, cnida]) stales = self.population.stale_sources_for_person(nida) assert stales == [] # are any of the possible sources not used or gone stale possible = [IDP_ONE, IDP_OTHER] stales = self.population.stale_sources_for_person(nida, possible) assert stales == [IDP_OTHER] (identity, stale) = self.population.get_identity(nida) assert stale == [] assert identity == {"givenName": "Bertil", "surName": "Bertilsson", "mail": "*****@*****.**" } info = self.population.get_info_from(nida, IDP_ONE) assert sorted(list(info.keys())) == sorted(["not_on_or_after", "name_id", "ava"]) assert info["name_id"] == nida assert info["ava"] == {"givenName": "Bertil", "surName": "Bertilsson", "mail": "*****@*****.**" }
def get_identity(self, name_id, entities=None, check_not_on_or_after=True): """ Get all the identity information that has been received and are still valid about the subject. :param name_id: The subject identifier, a NameID instance :param entities: The identifiers of the entities whoes assertions are interesting. If the list is empty all entities are interesting. :return: A 2-tuple consisting of the identity information (a dictionary of attributes and values) and the list of entities whoes information has timed out. """ if not entities: try: cni = code(name_id) entities = self._db[cni].keys() except KeyError: return {}, [] res = {} oldees = [] for entity_id in entities: try: info = self.get(name_id, entity_id, check_not_on_or_after) except ToOld: oldees.append(entity_id) continue if not info: oldees.append(entity_id) continue for key, vals in info["ava"].items(): try: tmp = set(res[key]).union(set(vals)) res[key] = list(tmp) except KeyError: res[key] = vals return res, oldees
def update_user_session(session_info, user): """ Store login info in the session :param session_info: the SAML session info :param user: the authenticated user :type session_info: dict :type user: eduid_userdb.User :return: None :rtype: None """ session['_saml2_session_name_id'] = code(session_info['name_id']) session['eduPersonPrincipalName'] = user.eppn session[ 'user_eppn'] = user.eppn # TODO: Remove when we have deployed and IdP that sets user_eppn session['user_is_logged_in'] = True loa = get_loa(current_app.config.available_loa, session_info) session['eduPersonAssurance'] = loa session['eduidIdPCredentialsUsed'] = get_saml_attribute( session_info, 'eduidIdPCredentialsUsed')
def test_modify_person(self): session_info = { "name_id": nid, "issuer": IDP_ONE, "not_on_or_after": in_a_while(minutes=15), "ava": { "givenName": "Arne", "surName": "Andersson", "mail": "*****@*****.**" } } self.population.add_information_about_person(session_info) issuers = self.population.issuers_of_info(nid) assert _eq(issuers, [IDP_ONE, IDP_OTHER]) subjects = [code(c) for c in self.population.subjects()] assert _eq(subjects, [cnid, cnida]) # Are any of the sources gone stale stales = self.population.stale_sources_for_person(nid) assert stales == [] # are any of the possible sources not used or gone stale possible = [IDP_ONE, IDP_OTHER] stales = self.population.stale_sources_for_person(nid, possible) assert stales == [] (identity, stale) = self.population.get_identity(nid) assert stale == [] assert identity == { 'mail': '*****@*****.**', 'givenName': 'Arne', 'surName': 'Andersson', "eduPersonEntitlement": "Anka" } info = self.population.get_info_from(nid, IDP_OTHER) assert info.keys() == ["not_on_or_after", "name_id", "ava"] assert info["name_id"] == nid assert info["ava"] == {"eduPersonEntitlement": "Anka"}
def get(self, name_id, entity_id, check_not_on_or_after=True): """ Get session information about a subject gotten from a specified IdP/AA. :param name_id: The subject identifier, a NameID instance :param entity_id: The identifier of the entity_id :param check_not_on_or_after: if True it will check if this subject is still valid or if it is too old. Otherwise it will not check this. True by default. :return: The session information """ cni = code(name_id) try: (timestamp, info) = self._db[cni][entity_id] except KeyError: return None info = info.copy() if check_not_on_or_after and time_util.after(timestamp): raise TooOld("past %s" % str(timestamp)) if 'name_id' in info and isinstance(info['name_id'], six.string_types): info['name_id'] = decode(info['name_id']) return info or None
def do_logout( self, name_id, entity_ids, reason, expire, sign=None, expected_binding=None, sign_alg=None, digest_alg=None, **kwargs, ): """ :param name_id: Identifier of the Subject (a NameID instance) :param entity_ids: List of entity ids for the IdPs that have provided information concerning the subject :param reason: The reason for doing the logout :param expire: Try to logout before this time. :param sign: Whether to sign the request or not :param expected_binding: Specify the expected binding then not try it all :param kwargs: Extra key word arguments. :return: """ # check time if not not_on_or_after(expire): # I've run out of time # Do the local logout anyway self.local_logout(name_id) return 0, "504 Gateway Timeout", [], [] not_done = entity_ids[:] responses = {} bindings_slo_preferred = self.config.preferred_binding[ "single_logout_service"] for entity_id in entity_ids: logger.debug("Logout from '%s'", entity_id) bindings_slo_supported = self.metadata.single_logout_service( entity_id=entity_id, typ="idpsso") bindings_slo_preferred_and_supported = ( binding for binding in bindings_slo_preferred if binding in bindings_slo_supported) bindings_slo_choices = filter(lambda x: x, ( expected_binding, *bindings_slo_preferred_and_supported, *bindings_slo_supported, )) binding = next(bindings_slo_choices, None) if not binding: logger.info({ "message": "Entity does not support SLO", "entity": entity_id, }) continue service_info = bindings_slo_supported[binding] service_location = next(locations(service_info), None) if not service_location: logger.info({ "message": "Entity SLO service does not have a location", "entity": entity_id, "service_location": service_location, }) continue session_info = self.users.get_info_from(name_id, entity_id, False) session_index = session_info.get('session_index') session_indexes = [session_index] if session_index else None sign = sign if sign is not None else self.logout_requests_signed sign_post = sign and (binding == BINDING_HTTP_POST or binding == BINDING_SOAP) sign_redirect = sign and binding == BINDING_HTTP_REDIRECT log_report = { "message": "Invoking SLO on entity", "entity": entity_id, "binding": binding, "location": service_location, "session_indexes": session_indexes, "sign": sign, } logger.info(log_report) req_id, request = self.create_logout_request( service_location, entity_id, name_id=name_id, reason=reason, expire=expire, session_indexes=session_indexes, sign=sign_post, sign_alg=sign_alg, digest_alg=digest_alg, ) relay_state = self._relay_state(req_id) http_info = self.apply_binding( binding, str(request), service_location, relay_state, sign=sign_redirect, sigalg=sign_alg, ) if binding == BINDING_SOAP: response = self.send(**http_info) if response and response.status_code == 200: not_done.remove(entity_id) response_text = response.text log_report_response = { **log_report, "message": "Response from SLO service", "response_text": response_text, } logger.debug(log_report_response) res = self.parse_logout_request_response( response_text, binding) responses[entity_id] = res else: log_report_response = { **log_report, "message": "Bad status_code response from SLO service", "status_code": (response and response.status_code), } logger.info(log_report_response) else: self.state[req_id] = { "entity_id": entity_id, "operation": "SLO", "entity_ids": entity_ids, "name_id": code(name_id), "reason": reason, "not_on_or_after": expire, "sign": sign, } responses[entity_id] = (binding, http_info) not_done.remove(entity_id) if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids, )) return responses
def do_logout(self, name_id, entity_ids, reason, expire, sign=None, expected_binding=None): """ :param name_id: Identifier of the Subject (a NameID instance) :param entity_ids: List of entity ids for the IdPs that have provided information concerning the subject :param reason: The reason for doing the logout :param expire: Try to logout before this time. :param sign: Whether to sign the request or not :param expected_binding: Specify the expected binding then not try it all :return: """ # check time if not not_on_or_after(expire): # I've run out of time # Do the local logout anyway self.local_logout(name_id) return 0, "504 Gateway Timeout", [], [] not_done = entity_ids[:] responses = {} for entity_id in entity_ids: logger.debug("Logout from '%s'" % entity_id) # for all where I can use the SOAP binding, do those first for binding in [ BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT ]: if expected_binding and binding != expected_binding: continue try: srvs = self.metadata.single_logout_service( entity_id, binding, "idpsso") except: srvs = None if not srvs: logger.debug("No SLO '%s' service" % binding) continue destination = destinations(srvs)[0] logger.info("destination to provider: %s" % destination) req_id, request = self.create_logout_request(destination, entity_id, name_id=name_id, reason=reason, expire=expire) #to_sign = [] if binding.startswith("http://"): sign = True if sign is None: sign = self.logout_requests_signed if sign: srequest = self.sign(request) else: srequest = "%s" % request relay_state = self._relay_state(req_id) http_info = self.apply_binding(binding, srequest, destination, relay_state) if binding == BINDING_SOAP: response = self.send(**http_info) if response and response.status_code == 200: not_done.remove(entity_id) response = response.text logger.info("Response: %s" % response) res = self.parse_logout_request_response(response) responses[entity_id] = res else: logger.info("NOT OK response from %s" % destination) else: self.state[req_id] = { "entity_id": entity_id, "operation": "SLO", "entity_ids": entity_ids, "name_id": code(name_id), "reason": reason, "not_on_of_after": expire, "sign": sign } responses[entity_id] = (binding, http_info) not_done.remove(entity_id) # only try one binding break if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids, )) return responses
from saml2.saml import NAMEID_FORMAT_TRANSIENT, NameID from saml2.population import Population from saml2.time_util import in_a_while IDP_ONE = "urn:mace:example.com:saml:one:idp" IDP_OTHER = "urn:mace:example.com:saml:other:idp" nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="123456") nida = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="abcdef") cnid = code(nid) cnida = code(nida) def _eq(l1, l2): return set(l1) == set(l2) class TestPopulationMemoryBased(): def setup_class(self): self.population = Population() def test_add_person(self): session_info = { "name_id": nid, "issuer": IDP_ONE,
def construct_message(self): _cli = self.conv.entity _entity_id = self.req_args['entity_id'] _name_id = self.req_args['name_id'] sls_args = { 'entity_id': _entity_id, 'binding': self.binding, 'typ': 'idpsso'} try: srvs = _cli.metadata.single_logout_service(**sls_args) except: msg = "No SLO '{}' service".format(self.binding) raise UnknownBinding(msg) destination = destinations(srvs)[0] logger.info("destination to provider: %s", destination) self.conv.destination = destination try: session_info = _cli.users.get_info_from(_name_id, _entity_id, False) session_indexes = [session_info['session_index']] except KeyError: session_indexes = None try: expire = self.req_args['expire'] except KeyError: expire = in_a_while(minutes=5) req_id, request = _cli.create_logout_request( destination, _entity_id, name_id=_name_id, reason=self.req_args['reason'], expire=expire, session_indexes=session_indexes) self.conv.events.store(EV_REQUEST_ARGS, self.req_args, sender=self.__class__, sub='construct_message') self.conv.events.store(EV_PROTOCOL_REQUEST, request, sender=self.__class__, sub='construct_message') # to_sign = [] if self.binding.startswith("http://"): sign = True else: try: sign = self.req_args['sign'] except KeyError: sign = _cli.logout_requests_signed sigalg = None key = None if sign: if self.binding == BINDING_HTTP_REDIRECT: try: sigalg = self.req_args["sigalg"] except KeyError: sigalg = ds.sig_default try: key = self.req_args["key"] except KeyError: key = _cli.signkey srequest = str(request) else: srequest = _cli.sign(request) else: srequest = str(request) relay_state = _cli._relay_state(req_id) http_info = _cli.apply_binding(self.binding, srequest, destination, relay_state, sigalg=sigalg, key=key) if self.binding != BINDING_SOAP: _cli.state[req_id] = { "entity_id": _entity_id, "operation": "SLO", "name_id": code(_name_id), "reason": self.req_args['reason'], "not_on_of_after": expire, "sign": sign} self.conv.events.store(EV_HTTP_ARGS, http_info, sender=self.__class__, sub='construct_message') return http_info, req_id
def remove_authn_statements(self, name_id): logger.debug("remove authn about: %s" % name_id) key = sha1(code(name_id)).hexdigest() for item in self.assertion.find({"name_id_key": key}): self.assertion.remove(item["_id"])
def remove_authn_statements(self, name_id): logger.debug("remove authn about: %s" % name_id) nkey = sha1(code(name_id)).hexdigest() del self.authn[nkey]
def _set_subject_id(session, subject_id): session['_saml2_subject_id'] = code(subject_id)
def nid_eq(l1, l2): return _eq([code(c) for c in l1], [code(c) for c in l2])
from saml2.ident import code from saml2.saml import NAMEID_FORMAT_TRANSIENT, NameID from saml2.population import Population from saml2.time_util import in_a_while IDP_ONE = "urn:mace:example.com:saml:one:idp" IDP_OTHER = "urn:mace:example.com:saml:other:idp" nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="123456") nida = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="abcdef") cnid = code(nid) cnida = code(nida) def _eq(l1, l2): return set(l1) == set(l2) class TestPopulationMemoryBased(): def setup_class(self): self.population = Population() def test_add_person(self): session_info = { "name_id": nid, "issuer": IDP_ONE, "not_on_or_after": in_a_while(minutes=15), "ava": {
def do_logout(self, name_id, entity_ids, reason, expire, sign=None, expected_binding=None, **kwargs): """ :param name_id: Identifier of the Subject (a NameID instance) :param entity_ids: List of entity ids for the IdPs that have provided information concerning the subject :param reason: The reason for doing the logout :param expire: Try to logout before this time. :param sign: Whether to sign the request or not :param expected_binding: Specify the expected binding then not try it all :param kwargs: Extra key word arguments. :return: """ # check time if not not_on_or_after(expire): # I've run out of time # Do the local logout anyway self.local_logout(name_id) return 0, "504 Gateway Timeout", [], [] not_done = entity_ids[:] responses = {} for entity_id in entity_ids: logger.debug("Logout from '%s'", entity_id) # for all where I can use the SOAP binding, do those first for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]: if expected_binding and binding != expected_binding: continue try: srvs = self.metadata.single_logout_service(entity_id, binding, "idpsso") except: srvs = None if not srvs: logger.debug("No SLO '%s' service", binding) continue destination = destinations(srvs)[0] logger.info("destination to provider: %s", destination) try: session_info = self.users.get_info_from(name_id, entity_id, False) session_indexes = [session_info['session_index']] except KeyError: session_indexes = None req_id, request = self.create_logout_request( destination, entity_id, name_id=name_id, reason=reason, expire=expire, session_indexes=session_indexes) # to_sign = [] if binding.startswith("http://"): sign = True if sign is None: sign = self.logout_requests_signed sigalg = None key = None if sign: if binding == BINDING_HTTP_REDIRECT: sigalg = kwargs.get("sigalg", ds.sig_default) key = kwargs.get("key", self.signkey) srequest = str(request) else: srequest = self.sign(request) else: srequest = str(request) relay_state = self._relay_state(req_id) http_info = self.apply_binding(binding, srequest, destination, relay_state, sigalg=sigalg, key=key) if binding == BINDING_SOAP: response = self.send(**http_info) if response and response.status_code == 200: not_done.remove(entity_id) response = response.text logger.info("Response: %s", response) res = self.parse_logout_request_response(response, binding) responses[entity_id] = res else: logger.info("NOT OK response from %s", destination) else: self.state[req_id] = {"entity_id": entity_id, "operation": "SLO", "entity_ids": entity_ids, "name_id": code(name_id), "reason": reason, "not_on_of_after": expire, "sign": sign} responses[entity_id] = (binding, http_info) not_done.remove(entity_id) # only try one binding break if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids,)) return responses
def do_logout( self, name_id, entity_ids, reason, expire, sign=None, expected_binding=None, sign_alg=None, digest_alg=None, **kwargs, ): """ :param name_id: Identifier of the Subject (a NameID instance) :param entity_ids: List of entity ids for the IdPs that have provided information concerning the subject :param reason: The reason for doing the logout :param expire: Try to logout before this time. :param sign: Whether to sign the request or not :param expected_binding: Specify the expected binding then not try it all :param kwargs: Extra key word arguments. :return: """ # check time if not not_on_or_after(expire): # I've run out of time # Do the local logout anyway self.local_logout(name_id) return 0, "504 Gateway Timeout", [], [] not_done = entity_ids[:] responses = {} for entity_id in entity_ids: logger.debug("Logout from '%s'", entity_id) # for all where I can use the SOAP binding, do those first for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]: if expected_binding and binding != expected_binding: continue try: srvs = self.metadata.single_logout_service( entity_id, binding, "idpsso" ) except: srvs = None if not srvs: logger.debug("No SLO '%s' service", binding) continue destination = next(locations(srvs), None) logger.info("destination to provider: %s", destination) try: session_info = self.users.get_info_from( name_id, entity_id, False ) session_indexes = [session_info['session_index']] except KeyError: session_indexes = None sign_post = False if binding == BINDING_HTTP_REDIRECT else sign sign_redirect = False if binding == BINDING_HTTP_POST and sign else sign req_id, request = self.create_logout_request( destination, entity_id, name_id=name_id, reason=reason, expire=expire, session_indexes=session_indexes, sign=sign_post, sign_alg=sign_alg, digest_alg=digest_alg, ) relay_state = self._relay_state(req_id) http_info = self.apply_binding( binding, str(request), destination, relay_state, sign=sign_redirect, sigalg=sign_alg, ) if binding == BINDING_SOAP: response = self.send(**http_info) if response and response.status_code == 200: not_done.remove(entity_id) response = response.text logger.info("Response: %s", response) res = self.parse_logout_request_response(response, binding) responses[entity_id] = res else: logger.info("NOT OK response from %s", destination) else: self.state[req_id] = { "entity_id": entity_id, "operation": "SLO", "entity_ids": entity_ids, "name_id": code(name_id), "reason": reason, "not_on_or_after": expire, "sign": sign, } responses[entity_id] = (binding, http_info) not_done.remove(entity_id) # only try one binding break if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids,)) return responses
def _set_subject_id(session, subject_id): session["_saml2_subject_id"] = code(subject_id)