Added support for eduPersonTargetedID handling.
This commit is contained in:
@@ -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)
|
||||
|
||||
50
src/saml2/eptid.py
Normal file
50
src/saml2/eptid.py
Normal file
@@ -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)
|
||||
@@ -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]
|
||||
|
||||
class MDB_eptid(MDB):
|
||||
primary_key = "userid"
|
||||
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
|
||||
|
||||
def bindings(self, entity_id, typ, service):
|
||||
pass
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user