diff options
-rw-r--r-- | src/saml2/config.py | 82 | ||||
-rw-r--r-- | src/saml2/eptid.py | 50 | ||||
-rw-r--r-- | src/saml2/mongo_store.py | 123 | ||||
-rw-r--r-- | src/saml2/server.py | 18 |
4 files changed, 229 insertions, 44 deletions
diff --git a/src/saml2/config.py b/src/saml2/config.py index ec28d397..30c44cbb 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -91,7 +91,8 @@ AA_IDP_ARGS = [ "ui_info", "name_id_format", "domain", - "name_qualifier" + "name_qualifier", + "edu_person_targeted_id", ] PDP_ARGS = ["endpoints", "name_form", "name_id_format"] @@ -159,30 +160,30 @@ class Config(object): def __init__(self, homedir="."): self._homedir = homedir self.entityid = None - self.xmlsec_binary= None - self.debug=False - self.key_file=None - self.cert_file=None - self.secret=None - self.accepted_time_diff=None - self.name=None - self.ca_certs=None + self.xmlsec_binary = None + self.debug = False + self.key_file = None + self.cert_file = None + self.secret = None + self.accepted_time_diff = None + self.name = None + self.ca_certs = None self.verify_ssl_cert = False - self.description=None - self.valid_for=None - self.organization=None - self.contact_person=None - self.name_form=None - self.nameid_form=None - self.virtual_organization=None - self.logger=None - self.only_use_keys_in_metadata=True - self.logout_requests_signed=None - self.disable_ssl_certificate_validation=None + self.description = None + self.valid_for = None + self.organization = None + self.contact_person = None + self.name_form = None + self.nameid_form = None + self.virtual_organization = None + self.logger = None + self.only_use_keys_in_metadata = True + self.logout_requests_signed = None + self.disable_ssl_certificate_validation = None self.context = "" - self.attribute_converters=None - self.metadata=None - self.policy=None + self.attribute_converters = None + self.metadata = None + self.policy = None self.serves = [] self.vorg = {} self.preferred_binding = PREFERRED_BINDING @@ -193,7 +194,7 @@ class Config(object): if context == "": setattr(self, attr, val) else: - setattr(self, "_%s_%s" % (context,attr), val) + setattr(self, "_%s_%s" % (context, attr), val) def getattr(self, attr, context=None): if context is None: @@ -202,7 +203,7 @@ class Config(object): if context == "": return getattr(self, attr, None) else: - return getattr(self, "_%s_%s" % (context,attr), None) + return getattr(self, "_%s_%s" % (context, attr), None) def load_special(self, cnf, typ, metadata_construction=False): for arg in SPEC[typ]: @@ -228,8 +229,7 @@ class Config(object): acs = ac_factory() if not acs: - raise Exception(("No attribute converters, ", - "something is wrong!!")) + raise Exception("No attribute converters, something is wrong!!") _acs = self.getattr("attribute_converters", typ) if _acs: @@ -273,23 +273,23 @@ class Config(object): for arg in COMMON_ARGS: if arg == "virtual_organization": if "virtual_organization" in cnf: - for key,val in cnf["virtual_organization"].items(): + for key, val in cnf["virtual_organization"].items(): self.vorg[key] = VirtualOrg(None, key, val) continue - try: setattr(self, arg, _uc(cnf[arg])) except KeyError: pass - except TypeError: # Something that can't be a string + except TypeError: # Something that can't be a string setattr(self, arg, cnf[arg]) if "service" in cnf: for typ in ["aa", "idp", "sp", "pdp", "aq"]: try: - self.load_special(cnf["service"][typ], typ, - metadata_construction=metadata_construction) + self.load_special( + cnf["service"][typ], typ, + metadata_construction=metadata_construction) self.serves.append(typ) except KeyError: pass @@ -301,8 +301,8 @@ class Config(object): # verify that xmlsec is where it's supposed to be if not os.path.exists(self.xmlsec_binary): #if not os.access(, os.F_OK): - raise Exception("xmlsec binary not in '%s' !" % ( - self.xmlsec_binary)) + raise Exception( + "xmlsec binary not in '%s' !" % self.xmlsec_binary) self.load_complex(cnf, metadata_construction=metadata_construction) self.context = self.def_context @@ -340,8 +340,9 @@ class Config(object): except: disable_validation = False - mds = MetadataStore(ONTS.values(), acs, xmlsec_binary, ca_certs, - disable_ssl_certificate_validation=disable_validation) + mds = MetadataStore( + ONTS.values(), acs, xmlsec_binary, ca_certs, + disable_ssl_certificate_validation=disable_validation) mds.imp(metadata_conf) @@ -395,7 +396,7 @@ class Config(object): raise Exception("Unknown socktype!") try: handler = LOG_HANDLER[htyp](**args) - except TypeError: # difference between 2.6 and 2.7 + except TypeError: # difference between 2.6 and 2.7 del args["socktype"] handler = LOG_HANDLER[htyp](**args) else: @@ -415,7 +416,7 @@ class Config(object): return handler def setup_logger(self): - if root_logger.level != logging.NOTSET: # Someone got there before me + if root_logger.level != logging.NOTSET: # Someone got there before me return root_logger _logconf = self.logger @@ -424,13 +425,14 @@ class Config(object): try: root_logger.setLevel(LOG_LEVEL[_logconf["loglevel"].lower()]) - except KeyError: # reasonable default + except KeyError: # reasonable default root_logger.setLevel(logging.INFO) root_logger.addHandler(self.log_handler()) root_logger.info("Logging started") return root_logger + class SPConfig(Config): def_context = "sp" @@ -458,12 +460,14 @@ class SPConfig(Config): return None + class IdPConfig(Config): def_context = "idp" def __init__(self): Config.__init__(self) + def config_factory(typ, filename): if typ == "sp": conf = SPConfig().load_file(filename) diff --git a/src/saml2/eptid.py b/src/saml2/eptid.py new file mode 100644 index 00000000..dd66d8fa --- /dev/null +++ b/src/saml2/eptid.py @@ -0,0 +1,50 @@ +# An eduPersonTargetedID comprises +# the entity name of the identity provider, the entity name of the service +# provider, and the opaque string value. +# These strings are separated by "!" symbols. This form is advocated by +# Internet2 and may overtake the other form in due course. + +import hashlib +import shelve + +import logging + +logger = logging.getLogger(__name__) + + +class Eptid(object): + def __init__(self, secret): + self._db = {} + self.secret = secret + + def make(self, idp, sp, args): + md5 = hashlib.md5() + for arg in args: + md5.update(arg.encode("utf-8")) + md5.update(sp) + md5.update(self.secret) + md5.digest() + hashval = md5.hexdigest() + return "!".join([idp, sp, hashval]) + + def __getitem__(self, key): + return self._db[key] + + def __setitem__(self, key, value): + self._db[key] = value + + def get(self, idp, sp, args): + # key is a combination of sp_entity_id and object id + key = (".".join([sp, args[0]])).encode("utf-8") + try: + return self[key] + except KeyError: + val = self.make(idp, sp, args[1]) + self[key] = val + return val + + +class EptidShelve(Eptid): + def __init__(self, secret, filename): + Eptid.__init__(self, secret) + self._db = shelve.open(filename, writeback=True) diff --git a/src/saml2/mongo_store.py b/src/saml2/mongo_store.py index baa8433a..25a36b0e 100644 --- a/src/saml2/mongo_store.py +++ b/src/saml2/mongo_store.py @@ -2,6 +2,8 @@ from hashlib import sha1 import logging from pymongo import MongoClient +from saml2.eptid import Eptid +from saml2.mdstore import MetaData from saml2.s_utils import PolicyError from saml2.ident import code, IdentDB, Unknown @@ -35,6 +37,10 @@ __author__ = 'rolandh' logger = logging.getLogger(__name__) +class CorruptDatabase(Exception): + pass + + def context_match(cfilter, cntx): # TODO return True @@ -185,6 +191,7 @@ class IdentMDB(IdentDB): pass +#------------------------------------------------------------------------------ class MDB(object): primary_key = "mdb" @@ -193,8 +200,11 @@ class MDB(object): _db = connection[collection] self.db = _db[sub_collection] - def store(self, key, **kwargs): - doc = {self.primary_key: key} + def store(self, value, **kwargs): + if value: + doc = {self.primary_key: value} + else: + doc = {} doc.update(kwargs) _ = self.db.insert(doc) @@ -217,6 +227,111 @@ class MDB(object): for item in self.db.find(doc): self.db.remove(item["_id"]) + def keys(self): + for item in self.db.find(): + yield item[self.primary_key] + + def items(self): + for item in self.db.find(): + _key = item[self.primary_key] + del item[self.primary_key] + del item["_id"] + yield _key, item + + def __contains__(self, key): + doc = {self.primary_key: key} + res = [item for item in self.db.find(doc)] + if not res: + return False + else: + return True + + +#------------------------------------------------------------------------------ +class EptidMDB(Eptid): + primary_key = "eptid" + + def __init__(self, secret, collection="", sub_collection=""): + Eptid.__init__(self, secret) + self.mdb = MDB(collection, sub_collection) + self.mdb.primary_key = "entity_id" + + def __getitem__(self, key): + res = self.mdb.get(key) + if not res: + raise KeyError(key) + elif len(res) == 1: + return res[0] + else: + raise CorruptDatabase("Found more than one EPTID document") + + def __setitem__(self, key, value): + if key == self.mdb.primary_key: + _ = self.mdb.store(value) + else: + _ = self.mdb.store(**{key: value}) + + +#------------------------------------------------------------------------------ +class MetadataMDB(MetaData): + def __init__(self, onts, attrc, collection="", sub_collection=""): + MetaData.__init__(self, onts, attrc) + self.mdb = MDB(collection, sub_collection) + self.mdb.primary_key = "entity_id" + + def _service(self, entity_id, typ, service, binding=None): + """ Get me all services with a specified + entity ID and type, that supports the specified version of binding. + + + :param entity_id: The EntityId + :param typ: Type of service (idp, attribute_authority, ...) + :param service: which service that is sought for + :param binding: A binding identifier + :return: list of service descriptions. + Or if no binding was specified a list of 2-tuples (binding, srv) + """ + pass + + def _ext_service(self, entity_id, typ, service, binding): + try: + srvs = self.entity[entity_id][typ] + except KeyError: + return None + + if not srvs: + return srvs + + res = [] + for srv in srvs: + if "extensions" in srv: + for elem in srv["extensions"]["extension_elements"]: + if elem["__class__"] == service: + if elem["binding"] == binding: + res.append(elem) + + return res + + def load(self): + pass + + def items(self): + return self.mdb.items() + + def keys(self): + return self.mdb.keys() + + def __contains__(self, item): + pass + + def attribute_requirement(self): + pass + + def with_descriptor(self): + pass + + def construct_source_id(self): + pass -class MDB_eptid(MDB): - primary_key = "userid" + def bindings(self, entity_id, typ, service): + pass
\ No newline at end of file diff --git a/src/saml2/server.py b/src/saml2/server.py index 4d9dc974..077de45a 100644 --- a/src/saml2/server.py +++ b/src/saml2/server.py @@ -23,7 +23,8 @@ import os import shelve import memcache -from saml2.mongo_store import IdentMDB, SessionStorageMDB +from saml2.eptid import EptidShelve, Eptid +from saml2.mongo_store import IdentMDB, SessionStorageMDB, EptidMDB from saml2.sdb import SessionStorage from saml2.schema import soapenv @@ -70,6 +71,7 @@ class Server(Entity): self.symkey = symkey self.seed = rndstr() self.iv = os.urandom(16) + self.eptid = None def support_AssertionIDRequest(self): return True @@ -128,6 +130,20 @@ class Server(Entity): raise Exception("Couldn't open identity database: %s" % (dbspec,)) + dbspec = self.config.getattr("edu_person_targeted_id", "idp") + if not dbspec: + pass + else: + typ = dbspec[0] + addr = dbspec[1] + secret = dbspec[2] + if typ == "shelve": + self.eptid = EptidShelve(secret, addr) + elif typ == "mongodb": + self.eptid = EptidMDB(secret, addr, *dbspec[3:]) + else: + self.eptid = Eptid(secret) + def wants(self, sp_entity_id, index=None): """ Returns what attributes the SP requires and which are optional if any such demands are registered in the Metadata. |