Added support for entity categories.

This commit is contained in:
Roland Hedberg
2013-05-04 10:10:34 +02:00
parent 76da2bb6bb
commit 655a24f0d2
17 changed files with 364 additions and 72 deletions

View File

@@ -22,12 +22,12 @@ else:
#BASE = "http://lingon.catalogix.se:8088" #BASE = "http://lingon.catalogix.se:8088"
BASE = "http://localhost:8088" BASE = "http://localhost:8088"
CONFIG={ CONFIG = {
"entityid" : "%s/idp.xml" % BASE, "entityid": "%s/idp.xml" % BASE,
"description": "My IDP", "description": "My IDP",
"service": { "service": {
"aa": { "aa": {
"endpoints" : { "endpoints": {
"attribute_service": [ "attribute_service": [
("%s/attr" % BASE, BINDING_SOAP) ("%s/attr" % BASE, BINDING_SOAP)
] ]
@@ -36,16 +36,16 @@ CONFIG={
NAMEID_FORMAT_PERSISTENT] NAMEID_FORMAT_PERSISTENT]
}, },
"aq": { "aq": {
"endpoints" : { "endpoints": {
"authn_query_service": [ "authn_query_service": [
("%s/aqs" % BASE, BINDING_SOAP) ("%s/aqs" % BASE, BINDING_SOAP)
] ]
}, },
}, },
"idp": { "idp": {
"name" : "Rolands IdP", "name": "Rolands IdP",
"endpoints" : { "endpoints": {
"single_sign_on_service" : [ "single_sign_on_service": [
("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT), ("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT),
("%s/sso/post" % BASE, BINDING_HTTP_POST), ("%s/sso/post" % BASE, BINDING_HTTP_POST),
("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT), ("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT),
@@ -56,27 +56,28 @@ CONFIG={
("%s/slo/post" % BASE, BINDING_HTTP_POST), ("%s/slo/post" % BASE, BINDING_HTTP_POST),
("%s/slo/redirect" % BASE, BINDING_HTTP_REDIRECT) ("%s/slo/redirect" % BASE, BINDING_HTTP_REDIRECT)
], ],
"artifact_resolve_service":[ "artifact_resolve_service": [
("%s/ars" % BASE, BINDING_SOAP) ("%s/ars" % BASE, BINDING_SOAP)
], ],
"assertion_id_request_service": [ "assertion_id_request_service": [
("%s/airs" % BASE, BINDING_URI) ("%s/airs" % BASE, BINDING_URI)
], ],
"manage_name_id_service":[ "manage_name_id_service": [
("%s/mni/soap" % BASE, BINDING_SOAP), ("%s/mni/soap" % BASE, BINDING_SOAP),
("%s/mni/post" % BASE, BINDING_HTTP_POST), ("%s/mni/post" % BASE, BINDING_HTTP_POST),
("%s/mni/redirect" % BASE, BINDING_HTTP_REDIRECT), ("%s/mni/redirect" % BASE, BINDING_HTTP_REDIRECT),
("%s/mni/art" % BASE, BINDING_HTTP_ARTIFACT) ("%s/mni/art" % BASE, BINDING_HTTP_ARTIFACT)
], ],
"name_id_mapping_service":[ "name_id_mapping_service": [
("%s/nim" % BASE, BINDING_SOAP), ("%s/nim" % BASE, BINDING_SOAP),
], ],
}, },
"policy": { "policy": {
"default": { "default": {
"lifetime": {"minutes":15}, "lifetime": {"minutes": 15},
"attribute_restrictions": None, # means all I have "attribute_restrictions": None, # means all I have
"name_form": NAME_FORMAT_URI "name_form": NAME_FORMAT_URI,
"entity_categories": ["swami", "edugain"]
}, },
}, },
"subject_data": "./idp.subject", "subject_data": "./idp.subject",
@@ -84,10 +85,10 @@ CONFIG={
NAMEID_FORMAT_PERSISTENT] NAMEID_FORMAT_PERSISTENT]
}, },
}, },
"debug" : 1, "debug": 1,
"key_file" : "pki/mykey.pem", "key_file": "pki/mykey.pem",
"cert_file" : "pki/mycert.pem", "cert_file": "pki/mycert.pem",
"metadata" : { "metadata": {
"local": ["../sp/sp.xml"], "local": ["../sp/sp.xml"],
}, },
"organization": { "organization": {
@@ -95,27 +96,28 @@ CONFIG={
"name": "Rolands Identiteter", "name": "Rolands Identiteter",
"url": "http://www.example.com", "url": "http://www.example.com",
}, },
"contact_person": [{ "contact_person": [
"contact_type": "technical", {
"given_name": "Roland", "contact_type": "technical",
"sur_name": "Hedberg", "given_name": "Roland",
"email_address": "technical@example.com" "sur_name": "Hedberg",
},{ "email_address": "technical@example.com"
"contact_type": "support", }, {
"given_name": "Support", "contact_type": "support",
"email_address": "support@example.com" "given_name": "Support",
}, "email_address": "support@example.com"
},
], ],
# This database holds the map between a subjects local identifier and # This database holds the map between a subjects local identifier and
# the identifier returned to a SP # the identifier returned to a SP
"xmlsec_binary": xmlsec_path, "xmlsec_binary": xmlsec_path,
"attribute_map_dir" : "../attributemaps", "attribute_map_dir": "../attributemaps",
"logger": { "logger": {
"rotating": { "rotating": {
"filename": "idp.log", "filename": "idp.log",
"maxBytes": 500000, "maxBytes": 500000,
"backupCount": 5, "backupCount": 5,
}, },
"loglevel": "debug", "loglevel": "debug",
} }
} }

View File

@@ -73,7 +73,8 @@ setup(
packages=['saml2', 'xmldsig', 'xmlenc', 's2repoze', 's2repoze.plugins', packages=['saml2', 'xmldsig', 'xmlenc', 's2repoze', 's2repoze.plugins',
"saml2/profile", "saml2/schema", "saml2/extension", "saml2/profile", "saml2/schema", "saml2/extension",
"saml2/attributemaps", "saml2/authn_context"], "saml2/attributemaps", "saml2/authn_context",
"saml2/entity_category"],
package_dir={'': 'src'}, package_dir={'': 'src'},
package_data={'': ['xml/*.xml']}, package_data={'': ['xml/*.xml']},

View File

@@ -14,6 +14,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import importlib
import logging import logging
import re import re
@@ -21,14 +22,15 @@ from saml2.saml import NAME_FORMAT_URI
import xmlenc import xmlenc
from saml2 import saml from saml2 import saml
from saml2 import entity_category
from saml2.time_util import instant, in_a_while from saml2.time_util import instant, in_a_while
from saml2.attribute_converter import from_local from saml2.attribute_converter import from_local
from saml2.s_utils import sid, MissingValue from saml2.s_utils import sid, MissingValue
from saml2.s_utils import factory from saml2.s_utils import factory
from saml2.s_utils import assertion_factory from saml2.s_utils import assertion_factory
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -286,7 +288,19 @@ class Policy(object):
for _, spec in self._restrictions.items(): for _, spec in self._restrictions.items():
if spec is None: if spec is None:
continue continue
try:
_entcat = spec["entity_categories"]
except KeyError:
pass
else:
ecs = []
for cat in _entcat:
_mod = importlib.import_module(
"saml2.entity_category.%s" % cat)
ecs.append(_mod.RELEASE)
spec["entity_categories"] = ecs
try: try:
restr = spec["attribute_restrictions"] restr = spec["attribute_restrictions"]
except KeyError: except KeyError:
@@ -383,7 +397,53 @@ class Policy(object):
restrictions = None restrictions = None
return restrictions return restrictions
def get_entity_categories_restriction(self, sp_entity_id, mds):
if not self._restrictions:
return None
restrictions = {}
ec_maps = []
try:
try:
ec_maps = self._restrictions[sp_entity_id]["entity_categories"]
except KeyError:
try:
ec_maps = self._restrictions["default"]["entity_categories"]
except KeyError:
pass
except KeyError:
pass
if ec_maps:
# always released
for ec_map in ec_maps:
try:
attrs = ec_map[""]
except KeyError:
pass
else:
for attr in attrs:
restrictions[attr] = None
try:
ecs = mds.entity_categories(sp_entity_id)
except KeyError:
pass
else:
for ec in ecs:
for ec_map in ec_maps:
try:
attrs = ec_map[ec]
except KeyError:
pass
else:
for attr in attrs:
restrictions[attr] = None
return restrictions
def not_on_or_after(self, sp_entity_id): def not_on_or_after(self, sp_entity_id):
""" When the assertion stops being valid, should not be """ When the assertion stops being valid, should not be
used after this time. used after this time.
@@ -394,7 +454,7 @@ class Policy(object):
return in_a_while(**self.get_lifetime(sp_entity_id)) return in_a_while(**self.get_lifetime(sp_entity_id))
def filter(self, ava, sp_entity_id, required=None, optional=None): def filter(self, ava, sp_entity_id, mdstore, required=None, optional=None):
""" What attribute and attribute values returns depends on what """ What attribute and attribute values returns depends on what
the SP has said it wants in the request or in the metadata file and the SP has said it wants in the request or in the metadata file and
what the IdP/AA wants to release. An assumption is that what the SP what the IdP/AA wants to release. An assumption is that what the SP
@@ -408,8 +468,11 @@ class Policy(object):
:return: A possibly modified AVA :return: A possibly modified AVA
""" """
ava = filter_attribute_value_assertions( _rest = self.get_attribute_restriction(sp_entity_id)
ava, self.get_attribute_restriction(sp_entity_id)) if _rest is None:
_rest = self.get_entity_categories_restriction(sp_entity_id,
mdstore)
ava = filter_attribute_value_assertions(ava, _rest)
if required or optional: if required or optional:
ava = filter_on_attributes(ava, required, optional) ava = filter_on_attributes(ava, required, optional)
@@ -427,8 +490,8 @@ class Policy(object):
if metadata: if metadata:
spec = metadata.attribute_requirement(sp_entity_id) spec = metadata.attribute_requirement(sp_entity_id)
if spec: if spec:
return self.filter(ava, sp_entity_id, spec["required"], ava = self.filter(ava, sp_entity_id, metadata,
spec["optional"]) spec["required"], spec["optional"])
return self.filter(ava, sp_entity_id, [], []) return self.filter(ava, sp_entity_id, [], [])
@@ -447,19 +510,6 @@ class Policy(object):
audience=factory(saml.Audience, audience=factory(saml.Audience,
text=sp_entity_id))]) text=sp_entity_id))])
NAME = ["givenName", "surname", "initials", "displayName", "schacSn1",
"schacSn2"]
STATIC_ORG_INFO = ["organizationName", ""]
RESEARCH_AND_EDUCATION = "http://www.swamid.se/category/research-and-education"
SFS_1993_1153 = "http://www.swamid.se/category/sfs-1993-1153"
# EC_RELEASE = {
# "eduPersonPrincipalName", "eduPersonTargetedID", "mail", "email",
# "eduPersonScopedAffiliation"
# ]),
# "http://www.swamid.se/category/sfs-1993-1153": ["norEduPersonNIN"]
# }
class EntityCategories(object): class EntityCategories(object):

