def entities_descriptor(eds, valid_for, name, ident, sign, secc): 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) entities.id = ident xmldoc = secc.sign_statement("%s" % entities, class_name(entities)) entities = md.entities_descriptor_from_string(xmldoc) return entities
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
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 """ if url not in opargs: 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(url, op, **opargs) logger.debug("[Op] SP response: %s" % response) if response.status_code != 200: raise SAMLError("Request to SP failed: %s" % response.error) # 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): pass #print "RESP",response, self.http.response if response.status_code != 404: raise SAMLError("Error performing operation: %s" % (response.error, )) return response
def load(self, typ, *args, **kwargs): if typ == "local": key = args[0] _md = MetaDataFile(self.onts, self.attrc, args[0]) elif typ == "inline": self.ii += 1 key = self.ii _md = MetaData(self.onts, self.attrc, args[0], **kwargs) elif typ == "remote": key = kwargs["url"] _args = {} for _key in ["node_name", "check_validity"]: try: _args[_key] = kwargs[_key] except KeyError: pass _md = MetaDataExtern(self.onts, self.attrc, kwargs["url"], self.security, kwargs["cert"], self.http, **_args) elif typ == "mdfile": key = args[0] _md = MetaDataMD(self.onts, self.attrc, args[0]) elif typ == "loader": key = args[0] _md = MetaDataLoader(self.onts, self.attrc, args[0]) else: raise SAMLError("Unknown metadata type '%s'" % typ) _md.load() self.metadata[key] = _md
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"] expected_tag = "AuthnRequest" if authn_request.c_tag != expected_tag: raise ValueError( "Invalid AuthnRequest tag '{invalid}' should be '{valid}'". format(invalid=authn_request.c_tag, valid=expected_tag)) # 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, }
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 }
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
def get_nameid(self, userid, nformat, sp_name_qualifier, name_qualifier): if nformat == NAMEID_FORMAT_PERSISTENT: nameid = self.match_local_id(userid, sp_name_qualifier, name_qualifier) if nameid: logger.debug( "Found existing persistent NameId {nid} for user {uid}". format(nid=nameid, uid=userid)) return nameid _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) nameid = NameID( format=nformat, sp_name_qualifier=sp_name_qualifier, name_qualifier=name_qualifier, text=_id, ) self.store(userid, nameid) return nameid
def __init__(self, url=None, security=None, cert=None, entity_transform=None, **kwargs): """ :params url: mdx service url :params security: SecurityContext() :params cert: certificate used to check signature of signed metadata :params entity_transform: function transforming (e.g. base64, sha1 hash or URL quote hash) the entity id. It is applied to the entity id before it is concatenated with the request URL sent to the MDX server. Defaults to sha1 transformation. """ super(MetaDataMDX, self).__init__(None, **kwargs) if not url: raise SAMLError('URL for MDQ server not specified.') self.url = url.rstrip('/') if entity_transform: self.entity_transform = entity_transform else: self.entity_transform = MetaDataMDX.sha1_entity_transform self.cert = cert self.security = security # We assume that the MDQ server will return a single entity # described by a single <EntityDescriptor> element. The protocol # does allow multiple entities to be returned in an # <EntitiesDescriptor> element but we will not currently support # that use case since it is unlikely to be leveraged for most # flows. self.node_name = "%s:%s" % (md.EntityDescriptor.c_namespace, md.EntityDescriptor.c_tag)
def pick_binding(self, service, bindings=None, descr_type="", request=None, entity_id=""): if request and not entity_id: entity_id = request.issuer.text.strip() sfunc = getattr(self.metadata, service) if bindings is None: bindings = self.config.preferred_binding[service] if not descr_type: if self.entity_type == "sp": descr_type = "idpsso" else: descr_type = "spsso" for binding in bindings: try: srvs = sfunc(entity_id, binding, descr_type) if srvs: return binding, destinations(srvs)[0] except UnsupportedBinding: pass logger.error("Failed to find consumer URL: %s, %s, %s" % (entity_id, bindings, descr_type)) #logger.error("Bindings: %s" % bindings) #logger.error("Entities: %s" % self.metadata) raise SAMLError("Unkown entity or unsupported bindings")
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}
def parse_authn_request_response(self, xmlstr, binding, outstanding=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. :return: An response.AuthnResponse or None """ try: _ = self.config.entityid except KeyError: raise SAMLError("Missing entity_id specification") resp = None if xmlstr: kwargs = { "outstanding_queries": outstanding, "allow_unsolicited": self.allow_unsolicited, "return_addr": self.service_url(), "entity_id": self.config.entityid, "attribute_converters": self.config.attribute_converters } try: resp = self._parse_response(xmlstr, AuthnResponse, "assertion_consumer_service", binding, **kwargs) except StatusError, err: logger.error("SAML status error: %s" % err) raise except UnravelError: return None
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
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
def load(self, typ, *args, **kwargs): if typ == "local": key = args[0] _md = MetaDataFile(self.onts, self.attrc, args[0]) elif typ == "inline": self.ii += 1 key = self.ii _md = MetaData(self.onts, self.attrc, args[0], **kwargs) elif typ == "remote": key = kwargs["url"] _md = MetaDataExtern(self.onts, self.attrc, kwargs["url"], self.security, kwargs["cert"], self.http, node_name=kwargs.get('node_name')) elif typ == "mdfile": key = args[0] _md = MetaDataMD(self.onts, self.attrc, args[0]) elif typ == "loader": key = args[0] _md = MetaDataLoader(self.onts, self.attrc, args[0]) else: raise SAMLError("Unknown metadata type '%s'" % typ) _md.load() self.metadata[key] = _md
def __init__(self, entity_type, config=None, config_file="", virtual_organization=""): self.entity_type = entity_type self.users = None if config: self.config = config elif config_file: self.config = config_factory(entity_type, config_file) else: raise SAMLError("Missing configuration") for item in ["cert_file", "key_file", "ca_certs"]: _val = getattr(self.config, item, None) if not _val: continue if _val.startswith("http"): r = requests.request("GET", _val) if r.status_code == 200: _, filename = make_temp(r.text, ".pem", False) setattr(self.config, item, filename) else: raise Exception( "Could not fetch certificate from %s" % _val) try: self.signkey = RSA.importKey( open(self.config.getattr("key_file", ""), 'r').read()) except (KeyError, TypeError): self.signkey = None HTTPBase.__init__(self, self.config.verify_ssl_cert, self.config.ca_certs, self.config.key_file, self.config.cert_file) if self.config.vorg: for vo in self.config.vorg.values(): vo.sp = self self.metadata = self.config.metadata self.config.setup_logger() self.debug = self.config.debug self.sec = security_context(self.config) if virtual_organization: if isinstance(virtual_organization, six.string_types): self.vorg = self.config.vorg[virtual_organization] elif isinstance(virtual_organization, VirtualOrg): self.vorg = virtual_organization else: self.vorg = None self.artifact = {} if self.metadata: self.sourceid = self.metadata.construct_source_id() else: self.sourceid = {}
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 } 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.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
def parse_authn_request_response(self, xmlstr, binding, outstanding=None, outstanding_certs=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. :return: An response.AuthnResponse or None """ try: _ = self.config.entityid except KeyError: raise SAMLError("Missing entity_id specification") resp = None if xmlstr: kwargs = { "outstanding_queries": outstanding, "outstanding_certs": outstanding_certs, "allow_unsolicited": self.allow_unsolicited, "want_assertions_signed": self.want_assertions_signed, "want_response_signed": self.want_response_signed, "return_addrs": self.service_urls(), "entity_id": self.config.entityid, "attribute_converters": self.config.attribute_converters, "allow_unknown_attributes": self.config.allow_unknown_attributes, } 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 exc: logger.error("%s" % exc) raise #logger.debug(">> %s", resp) if resp is None: return None elif isinstance(resp, AuthnResponse): self.users.add_information_about_person(resp.session_info()) logger.info("--- ADDED person info ----") pass else: logger.error("Response type not supported: %s" % (saml2.class_name(resp), )) return resp
def entity_descriptor(confd): mycert = None enc_cert = None if confd.cert_file is not None: mycert = [] mycert.append("".join(open(confd.cert_file).readlines()[1:-1])) if confd.additional_cert_files is not None: for _cert_file in confd.additional_cert_files: mycert.append("".join(open(_cert_file).readlines()[1:-1])) if confd.encryption_keypairs is not None: enc_cert = [] for _encryption in confd.encryption_keypairs: enc_cert.append("".join( open(_encryption["cert_file"]).readlines()[1:-1])) 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_person_info(confd.contact_person) if confd.entity_category: 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") item = mdattr.EntityAttributes(attribute=attr) 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
def pick_binding(self, service, bindings=None, descr_type="", request=None, entity_id=""): if request and not entity_id: entity_id = request.issuer.text.strip() sfunc = getattr(self.metadata, service) if bindings is None: if request and request.protocol_binding: bindings = [request.protocol_binding] else: bindings = self.config.preferred_binding[service] if not descr_type: if self.entity_type == "sp": descr_type = "idpsso" else: descr_type = "spsso" _url = _index = None if request: try: _url = getattr(request, "%s_url" % service) except AttributeError: _url = None try: _index = getattr(request, "%s_index" % service) except AttributeError: pass for binding in bindings: try: srvs = sfunc(entity_id, binding, descr_type) if srvs: if _url: for srv in srvs: if srv["location"] == _url: return binding, _url elif _index: for srv in srvs: if srv["index"] == _index: return binding, srv["location"] else: return binding, destinations(srvs)[0] except UnsupportedBinding: pass logger.error("Failed to find consumer URL: %s, %s, %s" % (entity_id, bindings, descr_type)) #logger.error("Bindings: %s" % bindings) #logger.error("Entities: %s" % self.metadata) raise SAMLError("Unkown entity or unsupported bindings")
def apply_binding(self, binding, msg_str, destination="", relay_state="", response=False, sign=False, **kwargs): """ Construct the necessary HTTP arguments dependent on Binding :param binding: Which binding to use :param msg_str: The return message as a string (XML) if the message is to be signed it MUST contain the signature element. :param destination: Where to send the message :param relay_state: Relay_state if provided :param response: Which type of message this is :param kwargs: response type specific arguments :return: A dictionary """ # unless if BINDING_HTTP_ARTIFACT if response: typ = "SAMLResponse" else: typ = "SAMLRequest" if binding == BINDING_HTTP_POST: logger.info("HTTP POST") # if self.entity_type == 'sp': # info = self.use_http_post(msg_str, destination, relay_state, # typ) # info["url"] = destination # info["method"] = "POST" # else: info = self.use_http_form_post(msg_str, destination, relay_state, typ) info["url"] = destination info["method"] = "POST" elif binding == BINDING_HTTP_REDIRECT: logger.info("HTTP REDIRECT") if kwargs.get('sigalg', ''): signer = self.sec.sec_backend.get_signer(kwargs['sigalg']) else: signer = None info = self.use_http_get(msg_str, destination, relay_state, typ, signer=signer, **kwargs) info["url"] = str(destination) info["method"] = "GET" elif binding == BINDING_SOAP or binding == BINDING_PAOS: info = self.use_soap(msg_str, destination, sign=sign, **kwargs) elif binding == BINDING_URI: info = self.use_http_uri(msg_str, typ, destination) elif binding == BINDING_HTTP_ARTIFACT: if response: info = self.use_http_artifact(msg_str, destination, relay_state) info["method"] = "GET" info["status"] = 302 else: info = self.use_http_artifact(msg_str, destination, relay_state) else: raise SAMLError("Unknown binding type: %s" % binding) return info
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
def response_args(self, message, bindings=None, descr_type=""): """ :param message: The message to which a reply is constructed :param bindings: Which bindings can be used. :param descr_type: Type of descriptor (spssp, idpsso, ) :return: Dictionary """ info = {"in_response_to": message.id} if isinstance(message, AuthnRequest): rsrv = "assertion_consumer_service" descr_type = "spsso" info["sp_entity_id"] = message.issuer.text info["name_id_policy"] = message.name_id_policy elif isinstance(message, LogoutRequest): rsrv = "single_logout_service" elif isinstance(message, AttributeQuery): info["sp_entity_id"] = message.issuer.text rsrv = "attribute_consuming_service" descr_type = "spsso" elif isinstance(message, ManageNameIDRequest): rsrv = "manage_name_id_service" # The once below are solely SOAP so no return destination needed elif isinstance(message, AssertionIDRequest): rsrv = "" elif isinstance(message, ArtifactResolve): rsrv = "" elif isinstance(message, AssertionIDRequest): rsrv = "" elif isinstance(message, NameIDMappingRequest): rsrv = "" else: raise SAMLError("No support for this type of query") if bindings == [BINDING_SOAP]: info["binding"] = BINDING_SOAP info["destination"] = "" return info if rsrv: if not descr_type: if self.entity_type == "sp": descr_type = "idpsso" else: descr_type = "spsso" binding, destination = self.pick_binding(rsrv, bindings, descr_type=descr_type, request=message) info["binding"] = binding info["destination"] = destination return info
def create_logout_request(self, destination, issuer_entity_id, subject_id=None, name_id=None, reason=None, expire=None, message_id=0, consent=None, extensions=None, sign=False, session_indexes=None, sign_alg=None, digest_alg=None): """ Constructs a LogoutRequest :param destination: Destination of the request :param issuer_entity_id: The entity ID of the IdP the request is target at. :param subject_id: The identifier of the subject :param name_id: A NameID instance identifying the subject :param reason: An indication of the reason for the logout, in the form of a URI reference. :param expire: The time at which the request expires, after which the recipient may discard the message. :param message_id: Request identifier :param consent: Whether the principal have given her consent :param extensions: Possible extensions :param sign: Whether the query should be signed or not. :param session_indexes: SessionIndex instances or just values :return: A LogoutRequest instance """ if subject_id: if self.entity_type == "idp": name_id = NameID(text=self.users.get_entityid(subject_id, issuer_entity_id, False)) else: name_id = NameID(text=subject_id) if not name_id: raise SAMLError("Missing subject identification") args = {} if session_indexes: sis = [] for si in session_indexes: if isinstance(si, SessionIndex): sis.append(si) else: sis.append(SessionIndex(text=si)) args["session_index"] = sis return self._message(LogoutRequest, destination, message_id, consent, extensions, sign, name_id=name_id, reason=reason, not_on_or_after=expire, issuer=self._issuer(), sign_alg=sign_alg, digest_alg=digest_alg, **args)
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) nameid = NameID(format=nformat, sp_name_qualifier=sp_name_qualifier, name_qualifier=name_qualifier, text=_id) self.store(userid, nameid) return nameid
def artifact2message(self, artifact, descriptor): """ :param artifact: The Base64 encoded SAML artifact as sent over the net :param descriptor: The type of entity on the other side :return: A SAML message (request/response) """ destination = self.artifact2destination(artifact, descriptor) if not destination: raise SAMLError("Missing endpoint location") _sid = sid() msg = self.create_artifact_resolve(artifact, destination, _sid) return self.send_using_soap(msg, destination)
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"]]) logger.debug("[P3] Post to SP: %s" % ht_args["data"]) ht_args["headers"].append(('Content-Type', 'application/vnd.paos+xml')) # POST the package from the IdP to the SP response = self.send(args["rc_url"], "POST", **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: print((response.error)) raise SAMLError("Error POSTing package to SP: %s" % response.error) logger.debug("[P3] SP response: %s" % response.text) self.done_ecp = True logger.debug("Done ECP") return None
def __init__(self, entity_type, config=None, config_file="", virtual_organization=""): self.entity_type = entity_type self.users = None if config: self.config = config elif config_file: self.config = config_factory(entity_type, config_file) else: raise SAMLError("Missing configuration") HTTPBase.__init__(self, self.config.verify_ssl_cert, self.config.ca_certs, self.config.key_file, self.config.cert_file) if self.config.vorg: for vo in list(self.config.vorg.values()): vo.sp = self self.metadata = self.config.metadata self.config.setup_logger() self.debug = self.config.debug self.seed = rndbytes(32) self.sec = security_context(self.config) if virtual_organization: if isinstance(virtual_organization, str): self.vorg = self.config.vorg[virtual_organization] elif isinstance(virtual_organization, VirtualOrg): self.vorg = virtual_organization else: self.vorg = None self.artifact = {} if self.metadata: self.sourceid = self.metadata.construct_source_id() else: self.sourceid = {}
def load(self, typ, *args, **kwargs): if typ == "local": key = args[0] md = MetaDataFile(self.onts, self.attrc, args[0]) elif typ == "inline": self.ii += 1 key = self.ii md = MetaData(self.onts, self.attrc, args[0]) elif typ == "remote": key = kwargs["url"] md = MetaDataExtern(self.onts, self.attrc, kwargs["url"], self.security, kwargs["cert"], self.http) elif typ == "mdfile": key = args[0] md = MetaDataMD(self.onts, self.attrc, args[0]) else: raise SAMLError("Unknown metadata type '%s'" % typ) md.load() self.metadata[key] = md
def load(self, typ, *args, **kwargs): if typ == "local": key = args[0] # if library read every file in the library if os.path.isdir(key): files = [f for f in listdir(key) if isfile(join(key, f))] for fil in files: _fil = join(key, fil) _md = MetaDataFile(self.onts, self.attrc, _fil) _md.load() self.metadata[_fil] = _md return else: # else it's just a plain old file so read it _md = MetaDataFile(self.onts, self.attrc, key) elif typ == "inline": self.ii += 1 key = self.ii _md = MetaData(self.onts, self.attrc, args[0], **kwargs) elif typ == "remote": key = kwargs["url"] _args = {} for _key in ["node_name", "check_validity"]: try: _args[_key] = kwargs[_key] except KeyError: pass _md = MetaDataExtern(self.onts, self.attrc, kwargs["url"], self.security, kwargs["cert"], self.http, **_args) elif typ == "mdfile": key = args[0] _md = MetaDataMD(self.onts, self.attrc, args[0]) elif typ == "loader": key = args[0] _md = MetaDataLoader(self.onts, self.attrc, args[0]) else: raise SAMLError("Unknown metadata type '%s'" % typ) _md.load() self.metadata[key] = _md