Ejemplo n.º 1
0
    def imp(self, spec):
        # This serves as a backwards compatibility
        if type(spec) is dict:
            # Old style...
            for key, vals in spec.items():
                for val in vals:
                    if isinstance(val, dict):
                        if not self.check_validity:
                            val["check_validity"] = False
                        self.load(key, **val)
                    else:
                        self.load(key, val)
        else:
            for item in spec:
                try:
                    key = item['class']
                except (KeyError, AttributeError):
                    raise SAMLError("Misconfiguration in metadata %s" % item)
                mod, clas = key.rsplit('.', 1)
                try:
                    mod = importlib.import_module(mod)
                    MDloader = getattr(mod, clas)
                except (ImportError, AttributeError):
                    raise SAMLError("Unknown metadata loader %s" % key)

                # Separately handle MDExtern
                if MDloader == MetaDataExtern:
                    kwargs = {'http': self.http, 'security': self.security}
                else:
                    kwargs = {}

                if self.filter:
                    kwargs["filter"] = self.filter

                for key in item['metadata']:
                    # Separately handle MetaDataFile and directory
                    if MDloader == MetaDataFile and os.path.isdir(key[0]):
                        files = [
                            f for f in os.listdir(key[0])
                            if isfile(join(key[0], f))
                        ]
                        for fil in files:
                            _fil = join(key[0], fil)
                            _md = MetaDataFile(self.attrc, _fil)
                            _md.load()
                            self.metadata[_fil] = _md
                            if _md.to_old:
                                self.to_old[_fil] = _md.to_old
                        return

                    if len(key) == 2:
                        kwargs["cert"] = key[1]

                    _md = MDloader(self.attrc, key[0], **kwargs)
                    _md.load()
                    self.metadata[key[0]] = _md
                    if _md.to_old:
                        self.to_old[key[0]] = _md.to_old
Ejemplo n.º 2
0
    def operation(self, url, idp_entity_id, op, **opargs):
        """
        This is the method that should be used by someone that wants
        to authenticate using SAML ECP

        :param url: The page that access is sought for
        :param idp_entity_id: The entity ID of the IdP that should be
            used for authentication
        :param op: Which HTTP operation (GET/POST/PUT/DELETE)
        :param opargs: Arguments to the HTTP call
        :return: The page
        """
        sp_url = self._sp

        # ********************************************
        # Phase 1 - First conversation with the SP
        # ********************************************
        # headers needed to indicate to the SP that I'm ECP enabled

        opargs["headers"] = self.add_paos_headers(opargs["headers"])
        response = self.send(sp_url, op, **opargs)
        logger.debug("[Op] SP response: %s" % response)
        print(response.text)

        if response.status_code != 200:
            raise SAMLError("Request to SP failed: %s" % response.text)

        # The response might be a AuthnRequest instance in a SOAP envelope
        # body. If so it's the start of the ECP conversation
        # Two SOAP header blocks; paos:Request and ecp:Request
        # may also contain a ecp:RelayState SOAP header block
        # If channel-binding was part of the PAOS header any number of
        # <cb:ChannelBindings> header blocks may also be present
        # if 'holder-of-key' option then one or more <ecp:SubjectConfirmation>
        # header blocks may also be present
        try:
            respdict = self.parse_soap_message(response.text)
            self.ecp_conversation(respdict, idp_entity_id)

            # should by now be authenticated so this should go smoothly
            response = self.send(url, op, **opargs)
        except (soap.XmlParseError, AssertionError, KeyError):
            raise

        if response.status_code >= 400:
            raise SAMLError("Error performing operation: %s" %
                            (response.text, ))

        return response
Ejemplo n.º 3
0
    def __init__(self,
                 attrc,
                 url=None,
                 security=None,
                 cert=None,
                 http=None,
                 **kwargs):
        """
        :params attrc:
        :params url: Location of the metadata
        :params security: SecurityContext()
        :params cert: CertificMDloaderate used to sign the metadata
        :params http:
        """
        super(MetaDataExtern, self).__init__(attrc, **kwargs)
        if not url:
            raise SAMLError('URL not specified.')
        else:
            self.url = url

        # No cert is only an error if the metadata is unsigned
        self.cert = cert

        self.security = security
        self.http = http