View File

@@ -60,7 +60,8 @@ COMMON_ARGS = [
"logout_requests_signed", "logout_requests_signed",
"disable_ssl_certificate_validation", "disable_ssl_certificate_validation",
"referred_binding", "referred_binding",
"session_storage" "session_storage",
"entity_category"
] ]
SP_ARGS = [ SP_ARGS = [
@@ -189,6 +190,7 @@ class Config(object):
self.preferred_binding = PREFERRED_BINDING self.preferred_binding = PREFERRED_BINDING
self.domain = "" self.domain = ""
self.name_qualifier = "" self.name_qualifier = ""
self.entity_category = ""
def setattr(self, context, attr, val): def setattr(self, context, attr, val):
if context == "": if context == "":

View File

@@ -7,6 +7,7 @@ __author__ = 'rolandh'
IDPDISC_POLICY = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single" IDPDISC_POLICY = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single"
class DiscoveryServer(Entity): class DiscoveryServer(Entity):
def __init__(self, config=None, config_file=""): def __init__(self, config=None, config_file=""):
Entity.__init__(self, "disco", config, config_file) Entity.__init__(self, "disco", config, config_file)
@@ -65,7 +66,7 @@ class DiscoveryServer(Entity):
returnIDParam="entityID", returnIDParam="entityID",
entity_id=None): entity_id=None):
if entity_id: if entity_id:
qp = urlencode({returnIDParam:entity_id}) qp = urlencode({returnIDParam: entity_id})
part = urlparse(return_url) part = urlparse(return_url)
if part.query: if part.query:

