Example #1
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 locations(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
Example #2
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 list(locations(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
Example #3
0
    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 locations(srvs):
            resp = self._use_soap(dest,
                                  "authz_decision_query",
                                  action=action,
                                  evidence=evidence,
                                  resource=resource,
                                  subject=subject)
            if resp:
                return resp

        return None
Example #4
0
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 list(locations(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'])
Example #5
0
    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 locations(srvs):
            resp = self._use_soap(destination, "authn_query", consent=consent,
                                  extensions=extensions, sign=sign)
            if resp:
                return resp

        return None
Example #6
0
    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 next(locations(srvs), None)
            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 next(locations(srvs), None)
        except IndexError:
            raise IdpUnspecified("No IdP to send to given the premises")
Example #7
0
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 list(locations(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
Example #8
0
    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,
        sign_alg=None,
        digest_alg=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 = next(locations(srvs), None)

        if binding == BINDING_SOAP:
            return self._use_soap(
                destination,
                "attribute_query",
                consent=consent,
                extensions=extensions,
                sign=sign,
                sign_alg=sign_alg,
                digest_alg=digest_alg,
                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,
                name_id=subject_id,
                attribute=attribute,
                message_id=mid,
                consent=consent,
                extensions=extensions,
                sign=sign,
                sign_alg=sign_alg,
                digest_alg=digest_alg,
                nsprefix=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,
                str(query),
                destination,
                relay_state,
                sign=False,
                sigalg=sign_alg,
            )
        else:
            raise SAMLError("Unsupported binding")
Example #9
0
    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
Example #10
0
    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
Example #11
0
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.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 list(locations(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'])