def test_switch_1(): mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) mds.imp(METADATACONF["5"]) assert len(mds.keys()) > 160 idps = mds.with_descriptor("idpsso") print(idps.keys()) idpsso = mds.single_sign_on_service( 'https://aai-demo-idp.switch.ch/idp/shibboleth') assert len(idpsso) == 1 print(idpsso) assert destinations(idpsso) == [ 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO' ] assert len(idps) > 30 aas = mds.with_descriptor("attribute_authority") print(aas.keys()) aad = aas['https://aai-demo-idp.switch.ch/idp/shibboleth'] print(aad.keys()) assert len(aad["attribute_authority_descriptor"]) == 1 assert len(aad["idpsso_descriptor"]) == 1 sps = mds.with_descriptor("spsso") dual = [eid for eid, ent in idps.items() if eid in sps] print(len(dual)) assert len(dual) == 0
def test_switch_1(): mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) mds.imp(METADATACONF["5"]) assert len(mds.keys()) > 160 idps = mds.with_descriptor("idpsso") print(idps.keys()) idpsso = mds.single_sign_on_service( 'https://aai-demo-idp.switch.ch/idp/shibboleth') assert len(idpsso) == 1 print(idpsso) assert destinations(idpsso) == [ 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO'] assert len(idps) > 30 aas = mds.with_descriptor("attribute_authority") print(aas.keys()) aad = aas['https://aai-demo-idp.switch.ch/idp/shibboleth'] print(aad.keys()) assert len(aad["attribute_authority_descriptor"]) == 1 assert len(aad["idpsso_descriptor"]) == 1 sps = mds.with_descriptor("spsso") dual = [eid for eid, ent in idps.items() if eid in sps] print(len(dual)) assert len(dual) == 0
def do_assertion_id_request(self, assertion_ids, entity_id, consent=None, extensions=None, sign=False): srvs = self.metadata.assertion_id_request_service( entity_id, BINDING_SOAP) if not srvs: raise NoServiceDefined("%s: %s" % (entity_id, "assertion_id_request_service")) if isinstance(assertion_ids, six.string_types): assertion_ids = [assertion_ids] _id_refs = [AssertionIDRef(_id) for _id in assertion_ids] for destination in destinations(srvs): res = self._use_soap(destination, "assertion_id_request", assertion_id_refs=_id_refs, consent=consent, extensions=extensions, sign=sign) if res: return res return None
def test_swami_1(): UMU_IDP = 'https://idp.umu.se/saml2/idp/metadata.php' mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) mds.imp(METADATACONF["1"]) assert len(mds) == 1 # One source idps = mds.with_descriptor("idpsso") assert idps.keys() idpsso = mds.single_sign_on_service(UMU_IDP) assert len(idpsso) == 1 assert destinations(idpsso) == [ 'https://idp.umu.se/saml2/idp/SSOService.php'] _name = name(mds[UMU_IDP]) assert _name == u'Umeå University (SAML2)' certs = mds.certs(UMU_IDP, "idpsso", "signing") assert len(certs) == 1 sps = mds.with_descriptor("spsso") assert len(sps) == 108 wants = mds.attribute_requirement('https://connect8.sunet.se/shibboleth') lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]] assert _eq(lnamn, ['eduPersonPrincipalName', 'mail', 'givenName', 'sn', 'eduPersonScopedAffiliation']) wants = mds.attribute_requirement('https://beta.lobber.se/shibboleth') assert wants["required"] == [] lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]] assert _eq(lnamn, ['eduPersonPrincipalName', 'mail', 'givenName', 'sn', 'eduPersonScopedAffiliation', 'eduPersonEntitlement'])
def do_authz_decision_query(self, entity_id, action, subject_id, nameid_format, evidence=None, resource=None, sp_name_qualifier=None, name_qualifier=None, consent=None, extensions=None, sign=False): subject = saml.Subject( name_id=saml.NameID(text=subject_id, format=nameid_format, sp_name_qualifier=sp_name_qualifier, name_qualifier=name_qualifier)) srvs = self.metadata.authz_service(entity_id, BINDING_SOAP) for dest in destinations(srvs): resp = self._use_soap(dest, "authz_decision_query", action=action, evidence=evidence, resource=resource, subject=subject) if resp: return resp return None
def test_metadata(): conf = config.Config() conf.load_file("idp_conf_mdb") umu_idp = 'https://idp.umu.se/saml2/idp/metadata.php' # Set up a Metadata store mds = MetadataStore(ATTRCONV, conf, disable_ssl_certificate_validation=True) # Import metadata from local file. mds.imp([{"class": "saml2_tophat.mdstore.MetaDataFile", "metadata": [(full_path("swamid-2.0.xml"), )]}]) assert len(mds) == 1 # One source try: export_mdstore_to_mongo_db(mds, "metadata", "test") except ConnectionFailure: pass else: mdmdb = MetadataMDB(ATTRCONV, "metadata", "test") # replace all metadata instances with this one mds.metadata = {"mongo_db": mdmdb} idps = mds.with_descriptor("idpsso") assert idps.keys() idpsso = mds.single_sign_on_service(umu_idp) assert len(idpsso) == 1 assert destinations(idpsso) == [ 'https://idp.umu.se/saml2/idp/SSOService.php'] _name = name(mds[umu_idp]) assert _name == u'Ume\xe5 University' certs = mds.certs(umu_idp, "idpsso", "signing") assert len(certs) == 1 sps = mds.with_descriptor("spsso") assert len(sps) == 417 wants = mds.attribute_requirement('https://connect.sunet.se/shibboleth') assert wants["optional"] == [] lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["required"]] assert _eq(lnamn, ['eduPersonPrincipalName', 'mail', 'givenName', 'sn', 'eduPersonScopedAffiliation', 'eduPersonAffiliation']) wants = mds.attribute_requirement( "https://gidp.geant.net/sp/module.php/saml/sp/metadata.php/default-sp") # Optional lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]] assert _eq(lnamn, ['displayName', 'commonName', 'schacHomeOrganization', 'eduPersonAffiliation', 'schacHomeOrganizationType']) # Required lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["required"]] assert _eq(lnamn, ['eduPersonTargetedID', 'mail', 'eduPersonScopedAffiliation'])
def do_authn_query(self, entity_id, consent=None, extensions=None, sign=False): srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP) for destination in destinations(srvs): resp = self._use_soap(destination, "authn_query", consent=consent, extensions=extensions, sign=sign) if resp: return resp return None
def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT): if entityid: # verify that it's in the metadata srvs = self.metadata.single_sign_on_service(entityid, binding) if srvs: return destinations(srvs)[0] else: logger.info("_sso_location: %s, %s", entityid, binding) raise IdpUnspecified("No IdP to send to given the premises") # get the idp location from the metadata. If there is more than one # IdP in the configuration raise exception eids = self.metadata.with_descriptor("idpsso") if len(eids) > 1: raise IdpUnspecified("Too many IdPs to choose from: %s" % eids) try: srvs = self.metadata.single_sign_on_service(list(eids.keys())[0], binding) return destinations(srvs)[0] except IndexError: raise IdpUnspecified("No IdP to send to given the premises")
def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT): if entityid: # verify that it's in the metadata srvs = self.metadata.single_sign_on_service(entityid, binding) if srvs: return destinations(srvs)[0] else: logger.info("_sso_location: %s, %s", entityid, binding) raise IdpUnspecified("No IdP to send to given the premises") # get the idp location from the metadata. If there is more than one # IdP in the configuration raise exception eids = self.metadata.with_descriptor("idpsso") if len(eids) > 1: raise IdpUnspecified("Too many IdPs to choose from: %s" % eids) try: srvs = self.metadata.single_sign_on_service( list(eids.keys())[0], binding) return destinations(srvs)[0] except IndexError: raise IdpUnspecified("No IdP to send to given the premises")
def do_assertion_id_request(self, assertion_ids, entity_id, consent=None, extensions=None, sign=False): srvs = self.metadata.assertion_id_request_service(entity_id, BINDING_SOAP) if not srvs: raise NoServiceDefined("%s: %s" % (entity_id, "assertion_id_request_service")) if isinstance(assertion_ids, six.string_types): assertion_ids = [assertion_ids] _id_refs = [AssertionIDRef(_id) for _id in assertion_ids] for destination in destinations(srvs): res = self._use_soap(destination, "assertion_id_request", assertion_id_refs=_id_refs, consent=consent, extensions=extensions, sign=sign) if res: return res return None
def test_incommon_1(): mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) mds.imp(METADATACONF["2"]) print(mds.entities()) assert mds.entities() > 1700 idps = mds.with_descriptor("idpsso") print(idps.keys()) assert len(idps) > 300 # ~ 18% try: _ = mds.single_sign_on_service('urn:mace:incommon:uiuc.edu') except UnknownPrincipal: pass idpsso = mds.single_sign_on_service('urn:mace:incommon:alaska.edu') assert len(idpsso) == 1 print(idpsso) assert destinations(idpsso) == [ 'https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO' ] sps = mds.with_descriptor("spsso") acs_sp = [] for nam, desc in sps.items(): if "attribute_consuming_service" in desc: acs_sp.append(nam) assert len(acs_sp) == 0 # Look for attribute authorities aas = mds.with_descriptor("attribute_authority") print(aas.keys()) assert len(aas) == 180
def test_swami_1(): UMU_IDP = 'https://idp.umu.se/saml2/idp/metadata.php' mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) mds.imp(METADATACONF["1"]) assert len(mds) == 1 # One source idps = mds.with_descriptor("idpsso") assert idps.keys() idpsso = mds.single_sign_on_service(UMU_IDP) assert len(idpsso) == 1 assert destinations(idpsso) == [ 'https://idp.umu.se/saml2/idp/SSOService.php' ] _name = name(mds[UMU_IDP]) assert _name == u'Umeå University (SAML2)' certs = mds.certs(UMU_IDP, "idpsso", "signing") assert len(certs) == 1 sps = mds.with_descriptor("spsso") assert len(sps) == 108 wants = mds.attribute_requirement('https://connect8.sunet.se/shibboleth') lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]] assert _eq(lnamn, [ 'eduPersonPrincipalName', 'mail', 'givenName', 'sn', 'eduPersonScopedAffiliation' ]) wants = mds.attribute_requirement('https://beta.lobber.se/shibboleth') assert wants["required"] == [] lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]] assert _eq(lnamn, [ 'eduPersonPrincipalName', 'mail', 'givenName', 'sn', 'eduPersonScopedAffiliation', 'eduPersonEntitlement' ])
def test_incommon_1(): mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) mds.imp(METADATACONF["2"]) print(mds.entities()) assert mds.entities() > 1700 idps = mds.with_descriptor("idpsso") print(idps.keys()) assert len(idps) > 300 # ~ 18% try: _ = mds.single_sign_on_service('urn:mace:incommon:uiuc.edu') except UnknownPrincipal: pass idpsso = mds.single_sign_on_service('urn:mace:incommon:alaska.edu') assert len(idpsso) == 1 print(idpsso) assert destinations(idpsso) == [ 'https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO'] sps = mds.with_descriptor("spsso") acs_sp = [] for nam, desc in sps.items(): if "attribute_consuming_service" in desc: acs_sp.append(nam) assert len(acs_sp) == 0 # Look for attribute authorities aas = mds.with_descriptor("attribute_authority") print(aas.keys()) assert len(aas) == 180
def test_metadata(): conf = config.Config() conf.load_file("idp_conf_mdb") umu_idp = 'https://idp.umu.se/saml2/idp/metadata.php' # Set up a Metadata store mds = MetadataStore(ATTRCONV, conf, disable_ssl_certificate_validation=True) # Import metadata from local file. mds.imp([{ "class": "saml2_tophat.mdstore.MetaDataFile", "metadata": [(full_path("swamid-2.0.xml"), )] }]) assert len(mds) == 1 # One source try: export_mdstore_to_mongo_db(mds, "metadata", "test") except ConnectionFailure: pass else: mdmdb = MetadataMDB(ATTRCONV, "metadata", "test") # replace all metadata instances with this one mds.metadata = {"mongo_db": mdmdb} idps = mds.with_descriptor("idpsso") assert idps.keys() idpsso = mds.single_sign_on_service(umu_idp) assert len(idpsso) == 1 assert destinations(idpsso) == [ 'https://idp.umu.se/saml2/idp/SSOService.php' ] _name = name(mds[umu_idp]) assert _name == u'Ume\xe5 University' certs = mds.certs(umu_idp, "idpsso", "signing") assert len(certs) == 1 sps = mds.with_descriptor("spsso") assert len(sps) == 417 wants = mds.attribute_requirement( 'https://connect.sunet.se/shibboleth') assert wants["optional"] == [] lnamn = [ d_to_local_name(mds.attrc, attr) for attr in wants["required"] ] assert _eq(lnamn, [ 'eduPersonPrincipalName', 'mail', 'givenName', 'sn', 'eduPersonScopedAffiliation', 'eduPersonAffiliation' ]) wants = mds.attribute_requirement( "https://gidp.geant.net/sp/module.php/saml/sp/metadata.php/default-sp" ) # Optional lnamn = [ d_to_local_name(mds.attrc, attr) for attr in wants["optional"] ] assert _eq(lnamn, [ 'displayName', 'commonName', 'schacHomeOrganization', 'eduPersonAffiliation', 'schacHomeOrganizationType' ]) # Required lnamn = [ d_to_local_name(mds.attrc, attr) for attr in wants["required"] ] assert _eq( lnamn, ['eduPersonTargetedID', 'mail', 'eduPersonScopedAffiliation'])
def do_attribute_query(self, entityid, subject_id, attribute=None, sp_name_qualifier=None, name_qualifier=None, nameid_format=None, real_id=None, consent=None, extensions=None, sign=False, binding=BINDING_SOAP, nsprefix=None): """ Does a attribute request to an attribute authority, this is by default done over SOAP. :param entityid: To whom the query should be sent :param subject_id: The identifier of the subject :param attribute: A dictionary of attributes and values that is asked for :param sp_name_qualifier: The unique identifier of the service provider or affiliation of providers for whom the identifier was generated. :param name_qualifier: The unique identifier of the identity provider that generated the identifier. :param nameid_format: The format of the name ID :param real_id: The identifier which is the key to this entity in the identity database :param binding: Which binding to use :param nsprefix: Namespace prefixes preferred before those automatically produced. :return: The attributes returned if BINDING_SOAP was used. HTTP args if BINDING_HTT_POST was used. """ if real_id: response_args = {"real_id": real_id} else: response_args = {} if not binding: binding, destination = self.pick_binding("attribute_service", None, "attribute_authority", entity_id=entityid) else: srvs = self.metadata.attribute_service(entityid, binding) if srvs is []: raise SAMLError("No attribute service support at entity") destination = destinations(srvs)[0] if binding == BINDING_SOAP: return self._use_soap(destination, "attribute_query", consent=consent, extensions=extensions, sign=sign, subject_id=subject_id, attribute=attribute, sp_name_qualifier=sp_name_qualifier, name_qualifier=name_qualifier, format=nameid_format, response_args=response_args) elif binding == BINDING_HTTP_POST: mid = sid() query = self.create_attribute_query(destination, subject_id, attribute, mid, consent, extensions, sign, nsprefix) self.state[query.id] = { "entity_id": entityid, "operation": "AttributeQuery", "subject_id": subject_id, "sign": sign } relay_state = self._relay_state(query.id) return self.apply_binding(binding, "%s" % query, destination, relay_state, sign=sign) else: raise SAMLError("Unsupported binding")
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 = 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 if sign: if binding == BINDING_HTTP_REDIRECT: sigalg = kwargs.get( "sigalg", ds.DefaultSignature().get_sign_alg()) # key = kwargs.get("key", self.signkey) srequest = str(request) else: srequest = self.sign(request, sign_alg=sign_alg, digest_alg=digest_alg) else: srequest = str(request) relay_state = self._relay_state(req_id) http_info = self.apply_binding(binding, srequest, destination, relay_state, sign=sign, sigalg=sigalg) 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 do_attribute_query(self, entityid, subject_id, attribute=None, sp_name_qualifier=None, name_qualifier=None, nameid_format=None, real_id=None, consent=None, extensions=None, sign=False, binding=BINDING_SOAP, nsprefix=None): """ Does a attribute request to an attribute authority, this is by default done over SOAP. :param entityid: To whom the query should be sent :param subject_id: The identifier of the subject :param attribute: A dictionary of attributes and values that is asked for :param sp_name_qualifier: The unique identifier of the service provider or affiliation of providers for whom the identifier was generated. :param name_qualifier: The unique identifier of the identity provider that generated the identifier. :param nameid_format: The format of the name ID :param real_id: The identifier which is the key to this entity in the identity database :param binding: Which binding to use :param nsprefix: Namespace prefixes preferred before those automatically produced. :return: The attributes returned if BINDING_SOAP was used. HTTP args if BINDING_HTT_POST was used. """ if real_id: response_args = {"real_id": real_id} else: response_args = {} if not binding: binding, destination = self.pick_binding("attribute_service", None, "attribute_authority", entity_id=entityid) else: srvs = self.metadata.attribute_service(entityid, binding) if srvs is []: raise SAMLError("No attribute service support at entity") destination = destinations(srvs)[0] if binding == BINDING_SOAP: return self._use_soap(destination, "attribute_query", consent=consent, extensions=extensions, sign=sign, subject_id=subject_id, attribute=attribute, sp_name_qualifier=sp_name_qualifier, name_qualifier=name_qualifier, format=nameid_format, response_args=response_args) elif binding == BINDING_HTTP_POST: mid = sid() query = self.create_attribute_query(destination, subject_id, attribute, mid, consent, extensions, sign, nsprefix) self.state[query.id] = {"entity_id": entityid, "operation": "AttributeQuery", "subject_id": subject_id, "sign": sign} relay_state = self._relay_state(query.id) return self.apply_binding(binding, "%s" % query, destination, relay_state, sign=sign) else: raise SAMLError("Unsupported binding")
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 = 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 if sign: if binding == BINDING_HTTP_REDIRECT: sigalg = kwargs.get( "sigalg", ds.DefaultSignature().get_sign_alg()) # key = kwargs.get("key", self.signkey) srequest = str(request) else: srequest = self.sign(request, sign_alg=sign_alg, digest_alg=digest_alg) else: srequest = str(request) relay_state = self._relay_state(req_id) http_info = self.apply_binding(binding, srequest, destination, relay_state, sign=sign, sigalg=sigalg) 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