View File

@@ -0,0 +1,14 @@
__author__ = 'rolandh'
ENTITYATTRIBUTES = "urn:oasis:names:tc:SAML:metadata:attribute&EntityAttributes"
def entity_categories(md):
res = []
if "extensions" in md:
for elem in md["extensions"]["extension_elements"]:
if elem["__class__"] == ENTITYATTRIBUTES:
for attr in elem["attribute"]:
res.append(attr["text"])
return res

View File

@@ -0,0 +1,10 @@
__author__ = 'rolandh'
COC = "http://www.edugain.org/dataprotection/coc-eu-01-draft"
RELEASE = {
"": ["eduPersonTargetedID"],
COC: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "email",
"givenName", "surname", "displayName", "schacHomeOrganization"]
}

View File

@@ -0,0 +1,20 @@
__author__ = 'rolandh'
NAME = ["givenName", "surname", "initials", "displayName"]
STATIC_ORG_INFO = ["c", "o", "ou"]
OTHER = ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "email"]
RESEARCH_AND_EDUCATION = "http://www.swamid.se/category/research-and-education"
SFS_1993_1153 = "http://www.swamid.se/category/sfs-1993-1153"
EU = "http://www.swamid.se/category/eu-adequate-protection"
NREN = "http://www.swamid.se/category/nren-service"
HEI = "http://www.swamid.se/category/hei-service"
RELEASE = {
"": ["eduPersonTargetedID"],
SFS_1993_1153: ["norEduPersonNIN"],
RESEARCH_AND_EDUCATION: NAME + STATIC_ORG_INFO + OTHER,
}