Ejemplo n.º 4
0
    def parse_sp_ecp_response(respdict):
        if respdict is None:
            raise SAMLError("Unexpected reply from the SP")

        logger.debug("[P1] SP response dict: %s", respdict)

        # AuthnRequest in the body or not
        authn_request = respdict["body"]
        assert authn_request.c_tag == "AuthnRequest"

        # ecp.RelayState among headers
        _relay_state = None
        _paos_request = None
        for item in respdict["header"]:
            if item.c_tag == "RelayState" and item.c_namespace == ecp.NAMESPACE:
                _relay_state = item
            if item.c_tag == "Request" and item.c_namespace == paos.NAMESPACE:
                _paos_request = item

        if _paos_request is None:
            raise BadRequest("Missing request")

        _rc_url = _paos_request.response_consumer_url

        return {
            "authn_request": authn_request,
            "rc_url": _rc_url,
            "relay_state": _relay_state
        }
Ejemplo n.º 5
0
 def __call__(self, **kwargs):
     if not self.methods:
         raise SAMLError("No authentication methods defined")
     elif len(self.methods) == 1:
         return self.methods[0]
     else:
         pass  # TODO
Ejemplo n.º 6
0
def parse_cookie(name, seed, kaka):
    """Parses and verifies a cookie value

    :param seed: A seed used for the HMAC signature
    :param kaka: The cookie
    :return: A tuple consisting of (payload, timestamp)
    """
    if not kaka:
        return None

    cookie_obj = SimpleCookie(kaka)
    morsel = cookie_obj.get(name)

    if morsel:
        parts = morsel.value.split("|")
        if len(parts) != 3:
            return None
            # verify the cookie signature
        sig = cookie_signature(seed, parts[0], parts[1])
        if sig != parts[2]:
            raise SAMLError("Invalid cookie signature")

        try:
            return parts[0].strip(), parts[1]
        except KeyError:
            return None
    else:
        return None
Ejemplo n.º 7
0
    def load(self, *args, **kwargs):
        if self.filter:
            _args = {"filter": self.filter}
        else:
            _args = {}

        typ = args[0]
        if typ == "local":
            key = args[1]
            # if library read every file in the library
            if os.path.isdir(key):
                files = [f for f in os.listdir(key) if isfile(join(key, f))]
                for fil in files:
                    _fil = join(key, fil)
                    _md = MetaDataFile(self.attrc, _fil, **_args)
                    _md.load()
                    self.metadata[_fil] = _md
                return
            else:
                # else it's just a plain old file so read it
                _md = MetaDataFile(self.attrc, key, **_args)
        elif typ == "inline":
            self.ii += 1
            key = self.ii
            kwargs.update(_args)
            _md = InMemoryMetaData(self.attrc, args[1])
        elif typ == "remote":
            if "url" not in kwargs:
                raise ValueError(
                    "Remote metadata must be structured as a dict containing the key 'url'"
                )
            key = kwargs["url"]
            for _key in ["node_name", "check_validity"]:
                try:
                    _args[_key] = kwargs[_key]
                except KeyError:
                    pass

            if "cert" not in kwargs:
                kwargs["cert"] = ""

            _md = MetaDataExtern(self.attrc, kwargs["url"], self.security,
                                 kwargs["cert"], self.http, **_args)
        elif typ == "mdfile":
            key = args[1]
            _md = MetaDataMD(self.attrc, args[1], **_args)
        elif typ == "loader":
            key = args[1]
            _md = MetaDataLoader(self.attrc, args[1], **_args)
        elif typ == "mdq":
            key = args[1]
            _md = MetaDataMDX(args[1])
        else:
            raise SAMLError("Unknown metadata type '%s'" % typ)
        _md.load()
        self.metadata[key] = _md
Ejemplo n.º 8
0
def entities_descriptor(eds,
                        valid_for,
                        name,
                        ident,
                        sign,
                        secc,
                        sign_alg=None,
                        digest_alg=None):
    entities = md.EntitiesDescriptor(entity_descriptor=eds)
    if valid_for:
        entities.valid_until = in_a_while(hours=valid_for)
    if name:
        entities.name = name
    if ident:
        entities.id = ident

    if sign:
        if not ident:
            ident = sid()

        if not secc.key_file:
            raise SAMLError("If you want to do signing you should define " +
                            "a key to sign with")

        if not secc.my_cert:
            raise SAMLError("If you want to do signing you should define " +
                            "where your public key are")

        entities.signature = pre_signature_part(ident,
                                                secc.my_cert,
                                                1,
                                                sign_alg=sign_alg,
                                                digest_alg=digest_alg)
        entities.id = ident
        xmldoc = secc.sign_statement("%s" % entities, class_name(entities))
        entities = md.entities_descriptor_from_string(xmldoc)
    else:
        xmldoc = None

    return entities, xmldoc
