Added support for eduPersonTargetedID handling.

This commit is contained in:
Roland Hedberg
2013-04-19 15:31:30 +02:00
parent 21c1d96192
commit 59116d06dc
4 changed files with 229 additions and 44 deletions

View File

@@ -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
View 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)

View File

@@ -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

View File

@@ -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.