View File

@@ -46,6 +46,9 @@ REQ2SRV = {
"discovery_service_request": "discovery_response" "discovery_service_request": "discovery_response"
} }
ENTITYATTRIBUTES = "urn:oasis:names:tc:SAML:metadata:attribute&EntityAttributes"
# --------------------------------------------------- # ---------------------------------------------------
@@ -321,6 +324,16 @@ class MetaData(object):
return res return res
def entity_categories(self, entity_id):
res = []
if "extensions" in self[entity_id]:
for elem in self[entity_id]["extensions"]["extension_elements"]:
if elem["__class__"] == ENTITYATTRIBUTES:
for attr in elem["attribute"]:
res.append(attr["text"])
return res
class MetaDataFile(MetaData): class MetaDataFile(MetaData):
""" """
@@ -649,6 +662,17 @@ class MetadataStore(object):
ad = self.__getitem__(entity_id)["affiliation_descriptor"] ad = self.__getitem__(entity_id)["affiliation_descriptor"]
return [m["text"] for m in ad["affiliate_member"]] return [m["text"] for m in ad["affiliate_member"]]
def entity_categories(self, entity_id):
ext = self.__getitem__(entity_id)["extensions"]
res = []
for elem in ext["extension_elements"]:
if elem["__class__"] == ENTITYATTRIBUTES:
for attr in elem["attribute"]:
if attr["name"] == "http://macedir.org/entity-category":
res.extend([v["text"] for v in attr["attribute_value"]])
return res
def bindings(self, entity_id, typ, service): def bindings(self, entity_id, typ, service):
for md in self.metadata.values(): for md in self.metadata.values():
if entity_id in md.items(): if entity_id in md.items():

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
from saml2.time_util import in_a_while from saml2.time_util import in_a_while
from saml2.extension import mdui, idpdisc, shibmd from saml2.extension import mdui, idpdisc, shibmd, mdattr
from saml2.saml import NAME_FORMAT_URI from saml2.saml import NAME_FORMAT_URI, AttributeValue, Attribute
from saml2.attribute_converter import from_local_name from saml2.attribute_converter import from_local_name
from saml2 import md from saml2 import md, saml
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_SOAP from saml2 import BINDING_SOAP
@@ -549,6 +549,14 @@ def entity_descriptor(confd):
if confd.contact_person is not None: if confd.contact_person is not None:
entd.contact_person = do_contact_person_info(confd.contact_person) 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 serves = confd.serves
if not serves: if not serves:
raise Exception( raise Exception(

View File

@@ -9,6 +9,8 @@ import sys
import hmac import hmac
# from python 2.5 # from python 2.5
import imp
if sys.version_info >= (2, 5): if sys.version_info >= (2, 5):
import hashlib import hashlib
else: # before python 2.5 else: # before python 2.5
@@ -406,4 +408,31 @@ def fticks_log(sp, logf, idp_entity_id, user_id, secret, assertion):
"PN": csum.hexdigest(), "PN": csum.hexdigest(),
"AM": assertion.AuthnStatement.AuthnContext.AuthnContextClassRef.text "AM": assertion.AuthnStatement.AuthnContext.AuthnContextClassRef.text
} }
logf.info(FTICKS_FORMAT % "#".join(["%s=%s" % (a,v) for a,v in info])) logf.info(FTICKS_FORMAT % "#".join(["%s=%s" % (a,v) for a,v in info]))
def dynamic_importer(name, class_name=None):
"""
Dynamically imports modules / classes
"""
try:
fp, pathname, description = imp.find_module(name)
except ImportError:
print "unable to locate module: " + name
return None, None
try:
package = imp.load_module(name, fp, pathname, description)
except Exception, e:
raise
if class_name:
try:
_class = imp.load_module("%s.%s" % (name, class_name), fp,
pathname, description)
except Exception, e:
raise
return package, _class
else:
return package, None