Ejemplo n.º 9
0
    def get_nameid(self, userid, nformat, sp_name_qualifier, name_qualifier):
        _id = self.create_id(nformat, name_qualifier, sp_name_qualifier)

        if nformat == NAMEID_FORMAT_EMAILADDRESS:
            if not self.domain:
                raise SAMLError("Can't issue email nameids, unknown domain")

            _id = "%s@%s" % (_id, self.domain)

        # if nformat == NAMEID_FORMAT_PERSISTENT:
        #     _id = userid

        nameid = NameID(format=nformat,
                        sp_name_qualifier=sp_name_qualifier,
                        name_qualifier=name_qualifier,
                        text=_id)

        self.store(userid, nameid)
        return nameid
Ejemplo n.º 10
0
    def ecp_conversation(self, respdict, idp_entity_id=None):
        """

        :param respdict:
        :param idp_entity_id:
        :return:
        """

        args = self.parse_sp_ecp_response(respdict)

        # **********************
        # Phase 2 - talk to the IdP
        # **********************

        idp_response = self.phase2(idp_entity_id=idp_entity_id, **args)

        # **********************************
        # Phase 3 - back to the SP
        # **********************************

        ht_args = self.use_soap(idp_response, args["rc_url"],
                                [args["relay_state"]])
        ht_args["headers"][0] = ('Content-Type', MIME_PAOS)
        logger.debug("[P3] Post to SP: %s", ht_args["data"])

        # POST the package from the IdP to the SP
        response = self.send(**ht_args)

        if response.status_code == 302:
            # ignore where the SP is redirecting us to and go for the
            # url I started off with.
            pass
        else:
            raise SAMLError("Error POSTing package to SP: %s" % response.text)

        logger.debug("[P3] SP response: %s", response.text)

        self.done_ecp = True
        logger.debug("Done ECP")

        return None
Ejemplo n.º 11
0
    def nim_args(self,
                 local_policy=None,
                 sp_name_qualifier="",
                 name_id_policy=None,
                 name_qualifier=""):
        """

        :param local_policy:
        :param sp_name_qualifier:
        :param name_id_policy:
        :param name_qualifier:
        :return:
        """

        logger.debug("local_policy: %s, name_id_policy: %s", local_policy,
                     name_id_policy)

        if name_id_policy and name_id_policy.sp_name_qualifier:
            sp_name_qualifier = name_id_policy.sp_name_qualifier
        else:
            sp_name_qualifier = sp_name_qualifier

        if name_id_policy and name_id_policy.format:
            nameid_format = name_id_policy.format
        elif local_policy:
            nameid_format = local_policy.get_nameid_format(sp_name_qualifier)
        else:
            raise SAMLError("Unknown NameID format")

        if not name_qualifier:
            name_qualifier = self.name_qualifier

        return {
            "nformat": nameid_format,
            "sp_name_qualifier": sp_name_qualifier,
            "name_qualifier": name_qualifier
        }
Ejemplo n.º 12
0
 def __init__(self, attrc, filename=None, cert=None, **kwargs):
     super(MetaDataFile, self).__init__(attrc, **kwargs)
     if not filename:
         raise SAMLError('No file specified.')
     self.filename = filename
     self.cert = cert
Ejemplo n.º 13
0
    def parse_authn_request_response(self,
                                     xmlstr,
                                     binding,
                                     outstanding=None,
                                     outstanding_certs=None,
                                     conv_info=None):
        """ Deal with an AuthnResponse

        :param xmlstr: The reply as a xml string
        :param binding: Which binding that was used for the transport
        :param outstanding: A dictionary with session IDs as keys and
            the original web request from the user before redirection
            as values.
        :param outstanding_certs:
        :param conv_info: Information about the conversation.
        :return: An response.AuthnResponse or None
        """

        if not getattr(self.config, 'entityid', None):
            raise SAMLError("Missing entity_id specification")

        if not xmlstr:
            return None

        kwargs = {
            "outstanding_queries": outstanding,
            "outstanding_certs": outstanding_certs,
            "allow_unsolicited": self.allow_unsolicited,
            "want_assertions_signed": self.want_assertions_signed,
            "want_assertions_or_response_signed":
            self.want_assertions_or_response_signed,
            "want_response_signed": self.want_response_signed,
            "return_addrs": self.service_urls(binding=binding),
            "entity_id": self.config.entityid,
            "attribute_converters": self.config.attribute_converters,
            "allow_unknown_attributes": self.config.allow_unknown_attributes,
            'conv_info': conv_info,
            "valid_destination_regex": self.valid_destination_regex,
        }

        try:
            resp = self._parse_response(xmlstr, AuthnResponse,
                                        "assertion_consumer_service", binding,
                                        **kwargs)
        except StatusError as err:
            logger.error("SAML status error: %s", err)
            raise
        except UnravelError:
            return None
        except Exception as err:
            logger.error("XML parse error: %s", err)
            raise

        if not isinstance(resp, AuthnResponse):
            logger.error("Response type not supported: %s",
                         saml2_tophat.class_name(resp))
            return None

        if (resp.assertion and len(resp.response.encrypted_assertion) == 0
                and resp.assertion.subject.name_id):
            self.users.add_information_about_person(resp.session_info())
            logger.info("--- ADDED person info ----")

        return resp
