diff --git a/src/s2repoze/plugins/sp.py b/src/s2repoze/plugins/sp.py index 7521f83..041adc9 100644 --- a/src/s2repoze/plugins/sp.py +++ b/src/s2repoze/plugins/sp.py @@ -34,11 +34,8 @@ from repoze.who.interfaces import IChallenger, IIdentifier, IAuthenticator from repoze.who.interfaces import IMetadataProvider from repoze.who.plugins.form import FormPluginBase -from saml2 import BINDING_HTTP_REDIRECT from saml2.client import Saml2Client -from saml2.config import SPConfig from saml2.s_utils import sid -from saml2.virtual_org import VirtualOrg #from saml2.population import Population #from saml2.attribute_resolver import AttributeResolver @@ -417,13 +414,10 @@ def make_plugin(rememberer_name=None, # plugin for remember raise ValueError( 'must include rememberer_name in configuration') - config = SPConfig() - config.load_file(saml_conf) - - scl = Saml2Client(config, identity_cache=identity_cache, + scl = Saml2Client(config_file=saml_conf, identity_cache=identity_cache, virtual_organization=virtual_organization) - plugin = SAML2Plugin(rememberer_name, config, scl, wayf, cache, debug, + plugin = SAML2Plugin(rememberer_name, scl.config, scl, wayf, cache, debug, sid_store) return plugin diff --git a/src/saml2/client.py b/src/saml2/client.py index a92f5d7..de09fcc 100644 --- a/src/saml2/client.py +++ b/src/saml2/client.py @@ -40,7 +40,7 @@ from saml2.binding import send_using_soap, http_redirect_message from saml2.binding import http_post_message from saml2.population import Population from saml2.virtual_org import VirtualOrg -from saml2.config import SPConfig +from saml2.config import config_factory #from saml2.response import authn_response from saml2.response import response_factory @@ -72,9 +72,9 @@ class LogoutError(Exception): class Saml2Client(object): """ The basic pySAML2 service provider class """ - def __init__(self, config=None, debug=0, + def __init__(self, config=None, debug=0, identity_cache=None, state_cache=None, - virtual_organization=None): + virtual_organization=None, config_file=""): """ :param config: A saml2.config.Config instance :param debug: Whether debugging should be done even if the @@ -96,11 +96,14 @@ class Saml2Client(object): self.sec = None if config: self.config = config - self.metadata = config.metadata - self.sec = security_context(config) + elif config_file: + self.config = config_factory("sp", config_file) else: - self.config = SPConfig() - + raise Exception("Missing configuration") + + self.metadata = self.config.metadata + self.sec = security_context(config) + if virtual_organization: self.vorg = VirtualOrg(self, virtual_organization) else: @@ -464,12 +467,15 @@ class Saml2Client(object): log.info("No response") return None - def construct_logout_request(self, subject_id, destination, entity_id, + def construct_logout_request(self, subject_id, destination, + issuer_entity_id, reason=None, expire=None): """ Constructs a LogoutRequest :param subject_id: The identifier of the subject - :param destination: + :param destination: + :param issuer_entity_id: The entity ID of the IdP the request is + target at. :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, @@ -480,7 +486,8 @@ class Saml2Client(object): session_id = sid() # create NameID from subject_id name_id = saml.NameID( - text = self.users.get_entityid(subject_id, entity_id, False)) + text = self.users.get_entityid(subject_id, issuer_entity_id, + False)) request = samlp.LogoutRequest( id=session_id, diff --git a/src/saml2/config.py b/src/saml2/config.py index 2adfa42..2626143 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -2,65 +2,135 @@ __author__ = 'rolandh' +import sys +from importlib import import_module from saml2 import BINDING_SOAP, BINDING_HTTP_REDIRECT from saml2 import metadata from saml2.attribute_converter import ac_factory from saml2.assertion import Policy -SIMPLE_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file", - "secret", "accepted_time_diff", "virtual_organization", "name", - "description", "endpoints", "required_attributes", - "optional_attributes", "idp", "sp", "aa", "subject_data", - "want_assertions_signed", "authn_requests_signed", "type", - "organization", "contact_person", - "want_authn_requests_signed", "name_form"] +COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file", + "secret", "accepted_time_diff", "name", + "description", + "organization", + "contact_person", + "name_form", + "virtual_organization", + ] -COMPLEX_ARGS = ["metadata", "attribute_converters", "policy"] +SP_ARGS = [ + "required_attributes", + "optional_attributes", + "idp", + "subject_data", + "want_assertions_signed", + "authn_requests_signed", + "name_form", + "endpoints", + ] + +AA_IDP_ARGS = ["want_authn_requests_signed", + "provided_attributes", + "subject_data", + "sp", + "endpoints", + "metadata"] + +COMPLEX_ARGS = ["attribute_converters", "metadata", "policy"] +ALL = COMMON_ARGS + SP_ARGS + AA_IDP_ARGS + COMPLEX_ARGS + + +SPEC = { + "": COMMON_ARGS + COMPLEX_ARGS, + "sp": COMMON_ARGS + COMPLEX_ARGS + SP_ARGS, + "idp": COMMON_ARGS + COMPLEX_ARGS + AA_IDP_ARGS, + "aa": COMMON_ARGS + COMPLEX_ARGS + AA_IDP_ARGS, +} class Config(object): - def __init__(self): - self._attr = {} + def_context = "" + def __init__(self): + self._attr = {"": {}, "sp": {}, "idp": {}, "aa": {}} + self.context = "" + + def serves(self): + return [t for t in ["sp", "idp", "aa"] if self._attr[t]] + def __getattribute__(self, item): - if item in SIMPLE_ARGS or item in COMPLEX_ARGS: + if item == "context": + return object.__getattribute__(self, item) + + _context = self.context + if item in ALL: try: - return self._attr[item] + return self._attr[_context][item] except KeyError: + if _context: + try: + return self._attr[""][item] + except KeyError: + pass return None else: return object.__getattribute__(self, item) - def load(self, cnf): - - for arg in SIMPLE_ARGS: + def load_special(self, cnf, typ): + for arg in SPEC[typ]: try: - self._attr[arg] = cnf[arg] + self._attr[typ][arg] = cnf[arg] except KeyError: pass + self.context = typ + self.load_complex(cnf, typ) + self.context = self.def_context + + def load_complex(self, cnf, typ=""): try: - self._attr["policy"] = Policy(cnf["policy"]) + self._attr[typ]["policy"] = Policy(cnf["policy"]) except KeyError: pass try: acs = ac_factory(cnf["attribute_map_dir"]) try: - self._attr["attribute_converters"].extend(acs) + self._attr[typ]["attribute_converters"].extend(acs) except KeyError: - self._attr["attribute_converters"] = acs + self._attr[typ]["attribute_converters"] = acs except KeyError: pass try: - self._attr["metadata"] = self.load_metadata(cnf["metadata"]) + self._attr[typ]["metadata"] = self.load_metadata(cnf["metadata"]) except KeyError: pass + def load(self, cnf): + + for arg in COMMON_ARGS: + try: + self._attr[""][arg] = cnf[arg] + except KeyError: + pass + + if "service" in cnf: + for typ in ["aa", "idp", "sp"]: + try: + self.load_special(cnf["service"][typ], typ) + except KeyError: + pass + + self.load_complex(cnf) + self.context = self.def_context return self def load_file(self, config_file): - return self.load(eval(open(config_file).read())) + if sys.path[0] != ".": + sys.path.insert(0, ".") + mod = import_module(config_file) + #return self.load(eval(open(config_file).read())) + return self.load(mod.CONFIG) def load_metadata(self, metadata_conf): """ Loads metadata into an internal structure """ @@ -110,6 +180,8 @@ class Config(object): return None class SPConfig(Config): + def_context = "sp" + def __init__(self): Config.__init__(self) @@ -167,6 +239,8 @@ class SPConfig(Config): return self.metadata.idps() class IdPConfig(Config): + def_context = "idp" + def __init__(self): Config.__init__(self) @@ -190,3 +264,15 @@ class IdPConfig(Config): return [s[binding] for s in acs] return [] + +def config_factory(typ, file): + if typ == "sp": + conf = SPConfig().load_file(file) + conf.context = typ + elif typ in ["aa", "idp"]: + conf = IdPConfig().load_file(file) + conf.context = typ + else: + conf = Config().load_file(file) + conf.context = typ + return conf diff --git a/src/saml2/metadata.py b/src/saml2/metadata.py index e3aad67..357d687 100644 --- a/src/saml2/metadata.py +++ b/src/saml2/metadata.py @@ -719,6 +719,7 @@ def do_requested_attribute(attributes, acs, is_required="false"): for key in attr.keyswv(): args[key] = getattr(attr, key) args["is_required"] = is_required + args["name_format"] = NAME_FORMAT_URI lista.append(md.RequestedAttribute(**args)) return lista @@ -917,16 +918,18 @@ def entity_descriptor(confd, valid_for): if confd.contact_person is not None: entd.contact_person = do_contact_person_info(confd.contact_person) - if confd.type == "sp": - entd.spsso_descriptor = do_sp_sso_descriptor(confd, mycert) - elif confd.type == "idp": - entd.idpsso_descriptor = do_idp_sso_descriptor(confd, mycert) - elif confd.type == "aa": - entd.attribute_authority_descriptor = do_aa_descriptor(confd, mycert) - else: + serves = confd.serves() + if not serves: raise Exception( 'No service type ("sp","idp","aa") provided in the configuration') + if "sp" in serves: + entd.spsso_descriptor = do_sp_sso_descriptor(confd, mycert) + if "idp" in serves: + entd.idpsso_descriptor = do_idp_sso_descriptor(confd, mycert) + if "aa" in serves: + entd.attribute_authority_descriptor = do_aa_descriptor(confd, mycert) + return entd def entities_descriptor(eds, valid_for, name, ident, sign, secc): diff --git a/src/saml2/response.py b/src/saml2/response.py index bbc5889..3516cf8 100644 --- a/src/saml2/response.py +++ b/src/saml2/response.py @@ -56,7 +56,7 @@ def for_me(condition, myself ): return False -def authn_response(conf, entity_id, return_addr, outstanding_queries=None, +def authn_response(conf, return_addr, outstanding_queries=None, log=None, timeslack=0, debug=0): sec = security_context(conf) if not timeslack: @@ -65,12 +65,12 @@ def authn_response(conf, entity_id, return_addr, outstanding_queries=None, except TypeError: timeslack = 0 - return AuthnResponse(sec, conf.attribute_converters, entity_id, + return AuthnResponse(sec, conf.attribute_converters, conf.entityid, return_addr, outstanding_queries, log, timeslack, debug) # comes in over SOAP so synchronous -def attribute_response(conf, entity_id, return_addr, log=None, timeslack=0, +def attribute_response(conf, return_addr, log=None, timeslack=0, debug=0): sec = security_context(conf) if not timeslack: @@ -79,7 +79,7 @@ def attribute_response(conf, entity_id, return_addr, log=None, timeslack=0, except TypeError: timeslack = 0 - return AttributeResponse(sec, conf.attribute_converters, entity_id, + return AttributeResponse(sec, conf.attribute_converters, conf.entityid, return_addr, log, timeslack, debug) class StatusResponse(object): diff --git a/src/saml2/server.py b/src/saml2/server.py index d0f2425..47feac3 100644 --- a/src/saml2/server.py +++ b/src/saml2/server.py @@ -50,7 +50,7 @@ from saml2.binding import http_post_message from saml2.sigver import security_context from saml2.sigver import signed_instance_factory from saml2.sigver import pre_signature_part -from saml2.config import IdPConfig +from saml2.config import config_factory from saml2.assertion import Assertion, Policy class UnknownVO(Exception): @@ -212,6 +212,8 @@ class Server(object): self.load_config(config_file) elif config: self.conf = config + else: + raise Exception("Missing configuration") self.metadata = self.conf.metadata self.sec = security_context(self.conf, log) @@ -228,8 +230,7 @@ class Server(object): :param config_file: The name of the configuration file """ - self.conf = IdPConfig() - self.conf.load_file(config_file) + self.conf = config_factory("idp", config_file) try: # subject information is store in database # default database is a shelve database which is OK in some setups @@ -593,7 +594,10 @@ class Server(object): self.log.info("enpoints: %s" % (self.conf.endpoints,)) self.log.info("binding wanted: %s" % (binding,)) raise - + + if not slo: + raise Exception("No single_logout_server for that binding") + if self.log: self.log.info("Endpoint: %s" % slo) req = LogoutRequest(self.sec, slo)