View File

@@ -344,7 +344,7 @@ class Server(Entity):
ast = Assertion(identity) ast = Assertion(identity)
policy = self.config.getattr("policy", "aa") policy = self.config.getattr("policy", "aa")
if policy: if policy:
ast.apply_policy(sp_entity_id, policy) ast.apply_policy(sp_entity_id, policy, self.metadata)
else: else:
policy = Policy() policy = Policy()

View File

@@ -11,9 +11,9 @@ from pathutils import full_path, xmlsec_path
BASE = "http://lingon.catalogix.se:8087" BASE = "http://lingon.catalogix.se:8087"
CONFIG={ CONFIG = {
"entityid" : "urn:mace:example.com:saml:roland:sp", "entityid": "urn:mace:example.com:saml:roland:sp",
"name" : "urn:mace:example.com:saml:roland:sp", "name": "urn:mace:example.com:saml:roland:sp",
"description": "My own SP", "description": "My own SP",
"service": { "service": {
"sp": { "sp": {
@@ -22,10 +22,10 @@ CONFIG={
("%s/" % BASE, BINDING_HTTP_POST), ("%s/" % BASE, BINDING_HTTP_POST),
("%s/paos" % BASE, BINDING_PAOS), ("%s/paos" % BASE, BINDING_PAOS),
("%s/redirect" % BASE, BINDING_HTTP_REDIRECT)], ("%s/redirect" % BASE, BINDING_HTTP_REDIRECT)],
"artifact_resolution_service":[ "artifact_resolution_service": [
("%s/ars" % BASE, BINDING_SOAP) ("%s/ars" % BASE, BINDING_SOAP)
], ],
"manage_name_id_service":[ "manage_name_id_service": [
("%s/mni/soap" % BASE, BINDING_SOAP), ("%s/mni/soap" % BASE, BINDING_SOAP),
("%s/mni/post" % BASE, BINDING_HTTP_POST), ("%s/mni/post" % BASE, BINDING_HTTP_POST),
("%s/mni/redirect" % BASE, BINDING_HTTP_REDIRECT), ("%s/mni/redirect" % BASE, BINDING_HTTP_REDIRECT),
@@ -34,26 +34,27 @@ CONFIG={
"single_logout_service": [ "single_logout_service": [
("%s/sls" % BASE, BINDING_SOAP) ("%s/sls" % BASE, BINDING_SOAP)
], ],
"discovery_response":[ "discovery_response": [
("%s/disco" % BASE, BINDING_DISCO) ("%s/disco" % BASE, BINDING_DISCO)
] ]
}, },
"required_attributes": ["surName", "givenName", "mail"], "required_attributes": ["surName", "givenName", "mail"],
"optional_attributes": ["title", "eduPersonAffiliation"], "optional_attributes": ["title", "eduPersonAffiliation"],
"idp": ["urn:mace:example.com:saml:roland:idp"], "idp": ["urn:mace:example.com:saml:roland:idp"],
"name_id_format":[NAMEID_FORMAT_TRANSIENT, NAMEID_FORMAT_PERSISTENT] "name_id_format": [NAMEID_FORMAT_TRANSIENT,
NAMEID_FORMAT_PERSISTENT]
} }
}, },
"debug": 1, "debug": 1,
"key_file": full_path("test.key"), "key_file": full_path("test.key"),
"cert_file": full_path("test.pem"), "cert_file": full_path("test.pem"),
"ca_certs": full_path("cacerts.txt"), "ca_certs": full_path("cacerts.txt"),
"xmlsec_binary" : xmlsec_path, "xmlsec_binary": xmlsec_path,
"metadata": { "metadata": {
"local": [full_path("idp_all.xml"), full_path("vo_metadata.xml")], "local": [full_path("idp_all.xml"), full_path("vo_metadata.xml")],
}, },
"virtual_organization": { "virtual_organization": {
"urn:mace:example.com:it:tek":{ "urn:mace:example.com:it:tek": {
"nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID",
"common_identifier": "umuselin", "common_identifier": "umuselin",
} }
@@ -61,12 +62,15 @@ CONFIG={
"subject_data": "subject_data.db", "subject_data": "subject_data.db",
"accepted_time_diff": 60, "accepted_time_diff": 60,
"attribute_map_dir": full_path("attributemaps"), "attribute_map_dir": full_path("attributemaps"),
"entity_category": ["http://www.swamid.se/category/sfs-1993-1153",
#"http://www.swamid.se/category/research-and-education",
"http://www.swamid.se/category/hei-service"],
#"valid_for": 6, #"valid_for": 6,
"organization": { "organization": {
"name": ("AB Exempel", "se"), "name": ("AB Exempel", "se"),
"display_name": ("AB Exempel", "se"), "display_name": ("AB Exempel", "se"),
"url": "http://www.example.org", "url": "http://www.example.org",
}, },
"contact_person": [ "contact_person": [
{ {
"given_name": "Roland", "given_name": "Roland",

View File

@@ -731,5 +731,24 @@ def test_assertion_with_noop_attribute_conv():
assert attr.attribute_value[0].text == "Roland" assert attr.attribute_value[0].text == "Roland"
def test_filter_ava_5():
policy = Policy({
"default": {
"lifetime": {"minutes": 15},
#"attribute_restrictions": None # means all I have
"entity_categories": ["swami", "edugain"]
}
})
ava = {"givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com", "dj@example.com"]}
# No restrictions apply
ava = policy.filter(ava, "urn:mace:example.com:saml:curt:sp", [], [])
assert _eq(ava.keys(), ['mail', 'givenName', 'surName'])
assert _eq(ava["mail"], ["derek@nyy.mlb.com", "dj@example.com"])
if __name__ == "__main__": if __name__ == "__main__":
test_assertion_with_noop_attribute_conv() test_assertion_with_noop_attribute_conv()

View File

@@ -12,7 +12,6 @@ from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_ARTIFACT from saml2 import BINDING_HTTP_ARTIFACT
from saml2 import saml from saml2 import saml
from saml2 import sigver
from saml2 import config from saml2 import config
from saml2.attribute_converter import ac_factory from saml2.attribute_converter import ac_factory
from saml2.attribute_converter import d_to_local_name from saml2.attribute_converter import d_to_local_name

View File

@@ -0,0 +1,105 @@
from saml2 import saml, sigver
from saml2 import md
from saml2 import config
from saml2.assertion import Policy
from saml2.attribute_converter import ac_factory
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.extension import dri
from saml2.extension import mdattr
from saml2.extension import ui
from pathutils import full_path
from saml2.mdstore import MetadataStore
import xmldsig
import xmlenc
ONTS = {
saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui,
mdattr.NAMESPACE: mdattr,
dri.NAMESPACE: dri,
ui.NAMESPACE: ui,
idpdisc.NAMESPACE: idpdisc,
md.NAMESPACE: md,
xmldsig.NAMESPACE: xmldsig,
xmlenc.NAMESPACE: xmlenc
}
ATTRCONV = ac_factory(full_path("attributemaps"))
sec_config = config.Config()
sec_config.xmlsec_binary = sigver.get_xmlsec_binary(["/opt/local/bin"])
__author__ = 'rolandh'
MDS = MetadataStore(ONTS.values(), ATTRCONV, sec_config,
disable_ssl_certificate_validation=True)
MDS.imp({"mdfile": [full_path("swamid.md")]})
def _eq(l1, l2):
return set(l1) == set(l2)
def test_filter_ava():
policy = Policy({
"default": {
"lifetime": {"minutes": 15},
#"attribute_restrictions": None # means all I have
"entity_categories": ["swamid"]
}
})
ava = {"givenName": ["Derek"], "surname": ["Jeter"],
"email": ["derek@nyy.mlb.com", "dj@example.com"], "c": ["USA"]}
ava = policy.filter(ava, "https://connect.sunet.se/shibboleth", MDS)
assert _eq(ava.keys(), ['email', 'givenName', 'surname', 'c'])
assert _eq(ava["email"], ["derek@nyy.mlb.com", "dj@example.com"])
def test_filter_ava2():
policy = Policy({
"default": {
"lifetime": {"minutes": 15},
#"attribute_restrictions": None # means all I have
"entity_categories": ["edugain"]
}
})
ava = {"givenName": ["Derek"], "surname": ["Jeter"],
"email": ["derek@nyy.mlb.com"], "c": ["USA"],
"eduPersonTargetedID": "foo!bar!xyz"}
ava = policy.filter(ava, "https://connect.sunet.se/shibboleth", MDS)
# Mismatch, policy deals with eduGAIN, metadata says SWAMID
# So only minimum should come out
assert _eq(ava.keys(), ['eduPersonTargetedID'])
def test_filter_ava3():
policy = Policy({
"default": {
"lifetime": {"minutes": 15},
#"attribute_restrictions": None # means all I have
"entity_categories": ["swamid"]
}
})
mds = MetadataStore(ONTS.values(), ATTRCONV, sec_config,
disable_ssl_certificate_validation=True)
mds.imp({"local": [full_path("entity_cat_sfs_hei.xml")]})
ava = {"givenName": ["Derek"], "surname": ["Jeter"],
"email": ["derek@nyy.mlb.com"], "c": ["USA"],
"eduPersonTargetedID": "foo!bar!xyz",
"norEduPersonNIN": "19800101134"}
ava = policy.filter(ava, "urn:mace:example.com:saml:roland:sp", mds)
assert _eq(ava.keys(), ['eduPersonTargetedID', "norEduPersonNIN"])
if __name__ == "__main__":
test_filter_ava2()

View File

@@ -6,7 +6,7 @@ from saml2.metadata import entity_descriptor
from saml2.metadata import entities_descriptor from saml2.metadata import entities_descriptor
from saml2.metadata import sign_entity_descriptor from saml2.metadata import sign_entity_descriptor
from saml2.sigver import SecurityContext from saml2.sigver import SecurityContext, CryptoBackendXmlSec1
from saml2.sigver import get_xmlsec_cryptobackend from saml2.sigver import get_xmlsec_cryptobackend
from saml2.sigver import get_xmlsec_binary from saml2.sigver import get_xmlsec_binary
from saml2.validate import valid_instance from saml2.validate import valid_instance
@@ -61,9 +61,13 @@ for filespec in args.config:
cnf = Config().load_file(fil, metadata_construction=True) cnf = Config().load_file(fil, metadata_construction=True)
eds.append(entity_descriptor(cnf)) eds.append(entity_descriptor(cnf))
crypto = get_xmlsec_cryptobackend() if not xmlsec:
secc = SecurityContext(crypto, key_file=args.keyfile, crypto = get_xmlsec_cryptobackend()
cert_file=args.cert, debug=1) else:
crypto = CryptoBackendXmlSec1(xmlsec)
secc = SecurityContext(crypto, key_file=args.keyfile, cert_file=args.cert,
debug=1)
if args.id: if args.id:
desc = entities_descriptor(eds, valid_for, args.name, args.id, desc = entities_descriptor(eds, valid_for, args.name, args.id,