Ejemplo n.º 14
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):
        """ 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")
Ejemplo n.º 15
0
    def phase2(self,
               authn_request,
               rc_url,
               idp_entity_id,
               headers=None,
               sign=False,
               **kwargs):
        """
        Doing the second phase of the ECP conversation, the conversation
        with the IdP happens.

        :param authn_request: The AuthenticationRequest
        :param rc_url: The assertion consumer service url of the SP
        :param idp_entity_id: The EntityID of the IdP
        :param headers: Possible extra headers
        :param sign: If the message should be signed
        :return: The response from the IdP
        """

        _, destination = self.pick_binding("single_sign_on_service",
                                           [BINDING_SOAP],
                                           "idpsso",
                                           entity_id=idp_entity_id)

        ht_args = self.apply_binding(BINDING_SOAP,
                                     authn_request,
                                     destination,
                                     sign=sign)

        if headers:
            ht_args["headers"].extend(headers)

        logger.debug("[P2] Sending request: %s", ht_args["data"])

        # POST the request to the IdP
        response = self.send(**ht_args)

        logger.debug("[P2] Got IdP response: %s", response)

        if response.status_code != 200:
            raise SAMLError("Request to IdP failed (%s): %s" %
                            (response.status_code, response.text))

        # SAMLP response in a SOAP envelope body, ecp response in headers
        respdict = self.parse_soap_message(response.text)

        if respdict is None:
            raise SAMLError("Unexpected reply from the IdP")

        logger.debug("[P2] IdP response dict: %s", respdict)

        idp_response = respdict["body"]
        assert idp_response.c_tag == "Response"

        logger.debug("[P2] IdP AUTHN response: %s", idp_response)

        _ecp_response = None
        for item in respdict["header"]:
            if item.c_tag == "Response" and item.c_namespace == ecp.NAMESPACE:
                _ecp_response = item

        _acs_url = _ecp_response.assertion_consumer_service_url
        if rc_url != _acs_url:
            error = ("response_consumer_url '%s' does not match" % rc_url,
                     "assertion_consumer_service_url '%s" % _acs_url)
            # Send an error message to the SP
            _ = self.send(rc_url, "POST", data=soap.soap_fault(error))
            # Raise an exception so the user knows something went wrong
            raise SAMLError(error)

        return idp_response
Ejemplo n.º 16
0
def entity_descriptor(confd):
    mycert = None
    enc_cert = None
    if confd.cert_file is not None:
        mycert = []
        mycert.append("".join(read_cert(confd.cert_file)))
        if confd.additional_cert_files is not None:
            for _cert_file in confd.additional_cert_files:
                mycert.append("".join(read_cert(_cert_file)))
    if confd.encryption_keypairs is not None:
        enc_cert = []
        for _encryption in confd.encryption_keypairs:
            enc_cert.append("".join(read_cert(_encryption["cert_file"])))

    entd = md.EntityDescriptor()
    entd.entity_id = confd.entityid

    if confd.valid_for:
        entd.valid_until = in_a_while(hours=int(confd.valid_for))

    if confd.organization is not None:
        entd.organization = do_organization_info(confd.organization)
    if confd.contact_person is not None:
        entd.contact_person = do_contact_persons_info(confd.contact_person)

    if confd.assurance_certification:
        if not entd.extensions:
            entd.extensions = md.Extensions()
        ava = [AttributeValue(text=c) for c in confd.assurance_certification]
        attr = Attribute(
            attribute_value=ava,
            name="urn:oasis:names:tc:SAML:attribute:assurance-certification",
        )
        _add_attr_to_entity_attributes(entd.extensions, attr)

    if confd.entity_category:
        if not entd.extensions:
            entd.extensions = md.Extensions()
        ava = [AttributeValue(text=c) for c in confd.entity_category]
        attr = Attribute(attribute_value=ava,
                         name="http://macedir.org/entity-category")
        _add_attr_to_entity_attributes(entd.extensions, attr)

    if confd.entity_category_support:
        if not entd.extensions:
            entd.extensions = md.Extensions()
        ava = [AttributeValue(text=c) for c in confd.entity_category_support]
        attr = Attribute(attribute_value=ava,
                         name="http://macedir.org/entity-category-support")
        _add_attr_to_entity_attributes(entd.extensions, attr)

    for item in algorithm_support_in_metadata(confd.xmlsec_binary):
        if not entd.extensions:
            entd.extensions = md.Extensions()
        entd.extensions.add_extension_element(item)

    conf_sp_type = confd.getattr('sp_type', 'sp')
    conf_sp_type_in_md = confd.getattr('sp_type_in_metadata', 'sp')
    if conf_sp_type and conf_sp_type_in_md is True:
        if not entd.extensions:
            entd.extensions = md.Extensions()
        item = sp_type.SPType(text=conf_sp_type)
        entd.extensions.add_extension_element(item)

    serves = confd.serves
    if not serves:
        raise SAMLError(
            'No service type ("sp","idp","aa") provided in the configuration')

    if "sp" in serves:
        confd.context = "sp"
        entd.spsso_descriptor = do_spsso_descriptor(confd, mycert, enc_cert)
    if "idp" in serves:
        confd.context = "idp"
        entd.idpsso_descriptor = do_idpsso_descriptor(confd, mycert, enc_cert)
    if "aa" in serves:
        confd.context = "aa"
        entd.attribute_authority_descriptor = do_aa_descriptor(
            confd, mycert, enc_cert)
    if "pdp" in serves:
        confd.context = "pdp"
        entd.pdp_descriptor = do_pdp_descriptor(confd, mycert, enc_cert)
    if "aq" in serves:
        confd.context = "aq"
        entd.authn_authority_descriptor = do_aq_descriptor(
            confd, mycert, enc_cert)

    return entd
Ejemplo n.º 17
0
def do_uiinfo(_uiinfo):
    uii = mdui.UIInfo()
    for attr in [
            'display_name', 'description', "information_url",
            'privacy_statement_url'
    ]:
        try:
            val = _uiinfo[attr]
        except KeyError:
            continue

        aclass = uii.child_class(attr)
        inst = getattr(uii, attr)
        if isinstance(val, six.string_types):
            ainst = aclass(text=val)
            inst.append(ainst)
        elif isinstance(val, dict):
            ainst = aclass()
            ainst.text = val["text"]
            ainst.lang = val["lang"]
            inst.append(ainst)
        else:
            for value in val:
                if isinstance(value, six.string_types):
                    ainst = aclass(text=value)
                    inst.append(ainst)
                elif isinstance(value, dict):
                    ainst = aclass()
                    ainst.text = value["text"]
                    ainst.lang = value["lang"]
                    inst.append(ainst)

    try:
        _attr = "logo"
        val = _uiinfo[_attr]
        inst = getattr(uii, _attr)
        # dictionary or list of dictionaries
        if isinstance(val, dict):
            logo = mdui.Logo()
            for attr, value in val.items():
                if attr in logo.keys():
                    setattr(logo, attr, value)
            inst.append(logo)
        elif isinstance(val, list):
            for logga in val:
                if not isinstance(logga, dict):
                    raise SAMLError("Configuration error !!")
                logo = mdui.Logo()
                for attr, value in logga.items():
                    if attr in logo.keys():
                        setattr(logo, attr, value)
                inst.append(logo)
    except KeyError:
        pass

    try:
        _attr = "keywords"
        val = _uiinfo[_attr]
        inst = getattr(uii, _attr)
        # list of six.string_types, dictionary or list of dictionaries
        if isinstance(val, list):
            for value in val:
                keyw = mdui.Keywords()
                if isinstance(value, six.string_types):
                    keyw.text = value
                elif isinstance(value, dict):
                    keyw.text = " ".join(value["text"])
                    try:
                        keyw.lang = value["lang"]
                    except KeyError:
                        pass
                else:
                    raise SAMLError("Configuration error: ui_info keywords")
                inst.append(keyw)
        elif isinstance(val, dict):
            keyw = mdui.Keywords()
            keyw.text = " ".join(val["text"])
            try:
                keyw.lang = val["lang"]
            except KeyError:
                pass
            inst.append(keyw)
        else:
            raise SAMLError("Configuration Error: ui_info keywords")
    except KeyError:
        pass

    return uii