Rewrote to use NameID instances every where where I previously used just the text part of the instance.
This commit is contained in:
parent
71246a3829
commit
f295e06ab7
@ -31,7 +31,6 @@ from saml2.saml import AUTHN_PASSWORD
|
|||||||
|
|
||||||
logger = logging.getLogger("saml2.idp")
|
logger = logging.getLogger("saml2.idp")
|
||||||
|
|
||||||
|
|
||||||
def _expiration(timeout, tformat="%a, %d-%b-%Y %H:%M:%S GMT"):
|
def _expiration(timeout, tformat="%a, %d-%b-%Y %H:%M:%S GMT"):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -143,7 +142,9 @@ class Service(object):
|
|||||||
"""
|
"""
|
||||||
Single log out using HTTP_SOAP binding
|
Single log out using HTTP_SOAP binding
|
||||||
"""
|
"""
|
||||||
|
logger.debug("- SOAP -")
|
||||||
_dict = self.unpack_soap()
|
_dict = self.unpack_soap()
|
||||||
|
logger.debug("_dict: %s" % _dict)
|
||||||
return self.operation(_dict, BINDING_SOAP)
|
return self.operation(_dict, BINDING_SOAP)
|
||||||
|
|
||||||
def uri(self):
|
def uri(self):
|
||||||
@ -424,7 +425,9 @@ class SLO(Service):
|
|||||||
def do(self, request, binding, relay_state=""):
|
def do(self, request, binding, relay_state=""):
|
||||||
logger.info("--- Single Log Out Service ---")
|
logger.info("--- Single Log Out Service ---")
|
||||||
try:
|
try:
|
||||||
req_info = IDP.parse_logout_request(request, binding)
|
_, body = request.split("\n")
|
||||||
|
logger.debug("req: '%s'" % body)
|
||||||
|
req_info = IDP.parse_logout_request(body, binding)
|
||||||
except Exception, exc:
|
except Exception, exc:
|
||||||
logger.error("Bad request: %s" % exc)
|
logger.error("Bad request: %s" % exc)
|
||||||
resp = BadRequest("%s" % exc)
|
resp = BadRequest("%s" % exc)
|
||||||
|
@ -79,7 +79,7 @@ CONFIG={
|
|||||||
"name_form": NAME_FORMAT_URI
|
"name_form": NAME_FORMAT_URI
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"subject_data": "./idp.subject.db",
|
"subject_data": "./idp.subject",
|
||||||
"name_id_format": [NAMEID_FORMAT_TRANSIENT,
|
"name_id_format": [NAMEID_FORMAT_TRANSIENT,
|
||||||
NAMEID_FORMAT_PERSISTENT]
|
NAMEID_FORMAT_PERSISTENT]
|
||||||
},
|
},
|
||||||
@ -88,7 +88,7 @@ CONFIG={
|
|||||||
"key_file" : "pki/mykey.pem",
|
"key_file" : "pki/mykey.pem",
|
||||||
"cert_file" : "pki/mycert.pem",
|
"cert_file" : "pki/mycert.pem",
|
||||||
"metadata" : {
|
"metadata" : {
|
||||||
"local": ["../sp.xml"],
|
"local": ["../sp/sp.xml"],
|
||||||
},
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"display_name": "Rolands Identiteter",
|
"display_name": "Rolands Identiteter",
|
||||||
|
@ -14,6 +14,8 @@ from saml2.httputil import Redirect
|
|||||||
logger = logging.getLogger("saml2.SP")
|
logger = logging.getLogger("saml2.SP")
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def dict_to_table(ava, lev=0, width=1):
|
def dict_to_table(ava, lev=0, width=1):
|
||||||
txt = ['<table border=%s bordercolor="black">\n' % width]
|
txt = ['<table border=%s bordercolor="black">\n' % width]
|
||||||
for prop, valarr in ava.items():
|
for prop, valarr in ava.items():
|
||||||
@ -29,12 +31,12 @@ def dict_to_table(ava, lev=0, width=1):
|
|||||||
n = len(valarr)
|
n = len(valarr)
|
||||||
for val in valarr:
|
for val in valarr:
|
||||||
if not i:
|
if not i:
|
||||||
txt.append("<th rowspan=%d>%s</td>\n" % (len(valarr),prop))
|
txt.append("<th rowspan=%d>%s</td>\n" % (len(valarr), prop))
|
||||||
else:
|
else:
|
||||||
txt.append("<tr>\n")
|
txt.append("<tr>\n")
|
||||||
if isinstance(val, dict):
|
if isinstance(val, dict):
|
||||||
txt.append("<td>\n")
|
txt.append("<td>\n")
|
||||||
txt.extend(dict_to_table(val, lev+1, width-1))
|
txt.extend(dict_to_table(val, lev + 1, width - 1))
|
||||||
txt.append("</td>\n")
|
txt.append("</td>\n")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -48,12 +50,13 @@ def dict_to_table(ava, lev=0, width=1):
|
|||||||
elif isinstance(valarr, dict):
|
elif isinstance(valarr, dict):
|
||||||
txt.append("<th>%s</th>\n" % prop)
|
txt.append("<th>%s</th>\n" % prop)
|
||||||
txt.append("<td>\n")
|
txt.append("<td>\n")
|
||||||
txt.extend(dict_to_table(valarr, lev+1, width-1))
|
txt.extend(dict_to_table(valarr, lev + 1, width - 1))
|
||||||
txt.append("</td>\n")
|
txt.append("</td>\n")
|
||||||
txt.append("</tr>\n")
|
txt.append("</tr>\n")
|
||||||
txt.append('</table>\n')
|
txt.append('</table>\n')
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
|
||||||
def _expiration(timeout, tformat=None):
|
def _expiration(timeout, tformat=None):
|
||||||
if timeout == "now":
|
if timeout == "now":
|
||||||
return time_util.instant(tformat)
|
return time_util.instant(tformat)
|
||||||
@ -61,6 +64,7 @@ def _expiration(timeout, tformat=None):
|
|||||||
# validity time should match lifetime of assertions
|
# validity time should match lifetime of assertions
|
||||||
return time_util.in_a_while(minutes=timeout, format=tformat)
|
return time_util.in_a_while(minutes=timeout, format=tformat)
|
||||||
|
|
||||||
|
|
||||||
def delete_cookie(environ, name):
|
def delete_cookie(environ, name):
|
||||||
kaka = environ.get("HTTP_COOKIE", '')
|
kaka = environ.get("HTTP_COOKIE", '')
|
||||||
if kaka:
|
if kaka:
|
||||||
@ -68,13 +72,14 @@ def delete_cookie(environ, name):
|
|||||||
morsel = cookie_obj.get(name, None)
|
morsel = cookie_obj.get(name, None)
|
||||||
cookie = SimpleCookie()
|
cookie = SimpleCookie()
|
||||||
cookie[name] = morsel
|
cookie[name] = morsel
|
||||||
cookie[name]["expires"] =\
|
cookie[name]["expires"] = _expiration("now",
|
||||||
_expiration("now", "%a, %d-%b-%Y %H:%M:%S CET")
|
"%a, %d-%b-%Y %H:%M:%S CET")
|
||||||
return tuple(cookie.output().split(": ", 1))
|
return tuple(cookie.output().split(": ", 1))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def whoami(environ, start_response, user):
|
def whoami(environ, start_response, user):
|
||||||
identity = environ["repoze.who.identity"]["user"]
|
identity = environ["repoze.who.identity"]["user"]
|
||||||
@ -86,17 +91,20 @@ def whoami(environ, start_response, user):
|
|||||||
resp = Response(response)
|
resp = Response(response)
|
||||||
return resp(environ, start_response)
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def not_found(environ, start_response):
|
def not_found(environ, start_response):
|
||||||
"""Called if no URL matches."""
|
"""Called if no URL matches."""
|
||||||
resp = NotFound('Not Found')
|
resp = NotFound('Not Found')
|
||||||
return resp(environ, start_response)
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def not_authn(environ, start_response):
|
def not_authn(environ, start_response):
|
||||||
resp = Unauthorized('Unknown user')
|
resp = Unauthorized('Unknown user')
|
||||||
return resp(environ, start_response)
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def slo(environ, start_response, user):
|
def slo(environ, start_response, user):
|
||||||
# so here I might get either a LogoutResponse or a LogoutRequest
|
# so here I might get either a LogoutResponse or a LogoutRequest
|
||||||
@ -107,8 +115,8 @@ def slo(environ, start_response, user):
|
|||||||
query = parse_qs(environ["QUERY_STRING"])
|
query = parse_qs(environ["QUERY_STRING"])
|
||||||
logger.info("query: %s" % query)
|
logger.info("query: %s" % query)
|
||||||
try:
|
try:
|
||||||
response = sc.parse_logout_request_response(query["SAMLResponse"][0],
|
response = sc.parse_logout_request_response(
|
||||||
binding=BINDING_HTTP_REDIRECT)
|
query["SAMLResponse"][0], binding=BINDING_HTTP_REDIRECT)
|
||||||
if response:
|
if response:
|
||||||
logger.info("LOGOUT response parsed OK")
|
logger.info("LOGOUT response parsed OK")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -125,6 +133,7 @@ def slo(environ, start_response, user):
|
|||||||
resp = Redirect("Successful Logout", headers=headers)
|
resp = Redirect("Successful Logout", headers=headers)
|
||||||
return resp(environ, start_response)
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def logout(environ, start_response, user):
|
def logout(environ, start_response, user):
|
||||||
# This is where it starts when a user wants to log out
|
# This is where it starts when a user wants to log out
|
||||||
@ -150,6 +159,7 @@ def logout(environ, start_response, user):
|
|||||||
# start_response("500 Internal Server Error")
|
# start_response("500 Internal Server Error")
|
||||||
# return []
|
# return []
|
||||||
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def done(environ, start_response, user):
|
def done(environ, start_response, user):
|
||||||
# remove cookie and stored info
|
# remove cookie and stored info
|
||||||
@ -157,7 +167,7 @@ def done(environ, start_response, user):
|
|||||||
subject_id = environ["repoze.who.identity"]['repoze.who.userid']
|
subject_id = environ["repoze.who.identity"]['repoze.who.userid']
|
||||||
client = environ['repoze.who.plugins']["saml2auth"]
|
client = environ['repoze.who.plugins']["saml2auth"]
|
||||||
logger.info("[logout done] remaining subjects: %s" % (
|
logger.info("[logout done] remaining subjects: %s" % (
|
||||||
client.saml_client.users.subjects(),))
|
client.saml_client.users.subjects(),))
|
||||||
|
|
||||||
start_response('200 OK', [('Content-Type', 'text/html')])
|
start_response('200 OK', [('Content-Type', 'text/html')])
|
||||||
return ["<h3>You are now logged out from this service</h3>"]
|
return ["<h3>You are now logged out from this service</h3>"]
|
||||||
@ -175,6 +185,7 @@ urls = [
|
|||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def application(environ, start_response):
|
def application(environ, start_response):
|
||||||
"""
|
"""
|
||||||
The main WSGI application. Dispatch the current request to
|
The main WSGI application. Dispatch the current request to
|
||||||
@ -207,21 +218,23 @@ def application(environ, start_response):
|
|||||||
environ['myapp.url_args'] = path
|
environ['myapp.url_args'] = path
|
||||||
return callback(environ, start_response, user)
|
return callback(environ, start_response, user)
|
||||||
else:
|
else:
|
||||||
return not_authn(environ, start_response)
|
return not_authn(environ, start_response)
|
||||||
|
|
||||||
return not_found(environ, start_response)
|
return not_found(environ, start_response)
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
from repoze.who.config import make_middleware_with_config
|
from repoze.who.config import make_middleware_with_config
|
||||||
|
|
||||||
app_with_auth = make_middleware_with_config(application, {"here":"."},
|
app_with_auth = make_middleware_with_config(application, {"here": "."},
|
||||||
'./who.ini', log_file="repoze_who.log")
|
'./who.ini',
|
||||||
|
log_file="repoze_who.log")
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
PORT = 8087
|
PORT = 8087
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from wsgiref.simple_server import make_server
|
from wsgiref.simple_server import make_server
|
||||||
srv = make_server('localhost', PORT, app_with_auth)
|
srv = make_server('', PORT, app_with_auth)
|
||||||
print "SP listening on port: %s" % PORT
|
print "SP listening on port: %s" % PORT
|
||||||
srv.serve_forever()
|
srv.serve_forever()
|
@ -1,75 +1,18 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"
|
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="http://www.w3.org/2000/09/xmldsig#" entityID="http://localhost:8087/sp.xml"><ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:KeyDescriptor use="signing"><ns1:KeyInfo><ns1:X509Data><ns1:X509Certificate>MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||||
xmlns:ns1="http://www.w3.org/2000/09/xmldsig#">
|
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||||
<ns0:EntityDescriptor entityID="urn:mace:umu.se:saml:roland:sp">
|
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||||
<ns0:SPSSODescriptor AuthnRequestsSigned="false"
|
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||||
WantAssertionsSigned="true"
|
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||||
<ns0:KeyDescriptor>
|
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||||
<ns1:KeyInfo>
|
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||||
<ns1:X509Data>
|
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||||
<ns1:X509Certificate>
|
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||||
MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
</ns1:X509Certificate></ns1:X509Data></ns1:KeyInfo></ns0:KeyDescriptor><ns0:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8087/slo" /><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8087" index="1" /></ns0:SPSSODescriptor><ns0:Organization><ns0:OrganizationName xml:lang="en">Exempel AB</ns0:OrganizationName><ns0:OrganizationDisplayName xml:lang="se">Exempel AB</ns0:OrganizationDisplayName><ns0:OrganizationDisplayName xml:lang="en">Example Co.</ns0:OrganizationDisplayName><ns0:OrganizationURL xml:lang="en">http://www.example.com/roland</ns0:OrganizationURL></ns0:Organization><ns0:ContactPerson contactType="technical"><ns0:GivenName>John</ns0:GivenName><ns0:SurName>Smith</ns0:SurName><ns0:EmailAddress>john.smith@example.com</ns0:EmailAddress></ns0:ContactPerson></ns0:EntityDescriptor>
|
||||||
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
|
||||||
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
|
||||||
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
|
||||||
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
|
||||||
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
|
||||||
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
|
||||||
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
|
||||||
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
|
||||||
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
|
||||||
</ns1:X509Certificate>
|
|
||||||
</ns1:X509Data>
|
|
||||||
</ns1:KeyInfo>
|
|
||||||
</ns0:KeyDescriptor>
|
|
||||||
<ns0:SingleLogoutService
|
|
||||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
||||||
Location="http://localhost:8087/slo"/>
|
|
||||||
<ns0:AssertionConsumerService
|
|
||||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
||||||
Location="http://localhost:8087/" index="1"/>
|
|
||||||
<ns0:AttributeConsumingService index="1">
|
|
||||||
<ns0:ServiceName xml:lang="en">Rolands SP</ns0:ServiceName>
|
|
||||||
<ns0:ServiceDescription xml:lang="en">My SP
|
|
||||||
</ns0:ServiceDescription>
|
|
||||||
<ns0:RequestedAttribute FriendlyName="surname"
|
|
||||||
Name="urn:oid:2.5.4.4"
|
|
||||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
|
||||||
isRequired="true"/>
|
|
||||||
<ns0:RequestedAttribute FriendlyName="givenname"
|
|
||||||
Name="urn:oid:2.5.4.42"
|
|
||||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
|
||||||
isRequired="true"/>
|
|
||||||
<ns0:RequestedAttribute Name="edupersonaffiliation"
|
|
||||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
|
||||||
isRequired="true"/>
|
|
||||||
<ns0:RequestedAttribute FriendlyName="title"
|
|
||||||
Name="urn:oid:2.5.4.12"
|
|
||||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
|
||||||
isRequired="false"/>
|
|
||||||
</ns0:AttributeConsumingService>
|
|
||||||
</ns0:SPSSODescriptor>
|
|
||||||
<ns0:Organization>
|
|
||||||
<ns0:OrganizationName xml:lang="en">Exempel AB
|
|
||||||
</ns0:OrganizationName>
|
|
||||||
<ns0:OrganizationDisplayName xml:lang="se">Exempel AB
|
|
||||||
</ns0:OrganizationDisplayName>
|
|
||||||
<ns0:OrganizationDisplayName xml:lang="en">Example Co.
|
|
||||||
</ns0:OrganizationDisplayName>
|
|
||||||
<ns0:OrganizationURL xml:lang="en">http://www.example.com/roland
|
|
||||||
</ns0:OrganizationURL>
|
|
||||||
</ns0:Organization>
|
|
||||||
<ns0:ContactPerson contactType="technical">
|
|
||||||
<ns0:GivenName>John</ns0:GivenName>
|
|
||||||
<ns0:SurName>Smith</ns0:SurName>
|
|
||||||
<ns0:EmailAddress>john.smith@example.com</ns0:EmailAddress>
|
|
||||||
</ns0:ContactPerson>
|
|
||||||
</ns0:EntityDescriptor>
|
|
||||||
</ns0:EntitiesDescriptor>
|
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT
|
||||||
from saml2.saml import NAME_FORMAT_URI
|
from saml2.saml import NAME_FORMAT_URI
|
||||||
|
|
||||||
BASE= "http://localhost:8087/"
|
BASE= "http://localhost:8087"
|
||||||
|
#BASE= "http://lingon.catalogix.se:8087"
|
||||||
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"entityid" : "urn:mace:umu.se:saml:roland:sp",
|
"entityid" : "%s/sp.xml" % BASE,
|
||||||
"description": "My SP",
|
"description": "My SP",
|
||||||
"service": {
|
"service": {
|
||||||
"sp":{
|
"sp":{
|
||||||
"name" : "Rolands SP",
|
"name" : "Rolands SP",
|
||||||
"endpoints":{
|
"endpoints":{
|
||||||
"assertion_consumer_service": [BASE],
|
"assertion_consumer_service": [BASE],
|
||||||
"single_logout_service" : [(BASE+"slo",
|
"single_logout_service" : [(BASE+"/slo",
|
||||||
BINDING_HTTP_REDIRECT)],
|
BINDING_HTTP_REDIRECT)],
|
||||||
},
|
},
|
||||||
"required_attributes": ["surname", "givenname",
|
"required_attributes": ["surname", "givenname",
|
||||||
"edupersonaffiliation"],
|
"edupersonaffiliation"],
|
||||||
"optional_attributes": ["title"],
|
"optional_attributes": ["title"],
|
||||||
"idp": [ "urn:mace:umu.se:saml:roland:idp"],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debug" : 1,
|
"debug" : 1,
|
||||||
@ -25,7 +25,7 @@ CONFIG = {
|
|||||||
"cert_file" : "pki/mycert.pem",
|
"cert_file" : "pki/mycert.pem",
|
||||||
"attribute_map_dir" : "./attributemaps",
|
"attribute_map_dir" : "./attributemaps",
|
||||||
"metadata" : {
|
"metadata" : {
|
||||||
"local": ["../idp/idp.xml"],
|
"local": ["../idp2/idp.xml"],
|
||||||
},
|
},
|
||||||
# -- below used by make_metadata --
|
# -- below used by make_metadata --
|
||||||
"organization": {
|
"organization": {
|
||||||
|
@ -35,16 +35,13 @@ class AttributeResolver(object):
|
|||||||
self.saml2client = saml2client
|
self.saml2client = saml2client
|
||||||
self.metadata = saml2client.config.metadata
|
self.metadata = saml2client.config.metadata
|
||||||
|
|
||||||
def extend(self, subject_id, issuer, vo_members, name_id_format=None,
|
def extend(self, name_id, issuer, vo_members):
|
||||||
sp_name_qualifier=None, real_id=None):
|
|
||||||
"""
|
"""
|
||||||
:param subject_id: The identifier by which the subject is know
|
:param name_id: The identifier by which the subject is know
|
||||||
among all the participents of the VO
|
among all the participents of the VO
|
||||||
:param issuer: Who am I the poses the query
|
:param issuer: Who am I the poses the query
|
||||||
:param vo_members: The entity IDs of the IdP who I'm going to ask
|
:param vo_members: The entity IDs of the IdP who I'm going to ask
|
||||||
for extra attributes
|
for extra attributes
|
||||||
:param name_id_format: Used to make the IdPs aware of what's going
|
|
||||||
on here
|
|
||||||
:return: A dictionary with all the collected information about the
|
:return: A dictionary with all the collected information about the
|
||||||
subject
|
subject
|
||||||
"""
|
"""
|
||||||
@ -53,17 +50,13 @@ class AttributeResolver(object):
|
|||||||
for ass in self.metadata.attribute_consuming_service(member):
|
for ass in self.metadata.attribute_consuming_service(member):
|
||||||
for attr_serv in ass.attribute_service:
|
for attr_serv in ass.attribute_service:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Send attribute request to %s" % attr_serv.location)
|
"Send attribute request to %s" % attr_serv.location)
|
||||||
if attr_serv.binding != BINDING_SOAP:
|
if attr_serv.binding != BINDING_SOAP:
|
||||||
continue
|
continue
|
||||||
# attribute query assumes SOAP binding
|
# attribute query assumes SOAP binding
|
||||||
session_info = self.saml2client.attribute_query(
|
session_info = self.saml2client.attribute_query(
|
||||||
subject_id,
|
name_id, attr_serv.location, issuer_id=issuer,
|
||||||
attr_serv.location,
|
)
|
||||||
issuer_id=issuer,
|
|
||||||
sp_name_qualifier=sp_name_qualifier,
|
|
||||||
nameid_format=name_id_format,
|
|
||||||
real_id=real_id)
|
|
||||||
if session_info:
|
if session_info:
|
||||||
result.append(session_info)
|
result.append(session_info)
|
||||||
return result
|
return result
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import shelve
|
import shelve
|
||||||
|
from saml2.ident import code, decode
|
||||||
from saml2 import time_util
|
from saml2 import time_util
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -10,12 +11,15 @@ logger = logging.getLogger(__name__)
|
|||||||
# gathered from several different sources, all with their own
|
# gathered from several different sources, all with their own
|
||||||
# timeout time.
|
# timeout time.
|
||||||
|
|
||||||
|
|
||||||
class ToOld(Exception):
|
class ToOld(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CacheError(Exception):
|
class CacheError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
def __init__(self, filename=None):
|
def __init__(self, filename=None):
|
||||||
if filename:
|
if filename:
|
||||||
@ -25,18 +29,25 @@ class Cache(object):
|
|||||||
self._db = {}
|
self._db = {}
|
||||||
self._sync = False
|
self._sync = False
|
||||||
|
|
||||||
def delete(self, subject_id):
|
def delete(self, name_id):
|
||||||
del self._db[subject_id]
|
"""
|
||||||
|
|
||||||
|
:param name_id: The subject identifier, a NameID instance
|
||||||
|
"""
|
||||||
|
del self._db[code(name_id)]
|
||||||
|
|
||||||
if self._sync:
|
if self._sync:
|
||||||
self._db.sync()
|
try:
|
||||||
|
self._db.sync()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_identity(self, subject_id, entities=None,
|
def get_identity(self, name_id, entities=None,
|
||||||
check_not_on_or_after=True):
|
check_not_on_or_after=True):
|
||||||
""" Get all the identity information that has been received and
|
""" Get all the identity information that has been received and
|
||||||
are still valid about the subject.
|
are still valid about the subject.
|
||||||
|
|
||||||
:param subject_id: The identifier of the subject
|
:param name_id: The subject identifier, a NameID instance
|
||||||
:param entities: The identifiers of the entities whoes assertions are
|
:param entities: The identifiers of the entities whoes assertions are
|
||||||
interesting. If the list is empty all entities are interesting.
|
interesting. If the list is empty all entities are interesting.
|
||||||
:return: A 2-tuple consisting of the identity information (a
|
:return: A 2-tuple consisting of the identity information (a
|
||||||
@ -45,7 +56,8 @@ class Cache(object):
|
|||||||
"""
|
"""
|
||||||
if not entities:
|
if not entities:
|
||||||
try:
|
try:
|
||||||
entities = self._db[subject_id].keys()
|
cni = code(name_id)
|
||||||
|
entities = self._db[cni].keys()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return {}, []
|
return {}, []
|
||||||
|
|
||||||
@ -53,7 +65,7 @@ class Cache(object):
|
|||||||
oldees = []
|
oldees = []
|
||||||
for entity_id in entities:
|
for entity_id in entities:
|
||||||
try:
|
try:
|
||||||
info = self.get(subject_id, entity_id, check_not_on_or_after)
|
info = self.get(name_id, entity_id, check_not_on_or_after)
|
||||||
except ToOld:
|
except ToOld:
|
||||||
oldees.append(entity_id)
|
oldees.append(entity_id)
|
||||||
continue
|
continue
|
||||||
@ -70,74 +82,81 @@ class Cache(object):
|
|||||||
res[key] = vals
|
res[key] = vals
|
||||||
return res, oldees
|
return res, oldees
|
||||||
|
|
||||||
def get(self, subject_id, entity_id, check_not_on_or_after=True):
|
def get(self, name_id, entity_id, check_not_on_or_after=True):
|
||||||
""" Get session information about a subject gotten from a
|
""" Get session information about a subject gotten from a
|
||||||
specified IdP/AA.
|
specified IdP/AA.
|
||||||
|
|
||||||
:param subject_id: The identifier of the subject
|
:param name_id: The subject identifier, a NameID instance
|
||||||
:param entity_id: The identifier of the entity_id
|
:param entity_id: The identifier of the entity_id
|
||||||
:param check_not_on_or_after: if True it will check if this
|
:param check_not_on_or_after: if True it will check if this
|
||||||
subject is still valid or if it is too old. Otherwise it
|
subject is still valid or if it is too old. Otherwise it
|
||||||
will not check this. True by default.
|
will not check this. True by default.
|
||||||
:return: The session information
|
:return: The session information
|
||||||
"""
|
"""
|
||||||
(timestamp, info) = self._db[subject_id][entity_id]
|
cni = code(name_id)
|
||||||
|
(timestamp, info) = self._db[cni][entity_id]
|
||||||
if check_not_on_or_after and time_util.after(timestamp):
|
if check_not_on_or_after and time_util.after(timestamp):
|
||||||
raise ToOld("past %s" % timestamp)
|
raise ToOld("past %s" % timestamp)
|
||||||
|
|
||||||
return info or None
|
return info or None
|
||||||
|
|
||||||
def set(self, subject_id, entity_id, info, not_on_or_after=0):
|
def set(self, name_id, entity_id, info, not_on_or_after=0):
|
||||||
""" Stores session information in the cache. Assumes that the subject_id
|
""" Stores session information in the cache. Assumes that the name_id
|
||||||
is unique within the context of the Service Provider.
|
is unique within the context of the Service Provider.
|
||||||
|
|
||||||
:param subject_id: The subject identifier
|
:param name_id: The subject identifier, a NameID instance
|
||||||
:param entity_id: The identifier of the entity_id/receiver of an
|
:param entity_id: The identifier of the entity_id/receiver of an
|
||||||
assertion
|
assertion
|
||||||
:param info: The session info, the assertion is part of this
|
:param info: The session info, the assertion is part of this
|
||||||
:param not_on_or_after: A time after which the assertion is not valid.
|
:param not_on_or_after: A time after which the assertion is not valid.
|
||||||
"""
|
"""
|
||||||
if subject_id not in self._db:
|
cni = code(name_id)
|
||||||
self._db[subject_id] = {}
|
if cni not in self._db:
|
||||||
|
self._db[cni] = {}
|
||||||
|
|
||||||
self._db[subject_id][entity_id] = (not_on_or_after, info)
|
self._db[cni][entity_id] = (not_on_or_after, info)
|
||||||
if self._sync:
|
if self._sync:
|
||||||
self._db.sync()
|
try:
|
||||||
|
self._db.sync()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def reset(self, subject_id, entity_id):
|
def reset(self, name_id, entity_id):
|
||||||
""" Scrap the assertions received from a IdP or an AA about a special
|
""" Scrap the assertions received from a IdP or an AA about a special
|
||||||
subject.
|
subject.
|
||||||
|
|
||||||
:param subject_id: The subjects identifier
|
:param name_id: The subject identifier, a NameID instance
|
||||||
:param entity_id: The identifier of the entity_id of the assertion
|
:param entity_id: The identifier of the entity_id of the assertion
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.set(subject_id, entity_id, {}, 0)
|
self.set(name_id, entity_id, {}, 0)
|
||||||
|
|
||||||
def entities(self, subject_id):
|
def entities(self, name_id):
|
||||||
""" Returns all the entities of assertions for a subject, disregarding
|
""" Returns all the entities of assertions for a subject, disregarding
|
||||||
whether the assertion still is valid or not.
|
whether the assertion still is valid or not.
|
||||||
|
|
||||||
:param subject_id: The identifier of the subject
|
:param name_id: The subject identifier, a NameID instance
|
||||||
:return: A possibly empty list of entity identifiers
|
:return: A possibly empty list of entity identifiers
|
||||||
"""
|
"""
|
||||||
return self._db[subject_id].keys()
|
cni = code(name_id)
|
||||||
|
return self._db[cni].keys()
|
||||||
|
|
||||||
def receivers(self, subject_id):
|
def receivers(self, name_id):
|
||||||
""" Another name for entities() just to make it more logic in the IdP
|
""" Another name for entities() just to make it more logic in the IdP
|
||||||
scenario """
|
scenario """
|
||||||
return self.entities(subject_id)
|
return self.entities(name_id)
|
||||||
|
|
||||||
def active(self, subject_id, entity_id):
|
def active(self, name_id, entity_id):
|
||||||
""" Returns the status of assertions from a specific entity_id.
|
""" Returns the status of assertions from a specific entity_id.
|
||||||
|
|
||||||
:param subject_id: The ID of the subject
|
:param name_id: The ID of the subject
|
||||||
:param entity_id: The entity ID of the entity_id of the assertion
|
:param entity_id: The entity ID of the entity_id of the assertion
|
||||||
:return: True or False depending on if the assertion is still
|
:return: True or False depending on if the assertion is still
|
||||||
valid or not.
|
valid or not.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
(timestamp, info) = self._db[subject_id][entity_id]
|
cni = code(name_id)
|
||||||
|
(timestamp, info) = self._db[cni][entity_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -151,4 +170,4 @@ class Cache(object):
|
|||||||
|
|
||||||
:return: list of subject identifiers
|
:return: list of subject identifiers
|
||||||
"""
|
"""
|
||||||
return self._db.keys()
|
return [decode(c) for c in self._db.keys()]
|
||||||
|
@ -20,7 +20,6 @@ to conclude its tasks.
|
|||||||
"""
|
"""
|
||||||
from saml2.httpbase import HTTPError
|
from saml2.httpbase import HTTPError
|
||||||
from saml2.s_utils import sid
|
from saml2.s_utils import sid
|
||||||
from saml2.samlp import logout_response_from_string
|
|
||||||
import saml2
|
import saml2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -46,6 +45,7 @@ from saml2 import BINDING_SOAP
|
|||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Saml2Client(Base):
|
class Saml2Client(Base):
|
||||||
""" The basic pySAML2 service provider class """
|
""" The basic pySAML2 service provider class """
|
||||||
|
|
||||||
@ -81,12 +81,12 @@ class Saml2Client(Base):
|
|||||||
|
|
||||||
return req.id, info
|
return req.id, info
|
||||||
|
|
||||||
def global_logout(self, subject_id, reason="", expire=None, sign=None):
|
def global_logout(self, name_id, reason="", expire=None, sign=None):
|
||||||
""" More or less a layer of indirection :-/
|
""" More or less a layer of indirection :-/
|
||||||
Bootstrapping the whole thing by finding all the IdPs that should
|
Bootstrapping the whole thing by finding all the IdPs that should
|
||||||
be notified.
|
be notified.
|
||||||
|
|
||||||
:param subject_id: The identifier of the subject that wants to be
|
:param name_id: The identifier of the subject that wants to be
|
||||||
logged out.
|
logged out.
|
||||||
:param reason: Why the subject wants to log out
|
:param reason: Why the subject wants to log out
|
||||||
:param expire: The latest the log out should happen.
|
:param expire: The latest the log out should happen.
|
||||||
@ -99,17 +99,17 @@ class Saml2Client(Base):
|
|||||||
conversation.
|
conversation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info("logout request for: %s" % subject_id)
|
logger.info("logout request for: %s" % name_id)
|
||||||
|
|
||||||
# find out which IdPs/AAs I should notify
|
# find out which IdPs/AAs I should notify
|
||||||
entity_ids = self.users.issuers_of_info(subject_id)
|
entity_ids = self.users.issuers_of_info(name_id)
|
||||||
|
|
||||||
return self.do_logout(subject_id, entity_ids, reason, expire, sign)
|
return self.do_logout(name_id, entity_ids, reason, expire, sign)
|
||||||
|
|
||||||
def do_logout(self, subject_id, entity_ids, reason, expire, sign=None):
|
def do_logout(self, name_id, entity_ids, reason, expire, sign=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param subject_id: Identifier of the Subject
|
:param name_id: Identifier of the Subject a NameID instance
|
||||||
:param entity_ids: List of entity ids for the IdPs that have provided
|
:param entity_ids: List of entity ids for the IdPs that have provided
|
||||||
information concerning the subject
|
information concerning the subject
|
||||||
:param reason: The reason for doing the logout
|
:param reason: The reason for doing the logout
|
||||||
@ -118,34 +118,33 @@ class Saml2Client(Base):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# check time
|
# check time
|
||||||
if not not_on_or_after(expire): # I've run out of time
|
if not not_on_or_after(expire): # I've run out of time
|
||||||
# Do the local logout anyway
|
# Do the local logout anyway
|
||||||
self.local_logout(subject_id)
|
self.local_logout(name_id)
|
||||||
return 0, "504 Gateway Timeout", [], []
|
return 0, "504 Gateway Timeout", [], []
|
||||||
|
|
||||||
# for all where I can use the SOAP binding, do those first
|
|
||||||
not_done = entity_ids[:]
|
not_done = entity_ids[:]
|
||||||
responses = {}
|
responses = {}
|
||||||
|
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
response = False
|
logger.debug("Logout from '%s'" % entity_id)
|
||||||
|
# for all where I can use the SOAP binding, do those first
|
||||||
for binding in [BINDING_SOAP,
|
for binding in [BINDING_SOAP, BINDING_HTTP_POST,
|
||||||
BINDING_HTTP_POST,
|
|
||||||
BINDING_HTTP_REDIRECT]:
|
BINDING_HTTP_REDIRECT]:
|
||||||
srvs = self.metadata.single_logout_service(entity_id, binding,
|
srvs = self.metadata.single_logout_service(entity_id, binding,
|
||||||
"idpsso")
|
"idpsso")
|
||||||
if not srvs:
|
if not srvs:
|
||||||
|
logger.debug("No SLO '%s' service" % binding)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
destination = destinations(srvs)[0]
|
destination = destinations(srvs)[0]
|
||||||
|
|
||||||
logger.info("destination to provider: %s" % destination)
|
logger.info("destination to provider: %s" % destination)
|
||||||
request = self.create_logout_request(destination, entity_id,
|
request = self.create_logout_request(destination, entity_id,
|
||||||
subject_id, reason=reason,
|
name_id=name_id,
|
||||||
|
reason=reason,
|
||||||
expire=expire)
|
expire=expire)
|
||||||
|
|
||||||
to_sign = []
|
#to_sign = []
|
||||||
if binding.startswith("http://"):
|
if binding.startswith("http://"):
|
||||||
sign = True
|
sign = True
|
||||||
|
|
||||||
@ -160,28 +159,28 @@ class Saml2Client(Base):
|
|||||||
relay_state = self._relay_state(request.id)
|
relay_state = self._relay_state(request.id)
|
||||||
|
|
||||||
http_info = self.apply_binding(binding, srequest, destination,
|
http_info = self.apply_binding(binding, srequest, destination,
|
||||||
relay_state)
|
relay_state)
|
||||||
|
|
||||||
if binding == BINDING_SOAP:
|
if binding == BINDING_SOAP:
|
||||||
if response:
|
response = self.send(**http_info)
|
||||||
logger.info("Verifying response")
|
|
||||||
response = self.send(**http_info)
|
|
||||||
|
|
||||||
if response:
|
if response and response.status_code == 200:
|
||||||
not_done.remove(entity_id)
|
not_done.remove(entity_id)
|
||||||
logger.info("OK response from %s" % destination)
|
response = response.text
|
||||||
responses[entity_id] = logout_response_from_string(response)
|
logger.info("Response: %s" % response)
|
||||||
|
res = self.parse_logout_request_response(response)
|
||||||
|
responses[entity_id] = res
|
||||||
else:
|
else:
|
||||||
logger.info("NOT OK response from %s" % destination)
|
logger.info("NOT OK response from %s" % destination)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.state[request.id] = {"entity_id": entity_id,
|
self.state[request.id] = {"entity_id": entity_id,
|
||||||
"operation": "SLO",
|
"operation": "SLO",
|
||||||
"entity_ids": entity_ids,
|
"entity_ids": entity_ids,
|
||||||
"subject_id": subject_id,
|
"name_id": name_id,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
"not_on_of_after": expire,
|
"not_on_of_after": expire,
|
||||||
"sign": sign}
|
"sign": sign}
|
||||||
|
|
||||||
responses[entity_id] = (binding, http_info)
|
responses[entity_id] = (binding, http_info)
|
||||||
not_done.remove(entity_id)
|
not_done.remove(entity_id)
|
||||||
@ -217,9 +216,9 @@ class Saml2Client(Base):
|
|||||||
issuer = response.issuer()
|
issuer = response.issuer()
|
||||||
logger.info("issuer: %s" % issuer)
|
logger.info("issuer: %s" % issuer)
|
||||||
del self.state[response.in_response_to]
|
del self.state[response.in_response_to]
|
||||||
if status["entity_ids"] == [issuer]: # done
|
if status["entity_ids"] == [issuer]: # done
|
||||||
self.local_logout(status["subject_id"])
|
self.local_logout(status["subject_id"])
|
||||||
return 0, "200 Ok", [("Content-type","text/html")], []
|
return 0, "200 Ok", [("Content-type", "text/html")], []
|
||||||
else:
|
else:
|
||||||
status["entity_ids"].remove(issuer)
|
status["entity_ids"].remove(issuer)
|
||||||
return self.do_logout(status["subject_id"], status["entity_ids"],
|
return self.do_logout(status["subject_id"], status["entity_ids"],
|
||||||
@ -277,16 +276,15 @@ class Saml2Client(Base):
|
|||||||
consent=None, extensions=None, sign=False):
|
consent=None, extensions=None, sign=False):
|
||||||
|
|
||||||
subject = saml.Subject(
|
subject = saml.Subject(
|
||||||
name_id = saml.NameID(text=subject_id,
|
name_id=saml.NameID(text=subject_id, format=nameid_format,
|
||||||
format=nameid_format,
|
sp_name_qualifier=sp_name_qualifier,
|
||||||
sp_name_qualifier=sp_name_qualifier,
|
name_qualifier=name_qualifier))
|
||||||
name_qualifier=name_qualifier))
|
|
||||||
|
|
||||||
srvs = self.metadata.authz_service(entity_id, BINDING_SOAP)
|
srvs = self.metadata.authz_service(entity_id, BINDING_SOAP)
|
||||||
for dest in destinations(srvs):
|
for dest in destinations(srvs):
|
||||||
resp = self._use_soap(dest, "authz_decision_query",
|
resp = self._use_soap(dest, "authz_decision_query",
|
||||||
action=action, evidence=evidence,
|
action=action, evidence=evidence,
|
||||||
resource=resource, subject=subject)
|
resource=resource, subject=subject)
|
||||||
if resp:
|
if resp:
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@ -308,8 +306,8 @@ class Saml2Client(Base):
|
|||||||
|
|
||||||
for destination in destinations(srvs):
|
for destination in destinations(srvs):
|
||||||
res = self._use_soap(destination, "assertion_id_request",
|
res = self._use_soap(destination, "assertion_id_request",
|
||||||
assertion_id_refs=_id_refs, consent=consent,
|
assertion_id_refs=_id_refs, consent=consent,
|
||||||
extensions=extensions, sign=sign)
|
extensions=extensions, sign=sign)
|
||||||
if res:
|
if res:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -321,9 +319,8 @@ class Saml2Client(Base):
|
|||||||
srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP)
|
srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP)
|
||||||
|
|
||||||
for destination in destinations(srvs):
|
for destination in destinations(srvs):
|
||||||
resp = self._use_soap(destination, "authn_query",
|
resp = self._use_soap(destination, "authn_query", consent=consent,
|
||||||
consent=consent, extensions=extensions,
|
extensions=extensions, sign=sign)
|
||||||
sign=sign)
|
|
||||||
if resp:
|
if resp:
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@ -339,7 +336,8 @@ class Saml2Client(Base):
|
|||||||
|
|
||||||
:param entityid: To whom the query should be sent
|
:param entityid: To whom the query should be sent
|
||||||
:param subject_id: The identifier of the subject
|
:param subject_id: The identifier of the subject
|
||||||
:param attribute: A dictionary of attributes and values that is asked for
|
:param attribute: A dictionary of attributes and values that is
|
||||||
|
asked for
|
||||||
:param sp_name_qualifier: The unique identifier of the
|
:param sp_name_qualifier: The unique identifier of the
|
||||||
service provider or affiliation of providers for whom the
|
service provider or affiliation of providers for whom the
|
||||||
identifier was generated.
|
identifier was generated.
|
||||||
@ -353,7 +351,6 @@ class Saml2Client(Base):
|
|||||||
HTTP args if BINDING_HTT_POST was used.
|
HTTP args if BINDING_HTT_POST was used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
if real_id:
|
if real_id:
|
||||||
response_args = {"real_id": real_id}
|
response_args = {"real_id": real_id}
|
||||||
else:
|
else:
|
||||||
|
@ -78,23 +78,28 @@ ECP_SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
|||||||
ACTOR = "http://schemas.xmlsoap.org/soap/actor/next"
|
ACTOR = "http://schemas.xmlsoap.org/soap/actor/next"
|
||||||
MIME_PAOS = "application/vnd.paos+xml"
|
MIME_PAOS = "application/vnd.paos+xml"
|
||||||
|
|
||||||
|
|
||||||
class IdpUnspecified(Exception):
|
class IdpUnspecified(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VerifyError(Exception):
|
class VerifyError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LogoutError(Exception):
|
class LogoutError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoServiceDefined(Exception):
|
class NoServiceDefined(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Base(Entity):
|
class Base(Entity):
|
||||||
""" The basic pySAML2 service provider class """
|
""" The basic pySAML2 service provider class """
|
||||||
|
|
||||||
def __init__(self, config=None, identity_cache=None, state_cache=None,
|
def __init__(self, config=None, identity_cache=None, state_cache=None,
|
||||||
virtual_organization="",config_file=""):
|
virtual_organization="", config_file=""):
|
||||||
"""
|
"""
|
||||||
:param config: A saml2.config.Config instance
|
:param config: A saml2.config.Config instance
|
||||||
:param identity_cache: Where the class should store identity information
|
:param identity_cache: Where the class should store identity information
|
||||||
@ -108,12 +113,12 @@ class Base(Entity):
|
|||||||
|
|
||||||
# for server state storage
|
# for server state storage
|
||||||
if state_cache is None:
|
if state_cache is None:
|
||||||
self.state = {} # in memory storage
|
self.state = {} # in memory storage
|
||||||
else:
|
else:
|
||||||
self.state = state_cache
|
self.state = state_cache
|
||||||
|
|
||||||
for foo in ["allow_unsolicited", "authn_requests_signed",
|
for foo in ["allow_unsolicited", "authn_requests_signed",
|
||||||
"logout_requests_signed"]:
|
"logout_requests_signed"]:
|
||||||
if self.config.getattr("sp", foo) == 'true':
|
if self.config.getattr("sp", foo) == 'true':
|
||||||
setattr(self, foo, True)
|
setattr(self, foo, True)
|
||||||
else:
|
else:
|
||||||
@ -166,25 +171,25 @@ class Base(Entity):
|
|||||||
# Public API
|
# Public API
|
||||||
#
|
#
|
||||||
|
|
||||||
def add_vo_information_about_user(self, subject_id):
|
def add_vo_information_about_user(self, name_id):
|
||||||
""" Add information to the knowledge I have about the user. This is
|
""" Add information to the knowledge I have about the user. This is
|
||||||
for Virtual organizations.
|
for Virtual organizations.
|
||||||
|
|
||||||
:param subject_id: The subject identifier
|
:param name_id: The subject identifier
|
||||||
:return: A possibly extended knowledge.
|
:return: A possibly extended knowledge.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ava = {}
|
ava = {}
|
||||||
try:
|
try:
|
||||||
(ava, _) = self.users.get_identity(subject_id)
|
(ava, _) = self.users.get_identity(name_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# is this a Virtual Organization situation
|
# is this a Virtual Organization situation
|
||||||
if self.vorg:
|
if self.vorg:
|
||||||
if self.vorg.do_aggregation(subject_id):
|
if self.vorg.do_aggregation(name_id):
|
||||||
# Get the extended identity
|
# Get the extended identity
|
||||||
ava = self.users.get_identity(subject_id)[0]
|
ava = self.users.get_identity(name_id)[0]
|
||||||
return ava
|
return ava
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
@ -228,7 +233,8 @@ class Base(Entity):
|
|||||||
|
|
||||||
args = {}
|
args = {}
|
||||||
try:
|
try:
|
||||||
args["assertion_consumer_service_url"] = kwargs["assertion_consumer_service_url"]
|
args["assertion_consumer_service_url"] = kwargs[
|
||||||
|
"assertion_consumer_service_url"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if service_url_binding is None:
|
if service_url_binding is None:
|
||||||
service_url = self.service_url(binding)
|
service_url = self.service_url(binding)
|
||||||
@ -247,16 +253,17 @@ class Base(Entity):
|
|||||||
try:
|
try:
|
||||||
args["name_id_policy"] = kwargs["name_id_policy"]
|
args["name_id_policy"] = kwargs["name_id_policy"]
|
||||||
del kwargs["name_id_policy"]
|
del kwargs["name_id_policy"]
|
||||||
except:
|
except KeyError:
|
||||||
if allow_create:
|
if allow_create:
|
||||||
allow_create="true"
|
allow_create = "true"
|
||||||
else:
|
else:
|
||||||
allow_create="false"
|
allow_create = "false"
|
||||||
|
|
||||||
# Profile stuff, should be configurable
|
# Profile stuff, should be configurable
|
||||||
if nameid_format is None or nameid_format == NAMEID_FORMAT_TRANSIENT:
|
if nameid_format is None or \
|
||||||
name_id_policy = samlp.NameIDPolicy(allow_create=allow_create,
|
nameid_format == NAMEID_FORMAT_TRANSIENT:
|
||||||
format=NAMEID_FORMAT_TRANSIENT)
|
name_id_policy = samlp.NameIDPolicy(
|
||||||
|
allow_create=allow_create, format=NAMEID_FORMAT_TRANSIENT)
|
||||||
else:
|
else:
|
||||||
name_id_policy = samlp.NameIDPolicy(allow_create=allow_create,
|
name_id_policy = samlp.NameIDPolicy(allow_create=allow_create,
|
||||||
format=nameid_format)
|
format=nameid_format)
|
||||||
@ -272,28 +279,31 @@ class Base(Entity):
|
|||||||
if kwargs:
|
if kwargs:
|
||||||
if extensions is None:
|
if extensions is None:
|
||||||
extensions = []
|
extensions = []
|
||||||
fargs = [p for p,c,r in AuthnRequest.c_attributes.values()]
|
fargs = [p for p, c, r in AuthnRequest.c_attributes.values()]
|
||||||
fargs.extend([p for p,c in AuthnRequest.c_children.values()])
|
fargs.extend([p for p, c in AuthnRequest.c_children.values()])
|
||||||
for key,val in kwargs.items():
|
for key, val in kwargs.items():
|
||||||
if key not in fargs:
|
if key not in fargs:
|
||||||
# extension elements allowed
|
# extension elements allowed
|
||||||
extensions.append(saml2.element_to_extension_element(val))
|
extensions.append(saml2.element_to_extension_element(val))
|
||||||
else:
|
else:
|
||||||
args[key] = val
|
args[key] = val
|
||||||
|
try:
|
||||||
|
del args["id"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
return self._message(AuthnRequest, destination, sid, consent,
|
return self._message(AuthnRequest, destination, sid, consent,
|
||||||
extensions, sign,
|
extensions, sign,
|
||||||
protocol_binding=binding,
|
protocol_binding=binding,
|
||||||
scoping=scoping, **args)
|
scoping=scoping, **args)
|
||||||
|
|
||||||
|
|
||||||
def create_attribute_query(self, destination, name_id=None,
|
def create_attribute_query(self, destination, name_id=None,
|
||||||
attribute=None, sid=0, consent=None,
|
attribute=None, sid=0, consent=None,
|
||||||
extensions=None, sign=False, **kwargs):
|
extensions=None, sign=False, **kwargs):
|
||||||
""" Constructs an AttributeQuery
|
""" Constructs an AttributeQuery
|
||||||
|
|
||||||
:param destination: To whom the query should be sent
|
:param destination: To whom the query should be sent
|
||||||
:param subject_id: The identifier of the subject
|
:param name_id: The identifier of the subject
|
||||||
:param attribute: A dictionary of attributes and values that is
|
:param attribute: A dictionary of attributes and values that is
|
||||||
asked for. The key are one of 4 variants:
|
asked for. The key are one of 4 variants:
|
||||||
3-tuple of name_format,name and friendly_name,
|
3-tuple of name_format,name and friendly_name,
|
||||||
@ -333,7 +343,7 @@ class Base(Entity):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
subject = saml.Subject(name_id = name_id)
|
subject = saml.Subject(name_id=name_id)
|
||||||
|
|
||||||
if attribute:
|
if attribute:
|
||||||
attribute = do_attributes(attribute)
|
attribute = do_attributes(attribute)
|
||||||
@ -342,11 +352,9 @@ class Base(Entity):
|
|||||||
extensions, sign, subject=subject,
|
extensions, sign, subject=subject,
|
||||||
attribute=attribute)
|
attribute=attribute)
|
||||||
|
|
||||||
|
|
||||||
# MUST use SOAP for
|
# MUST use SOAP for
|
||||||
# AssertionIDRequest, SubjectQuery,
|
# AssertionIDRequest, SubjectQuery,
|
||||||
# AuthnQuery, AttributeQuery, or AuthzDecisionQuery
|
# AuthnQuery, AttributeQuery, or AuthzDecisionQuery
|
||||||
|
|
||||||
def create_authz_decision_query(self, destination, action,
|
def create_authz_decision_query(self, destination, action,
|
||||||
evidence=None, resource=None, subject=None,
|
evidence=None, resource=None, subject=None,
|
||||||
sid=0, consent=None, extensions=None,
|
sid=0, consent=None, extensions=None,
|
||||||
@ -369,8 +377,9 @@ class Base(Entity):
|
|||||||
extensions, sign, action=action, evidence=evidence,
|
extensions, sign, action=action, evidence=evidence,
|
||||||
resource=resource, subject=subject)
|
resource=resource, subject=subject)
|
||||||
|
|
||||||
def create_authz_decision_query_using_assertion(self, destination, assertion,
|
def create_authz_decision_query_using_assertion(self, destination,
|
||||||
action=None, resource=None,
|
assertion, action=None,
|
||||||
|
resource=None,
|
||||||
subject=None, sid=0,
|
subject=None, sid=0,
|
||||||
consent=None,
|
consent=None,
|
||||||
extensions=None,
|
extensions=None,
|
||||||
@ -397,14 +406,10 @@ class Base(Entity):
|
|||||||
else:
|
else:
|
||||||
_action = None
|
_action = None
|
||||||
|
|
||||||
return self.create_authz_decision_query(destination,
|
return self.create_authz_decision_query(
|
||||||
_action,
|
destination, _action, saml.Evidence(assertion=assertion),
|
||||||
saml.Evidence(assertion=assertion),
|
resource, subject, sid=sid, consent=consent, extensions=extensions,
|
||||||
resource, subject,
|
sign=sign)
|
||||||
sid=sid,
|
|
||||||
consent=consent,
|
|
||||||
extensions=extensions,
|
|
||||||
sign=sign)
|
|
||||||
|
|
||||||
def create_assertion_id_request(self, assertion_id_refs, **kwargs):
|
def create_assertion_id_request(self, assertion_id_refs, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -442,10 +447,10 @@ class Base(Entity):
|
|||||||
requested_authn_context=authn_context)
|
requested_authn_context=authn_context)
|
||||||
|
|
||||||
def create_name_id_mapping_request(self, name_id_policy,
|
def create_name_id_mapping_request(self, name_id_policy,
|
||||||
name_id=None, base_id=None,
|
name_id=None, base_id=None,
|
||||||
encrypted_id=None, destination=None,
|
encrypted_id=None, destination=None,
|
||||||
sid=0, consent=None, extensions=None,
|
sid=0, consent=None, extensions=None,
|
||||||
sign=False):
|
sign=False):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param name_id_policy:
|
:param name_id_policy:
|
||||||
@ -464,16 +469,17 @@ class Base(Entity):
|
|||||||
assert name_id or base_id or encrypted_id
|
assert name_id or base_id or encrypted_id
|
||||||
|
|
||||||
if name_id:
|
if name_id:
|
||||||
return self._message(NameIDMappingRequest, destination, sid, consent,
|
return self._message(NameIDMappingRequest, destination, sid,
|
||||||
extensions, sign, name_id_policy=name_id_policy,
|
consent, extensions, sign,
|
||||||
name_id=name_id)
|
name_id_policy=name_id_policy, name_id=name_id)
|
||||||
elif base_id:
|
elif base_id:
|
||||||
return self._message(NameIDMappingRequest, destination, sid, consent,
|
return self._message(NameIDMappingRequest, destination, sid,
|
||||||
extensions, sign, name_id_policy=name_id_policy,
|
consent, extensions, sign,
|
||||||
base_id=base_id)
|
name_id_policy=name_id_policy, base_id=base_id)
|
||||||
else:
|
else:
|
||||||
return self._message(NameIDMappingRequest, destination, sid, consent,
|
return self._message(NameIDMappingRequest, destination, sid,
|
||||||
extensions, sign, name_id_policy=name_id_policy,
|
consent, extensions, sign,
|
||||||
|
name_id_policy=name_id_policy,
|
||||||
encrypted_id=encrypted_id)
|
encrypted_id=encrypted_id)
|
||||||
|
|
||||||
# ======== response handling ===========
|
# ======== response handling ===========
|
||||||
@ -549,7 +555,7 @@ class Base(Entity):
|
|||||||
"attribute_converters": self.config.attribute_converters}
|
"attribute_converters": self.config.attribute_converters}
|
||||||
|
|
||||||
res = self._parse_response(response, AssertionIDResponse, "", binding,
|
res = self._parse_response(response, AssertionIDResponse, "", binding,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
@ -594,7 +600,7 @@ class Base(Entity):
|
|||||||
#
|
#
|
||||||
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
||||||
response_consumer_url=my_url,
|
response_consumer_url=my_url,
|
||||||
service = ECP_SERVICE)
|
service=ECP_SERVICE)
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# <ecp:RelayState>
|
# <ecp:RelayState>
|
||||||
@ -622,16 +628,16 @@ class Base(Entity):
|
|||||||
# SingleSignOnService
|
# SingleSignOnService
|
||||||
_, location = self.pick_binding("single_sign_on_service",
|
_, location = self.pick_binding("single_sign_on_service",
|
||||||
[_binding], entity_id=entityid)
|
[_binding], entity_id=entityid)
|
||||||
authn_req = self.create_authn_request(location,
|
authn_req = self.create_authn_request(
|
||||||
service_url_binding=BINDING_PAOS,
|
location, service_url_binding=BINDING_PAOS, **kwargs)
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# The SOAP envelope
|
# The SOAP envelope
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
||||||
soap_envelope = make_soap_enveloped_saml_thingy(authn_req,[paos_request,
|
soap_envelope = make_soap_enveloped_saml_thingy(authn_req,
|
||||||
relay_state])
|
[paos_request,
|
||||||
|
relay_state])
|
||||||
|
|
||||||
return authn_req.id, "%s" % soap_envelope
|
return authn_req.id, "%s" % soap_envelope
|
||||||
|
|
||||||
@ -644,7 +650,7 @@ class Base(Entity):
|
|||||||
_relay_state = None
|
_relay_state = None
|
||||||
for item in rdict["header"]:
|
for item in rdict["header"]:
|
||||||
if item.c_tag == "RelayState" and\
|
if item.c_tag == "RelayState" and\
|
||||||
item.c_namespace == ecp.NAMESPACE:
|
item.c_namespace == ecp.NAMESPACE:
|
||||||
_relay_state = item
|
_relay_state = item
|
||||||
|
|
||||||
response = self.parse_authn_request_response(rdict["body"],
|
response = self.parse_authn_request_response(rdict["body"],
|
||||||
|
@ -140,9 +140,9 @@ class HTTPBase(object):
|
|||||||
if morsel["max-age"]:
|
if morsel["max-age"]:
|
||||||
std_attr["expires"] = _since_epoch(morsel["max-age"])
|
std_attr["expires"] = _since_epoch(morsel["max-age"])
|
||||||
|
|
||||||
for att, set in PAIRS.items():
|
for att, item in PAIRS.items():
|
||||||
if std_attr[att]:
|
if std_attr[att]:
|
||||||
std_attr[set] = True
|
std_attr[item] = True
|
||||||
|
|
||||||
if std_attr["domain"] and std_attr["domain"].startswith("."):
|
if std_attr["domain"] and std_attr["domain"].startswith("."):
|
||||||
std_attr["domain_initial_dot"] = True
|
std_attr["domain_initial_dot"] = True
|
||||||
|
@ -19,9 +19,11 @@ logger = logging.getLogger(__name__)
|
|||||||
ATTR = ["name_qualifier", "sp_name_qualifier", "format", "sp_provided_id",
|
ATTR = ["name_qualifier", "sp_name_qualifier", "format", "sp_provided_id",
|
||||||
"text"]
|
"text"]
|
||||||
|
|
||||||
|
|
||||||
class Unknown(Exception):
|
class Unknown(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def code(item):
|
def code(item):
|
||||||
_res = []
|
_res = []
|
||||||
i = 0
|
i = 0
|
||||||
@ -32,13 +34,15 @@ def code(item):
|
|||||||
i += 1
|
i += 1
|
||||||
return ",".join(_res)
|
return ",".join(_res)
|
||||||
|
|
||||||
def decode(str):
|
|
||||||
|
def decode(txt):
|
||||||
_nid = NameID()
|
_nid = NameID()
|
||||||
for part in str.split(","):
|
for part in txt.split(","):
|
||||||
i, val = part.split("=")
|
i, val = part.split("=")
|
||||||
setattr(_nid, ATTR[int(i)], unquote(val))
|
setattr(_nid, ATTR[int(i)], unquote(val))
|
||||||
return _nid
|
return _nid
|
||||||
|
|
||||||
|
|
||||||
class IdentDB(object):
|
class IdentDB(object):
|
||||||
""" A class that handles identifiers of entities
|
""" A class that handles identifiers of entities
|
||||||
Keeps a list of all nameIDs returned per SP
|
Keeps a list of all nameIDs returned per SP
|
||||||
@ -51,19 +55,19 @@ class IdentDB(object):
|
|||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.name_qualifier = name_qualifier
|
self.name_qualifier = name_qualifier
|
||||||
|
|
||||||
def _create_id(self, format, name_qualifier="", sp_name_qualifier=""):
|
def _create_id(self, nformat, name_qualifier="", sp_name_qualifier=""):
|
||||||
_id = sha256(rndstr(32))
|
_id = sha256(rndstr(32))
|
||||||
_id.update(format)
|
_id.update(nformat)
|
||||||
if name_qualifier:
|
if name_qualifier:
|
||||||
_id.update(name_qualifier)
|
_id.update(name_qualifier)
|
||||||
if sp_name_qualifier:
|
if sp_name_qualifier:
|
||||||
_id.update(sp_name_qualifier)
|
_id.update(sp_name_qualifier)
|
||||||
return _id.hexdigest()
|
return _id.hexdigest()
|
||||||
|
|
||||||
def create_id(self, format, name_qualifier="", sp_name_qualifier=""):
|
def create_id(self, nformat, name_qualifier="", sp_name_qualifier=""):
|
||||||
_id = self._create_id(format, name_qualifier, sp_name_qualifier)
|
_id = self._create_id(nformat, name_qualifier, sp_name_qualifier)
|
||||||
while _id in self.db:
|
while _id in self.db:
|
||||||
_id = self._create_id(format, name_qualifier, sp_name_qualifier)
|
_id = self._create_id(nformat, name_qualifier, sp_name_qualifier)
|
||||||
return _id
|
return _id
|
||||||
|
|
||||||
def store(self, ident, name_id):
|
def store(self, ident, name_id):
|
||||||
@ -92,30 +96,30 @@ class IdentDB(object):
|
|||||||
|
|
||||||
del self.db[_cn]
|
del self.db[_cn]
|
||||||
|
|
||||||
def remove_local(self, id):
|
def remove_local(self, sid):
|
||||||
if isinstance(id, unicode):
|
if isinstance(sid, unicode):
|
||||||
id = id.encode("utf-8")
|
sid = sid.encode("utf-8")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for val in self.db[id].split(" "):
|
for val in self.db[sid].split(" "):
|
||||||
try:
|
try:
|
||||||
del self.db[val]
|
del self.db[val]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
del self.db[id]
|
del self.db[sid]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_nameid(self, userid, format, sp_name_qualifier, name_qualifier):
|
def get_nameid(self, userid, nformat, sp_name_qualifier, name_qualifier):
|
||||||
_id = self.create_id(format, name_qualifier, sp_name_qualifier)
|
_id = self.create_id(nformat, name_qualifier, sp_name_qualifier)
|
||||||
|
|
||||||
if format == NAMEID_FORMAT_EMAILADDRESS:
|
if nformat == NAMEID_FORMAT_EMAILADDRESS:
|
||||||
if not self.domain:
|
if not self.domain:
|
||||||
raise Exception("Can't issue email nameids, unknown domain")
|
raise Exception("Can't issue email nameids, unknown domain")
|
||||||
|
|
||||||
_id = "%s@%s" % (_id, self.domain)
|
_id = "%s@%s" % (_id, self.domain)
|
||||||
|
|
||||||
nameid = NameID(format=format, sp_name_qualifier=sp_name_qualifier,
|
nameid = NameID(format=nformat, sp_name_qualifier=sp_name_qualifier,
|
||||||
name_qualifier=name_qualifier, text=_id)
|
name_qualifier=name_qualifier, text=_id)
|
||||||
|
|
||||||
self.store(userid, nameid)
|
self.store(userid, nameid)
|
||||||
@ -150,8 +154,9 @@ class IdentDB(object):
|
|||||||
if not name_qualifier:
|
if not name_qualifier:
|
||||||
name_qualifier = self.name_qualifier
|
name_qualifier = self.name_qualifier
|
||||||
|
|
||||||
return {"format":nameid_format, "sp_name_qualifier": sp_name_qualifier,
|
return {"nformat": nameid_format,
|
||||||
"name_qualifier":name_qualifier}
|
"sp_name_qualifier": sp_name_qualifier,
|
||||||
|
"name_qualifier": name_qualifier}
|
||||||
|
|
||||||
def construct_nameid(self, userid, local_policy=None,
|
def construct_nameid(self, userid, local_policy=None,
|
||||||
sp_name_qualifier=None, name_id_policy=None,
|
sp_name_qualifier=None, name_id_policy=None,
|
||||||
@ -175,7 +180,8 @@ class IdentDB(object):
|
|||||||
return self.get_nameid(userid, NAMEID_FORMAT_TRANSIENT,
|
return self.get_nameid(userid, NAMEID_FORMAT_TRANSIENT,
|
||||||
sp_name_qualifier, name_qualifier)
|
sp_name_qualifier, name_qualifier)
|
||||||
|
|
||||||
def persistent_nameid(self, userid, sp_name_qualifier="", name_qualifier=""):
|
def persistent_nameid(self, userid, sp_name_qualifier="",
|
||||||
|
name_qualifier=""):
|
||||||
nameid = self.match_local_id(userid, sp_name_qualifier, name_qualifier)
|
nameid = self.match_local_id(userid, sp_name_qualifier, name_qualifier)
|
||||||
if nameid:
|
if nameid:
|
||||||
return nameid
|
return nameid
|
||||||
@ -194,6 +200,8 @@ class IdentDB(object):
|
|||||||
try:
|
try:
|
||||||
return self.db[code(name_id)]
|
return self.db[code(name_id)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
logger.debug("name: %s" % code(name_id))
|
||||||
|
logger.debug("id keys: %s" % self.db.keys())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def match_local_id(self, userid, sp_name_qualifier, name_qualifier):
|
def match_local_id(self, userid, sp_name_qualifier, name_qualifier):
|
||||||
|
@ -154,9 +154,11 @@ def make_soap_enveloped_saml_thingy(thingy, header_parts=None):
|
|||||||
|
|
||||||
if isinstance(thingy, basestring):
|
if isinstance(thingy, basestring):
|
||||||
# remove the first XML version/encoding line
|
# remove the first XML version/encoding line
|
||||||
|
logger.debug("thingy0: %s" % thingy)
|
||||||
_part = thingy.split("\n")
|
_part = thingy.split("\n")
|
||||||
thingy = _part[1]
|
thingy = "".join(_part[1:])
|
||||||
thingy = thingy.replace(PREFIX, "")
|
thingy = thingy.replace(PREFIX, "")
|
||||||
|
logger.debug("thingy: %s" % thingy)
|
||||||
_child = ElementTree.Element('')
|
_child = ElementTree.Element('')
|
||||||
_child.tag = '{%s}FuddleMuddle' % DUMMY_NAMESPACE
|
_child.tag = '{%s}FuddleMuddle' % DUMMY_NAMESPACE
|
||||||
body.append(_child)
|
body.append(_child)
|
||||||
@ -165,12 +167,12 @@ def make_soap_enveloped_saml_thingy(thingy, header_parts=None):
|
|||||||
# find an remove the namespace definition
|
# find an remove the namespace definition
|
||||||
i = _str.find(DUMMY_NAMESPACE)
|
i = _str.find(DUMMY_NAMESPACE)
|
||||||
j = _str.rfind("xmlns:", 0, i)
|
j = _str.rfind("xmlns:", 0, i)
|
||||||
cut1 = _str[j:i+len(DUMMY_NAMESPACE)+1]
|
cut1 = _str[j:i + len(DUMMY_NAMESPACE) + 1]
|
||||||
_str = _str.replace(cut1, "")
|
_str = _str.replace(cut1, "")
|
||||||
first = _str.find("<%s:FuddleMuddle" % (cut1[6:9],))
|
first = _str.find("<%s:FuddleMuddle" % (cut1[6:9],))
|
||||||
last = _str.find(">", first+14)
|
last = _str.find(">", first+14)
|
||||||
cut2 = _str[first:last+1]
|
cut2 = _str[first:last+1]
|
||||||
return _str.replace(cut2,thingy)
|
return _str.replace(cut2, thingy)
|
||||||
else:
|
else:
|
||||||
thingy.become_child_element_of(body)
|
thingy.become_child_element_of(body)
|
||||||
return ElementTree.tostring(envelope, encoding="UTF-8")
|
return ElementTree.tostring(envelope, encoding="UTF-8")
|
||||||
|
@ -3,6 +3,7 @@ from saml2.cache import Cache
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Population(object):
|
class Population(object):
|
||||||
def __init__(self, cache=None):
|
def __init__(self, cache=None):
|
||||||
if cache:
|
if cache:
|
||||||
@ -17,45 +18,48 @@ class Population(object):
|
|||||||
"""If there already are information from this source in the cache
|
"""If there already are information from this source in the cache
|
||||||
this function will overwrite that information"""
|
this function will overwrite that information"""
|
||||||
|
|
||||||
subject_id = session_info["name_id"]
|
name_id = session_info["name_id"]
|
||||||
issuer = session_info["issuer"]
|
issuer = session_info["issuer"]
|
||||||
del session_info["issuer"]
|
del session_info["issuer"]
|
||||||
self.cache.set(subject_id, issuer, session_info,
|
self.cache.set(name_id, issuer, session_info,
|
||||||
session_info["not_on_or_after"])
|
session_info["not_on_or_after"])
|
||||||
return subject_id
|
return name_id
|
||||||
|
|
||||||
def stale_sources_for_person(self, subject_id, sources=None):
|
def stale_sources_for_person(self, name_id, sources=None):
|
||||||
if not sources: # assume that all the members has be asked
|
"""
|
||||||
# once before, hence they are represented in the cache
|
|
||||||
sources = self.cache.entities(subject_id)
|
:param name_id: Identifier of the subject, a NameID instance
|
||||||
sources = [m for m in sources \
|
:param sources: Sources for information about the subject
|
||||||
if not self.cache.active(subject_id, m)]
|
:return:
|
||||||
|
"""
|
||||||
|
if not sources: # assume that all the members has be asked
|
||||||
|
# once before, hence they are represented in the cache
|
||||||
|
sources = self.cache.entities(name_id)
|
||||||
|
sources = [m for m in sources if not self.cache.active(name_id, m)]
|
||||||
return sources
|
return sources
|
||||||
|
|
||||||
def issuers_of_info(self, subject_id):
|
def issuers_of_info(self, name_id):
|
||||||
return self.cache.entities(subject_id)
|
return self.cache.entities(name_id)
|
||||||
|
|
||||||
def get_identity(self, subject_id, entities=None,
|
def get_identity(self, name_id, entities=None, check_not_on_or_after=True):
|
||||||
check_not_on_or_after=True):
|
return self.cache.get_identity(name_id, entities, check_not_on_or_after)
|
||||||
return self.cache.get_identity(subject_id, entities,
|
|
||||||
check_not_on_or_after)
|
|
||||||
|
|
||||||
def get_info_from(self, subject_id, entity_id):
|
def get_info_from(self, name_id, entity_id):
|
||||||
return self.cache.get(subject_id, entity_id)
|
return self.cache.get(name_id, entity_id)
|
||||||
|
|
||||||
def subjects(self):
|
def subjects(self):
|
||||||
"""Returns the name id's for all the persons in the cache"""
|
"""Returns the name id's for all the persons in the cache"""
|
||||||
return self.cache.subjects()
|
return self.cache.subjects()
|
||||||
|
|
||||||
def remove_person(self, subject_id):
|
def remove_person(self, name_id):
|
||||||
self.cache.delete(subject_id)
|
self.cache.delete(name_id)
|
||||||
|
|
||||||
def get_entityid(self, subject_id, source_id, check_not_on_or_after=True):
|
def get_entityid(self, name_id, source_id, check_not_on_or_after=True):
|
||||||
try:
|
try:
|
||||||
return self.cache.get(subject_id, source_id,
|
return self.cache.get(name_id, source_id, check_not_on_or_after)[
|
||||||
check_not_on_or_after)["name_id"]
|
"name_id"]
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def sources(self, subject_id):
|
def sources(self, name_id):
|
||||||
return self.cache.entities(subject_id)
|
return self.cache.entities(name_id)
|
||||||
|
@ -49,17 +49,21 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class IncorrectlySigned(Exception):
|
class IncorrectlySigned(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VerificationError(Exception):
|
class VerificationError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _dummy(_):
|
def _dummy(_):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def for_me(condition, myself):
|
def for_me(condition, myself):
|
||||||
# Am I among the intended audiences
|
# Am I among the intended audiences
|
||||||
for restriction in condition.audience_restriction:
|
for restriction in condition.audience_restriction:
|
||||||
@ -72,6 +76,7 @@ def for_me(condition, myself):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def authn_response(conf, return_addr, outstanding_queries=None, timeslack=0,
|
def authn_response(conf, return_addr, outstanding_queries=None, timeslack=0,
|
||||||
asynchop=True, allow_unsolicited=False):
|
asynchop=True, allow_unsolicited=False):
|
||||||
sec = security_context(conf)
|
sec = security_context(conf)
|
||||||
@ -82,8 +87,9 @@ def authn_response(conf, return_addr, outstanding_queries=None, timeslack=0,
|
|||||||
timeslack = 0
|
timeslack = 0
|
||||||
|
|
||||||
return AuthnResponse(sec, conf.attribute_converters, conf.entityid,
|
return AuthnResponse(sec, conf.attribute_converters, conf.entityid,
|
||||||
return_addr, outstanding_queries, timeslack,
|
return_addr, outstanding_queries, timeslack,
|
||||||
asynchop=asynchop, allow_unsolicited=allow_unsolicited)
|
asynchop=asynchop, allow_unsolicited=allow_unsolicited)
|
||||||
|
|
||||||
|
|
||||||
# comes in over SOAP so synchronous
|
# comes in over SOAP so synchronous
|
||||||
def attribute_response(conf, return_addr, timeslack=0, asynchop=False,
|
def attribute_response(conf, return_addr, timeslack=0, asynchop=False,
|
||||||
@ -96,8 +102,9 @@ def attribute_response(conf, return_addr, timeslack=0, asynchop=False,
|
|||||||
timeslack = 0
|
timeslack = 0
|
||||||
|
|
||||||
return AttributeResponse(sec, conf.attribute_converters, conf.entityid,
|
return AttributeResponse(sec, conf.attribute_converters, conf.entityid,
|
||||||
return_addr, timeslack, asynchop=asynchop,
|
return_addr, timeslack, asynchop=asynchop,
|
||||||
test=test)
|
test=test)
|
||||||
|
|
||||||
|
|
||||||
class StatusResponse(object):
|
class StatusResponse(object):
|
||||||
msgtype = "status_response"
|
msgtype = "status_response"
|
||||||
@ -111,7 +118,7 @@ class StatusResponse(object):
|
|||||||
self.request_id = request_id
|
self.request_id = request_id
|
||||||
|
|
||||||
self.xmlstr = ""
|
self.xmlstr = ""
|
||||||
self.name_id = ""
|
self.name_id = None
|
||||||
self.response = None
|
self.response = None
|
||||||
self.not_on_or_after = 0
|
self.not_on_or_after = 0
|
||||||
self.in_response_to = None
|
self.in_response_to = None
|
||||||
@ -121,7 +128,7 @@ class StatusResponse(object):
|
|||||||
|
|
||||||
def _clear(self):
|
def _clear(self):
|
||||||
self.xmlstr = ""
|
self.xmlstr = ""
|
||||||
self.name_id = ""
|
self.name_id = None
|
||||||
self.response = None
|
self.response = None
|
||||||
self.not_on_or_after = 0
|
self.not_on_or_after = 0
|
||||||
|
|
||||||
@ -149,9 +156,10 @@ class StatusResponse(object):
|
|||||||
# This will check signature on Assertion which is the default
|
# This will check signature on Assertion which is the default
|
||||||
try:
|
try:
|
||||||
self.response = self.sec.check_signature(instance)
|
self.response = self.sec.check_signature(instance)
|
||||||
except SignatureError: # The response as a whole might be signed or not
|
except SignatureError:
|
||||||
self.response = self.sec.check_signature(instance,
|
# The response as a whole might be signed or not
|
||||||
samlp.NAMESPACE+":Response")
|
self.response = self.sec.check_signature(
|
||||||
|
instance, samlp.NAMESPACE + ":Response")
|
||||||
else:
|
else:
|
||||||
self.not_signed = True
|
self.not_signed = True
|
||||||
self.response = instance
|
self.response = instance
|
||||||
@ -190,9 +198,9 @@ class StatusResponse(object):
|
|||||||
def issue_instant_ok(self):
|
def issue_instant_ok(self):
|
||||||
""" Check that the response was issued at a reasonable time """
|
""" Check that the response was issued at a reasonable time """
|
||||||
upper = time_util.shift_time(time_util.time_in_a_while(days=1),
|
upper = time_util.shift_time(time_util.time_in_a_while(days=1),
|
||||||
self.timeslack).timetuple()
|
self.timeslack).timetuple()
|
||||||
lower = time_util.shift_time(time_util.time_a_while_ago(days=1),
|
lower = time_util.shift_time(time_util.time_a_while_ago(days=1),
|
||||||
-self.timeslack).timetuple()
|
-self.timeslack).timetuple()
|
||||||
# print "issue_instant: %s" % self.response.issue_instant
|
# print "issue_instant: %s" % self.response.issue_instant
|
||||||
# print "%s < x < %s" % (lower, upper)
|
# print "%s < x < %s" % (lower, upper)
|
||||||
issued_at = str_to_time(self.response.issue_instant)
|
issued_at = str_to_time(self.response.issue_instant)
|
||||||
@ -200,10 +208,9 @@ class StatusResponse(object):
|
|||||||
|
|
||||||
def _verify(self):
|
def _verify(self):
|
||||||
if self.request_id and self.in_response_to and \
|
if self.request_id and self.in_response_to and \
|
||||||
self.in_response_to != self.request_id:
|
self.in_response_to != self.request_id:
|
||||||
logger.error("Not the id I expected: %s != %s" % (
|
logger.error("Not the id I expected: %s != %s" % (
|
||||||
self.in_response_to,
|
self.in_response_to, self.request_id))
|
||||||
self.request_id))
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -217,9 +224,9 @@ class StatusResponse(object):
|
|||||||
|
|
||||||
if self.asynchop:
|
if self.asynchop:
|
||||||
if self.response.destination and \
|
if self.response.destination and \
|
||||||
self.response.destination != self.return_addr:
|
self.response.destination != self.return_addr:
|
||||||
logger.error("%s != %s" % (self.response.destination,
|
logger.error("%s != %s" % (self.response.destination,
|
||||||
self.return_addr))
|
self.return_addr))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
assert self.issue_instant_ok()
|
assert self.issue_instant_ok()
|
||||||
@ -244,14 +251,17 @@ class StatusResponse(object):
|
|||||||
def issuer(self):
|
def issuer(self):
|
||||||
return self.response.issuer.text.strip()
|
return self.response.issuer.text.strip()
|
||||||
|
|
||||||
|
|
||||||
class LogoutResponse(StatusResponse):
|
class LogoutResponse(StatusResponse):
|
||||||
msgtype = "logout_response"
|
msgtype = "logout_response"
|
||||||
|
|
||||||
def __init__(self, sec_context, return_addr=None, timeslack=0,
|
def __init__(self, sec_context, return_addr=None, timeslack=0,
|
||||||
asynchop=True):
|
asynchop=True):
|
||||||
StatusResponse.__init__(self, sec_context, return_addr, timeslack,
|
StatusResponse.__init__(self, sec_context, return_addr, timeslack,
|
||||||
asynchop=asynchop)
|
asynchop=asynchop)
|
||||||
self.signature_check = self.sec.correctly_signed_logout_response
|
self.signature_check = self.sec.correctly_signed_logout_response
|
||||||
|
|
||||||
|
|
||||||
class NameIDMappingResponse(StatusResponse):
|
class NameIDMappingResponse(StatusResponse):
|
||||||
msgtype = "name_id_mapping_response"
|
msgtype = "name_id_mapping_response"
|
||||||
|
|
||||||
@ -261,6 +271,7 @@ class NameIDMappingResponse(StatusResponse):
|
|||||||
request_id, asynchop)
|
request_id, asynchop)
|
||||||
self.signature_check = self.sec.correctly_signed_name_id_mapping_response
|
self.signature_check = self.sec.correctly_signed_name_id_mapping_response
|
||||||
|
|
||||||
|
|
||||||
class ManageNameIDResponse(StatusResponse):
|
class ManageNameIDResponse(StatusResponse):
|
||||||
msgtype = "manage_name_id_response"
|
msgtype = "manage_name_id_response"
|
||||||
|
|
||||||
@ -273,15 +284,16 @@ class ManageNameIDResponse(StatusResponse):
|
|||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class AuthnResponse(StatusResponse):
|
class AuthnResponse(StatusResponse):
|
||||||
""" This is where all the profile compliance is checked.
|
""" This is where all the profile compliance is checked.
|
||||||
This one does saml2int compliance. """
|
This one does saml2int compliance. """
|
||||||
msgtype = "authn_response"
|
msgtype = "authn_response"
|
||||||
|
|
||||||
def __init__(self, sec_context, attribute_converters, entity_id,
|
def __init__(self, sec_context, attribute_converters, entity_id,
|
||||||
return_addr=None, outstanding_queries=None,
|
return_addr=None, outstanding_queries=None,
|
||||||
timeslack=0, asynchop=True, allow_unsolicited=False,
|
timeslack=0, asynchop=True, allow_unsolicited=False,
|
||||||
test=False):
|
test=False):
|
||||||
|
|
||||||
StatusResponse.__init__(self, sec_context, return_addr, timeslack,
|
StatusResponse.__init__(self, sec_context, return_addr, timeslack,
|
||||||
asynchop=asynchop)
|
asynchop=asynchop)
|
||||||
@ -335,7 +347,8 @@ class AuthnResponse(StatusResponse):
|
|||||||
if validate_on_or_after(authn_statement.session_not_on_or_after,
|
if validate_on_or_after(authn_statement.session_not_on_or_after,
|
||||||
self.timeslack):
|
self.timeslack):
|
||||||
self.session_not_on_or_after = calendar.timegm(
|
self.session_not_on_or_after = calendar.timegm(
|
||||||
time_util.str_to_time(authn_statement.session_not_on_or_after))
|
time_util.str_to_time(
|
||||||
|
authn_statement.session_not_on_or_after))
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -364,8 +377,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
try:
|
try:
|
||||||
if condition.not_on_or_after:
|
if condition.not_on_or_after:
|
||||||
self.not_on_or_after = validate_on_or_after(
|
self.not_on_or_after = validate_on_or_after(
|
||||||
condition.not_on_or_after,
|
condition.not_on_or_after, self.timeslack)
|
||||||
self.timeslack)
|
|
||||||
if condition.not_before:
|
if condition.not_before:
|
||||||
validate_before(condition.not_before, self.timeslack)
|
validate_before(condition.not_before, self.timeslack)
|
||||||
except Exception, excp:
|
except Exception, excp:
|
||||||
@ -375,7 +387,6 @@ class AuthnResponse(StatusResponse):
|
|||||||
else:
|
else:
|
||||||
self.not_on_or_after = 0
|
self.not_on_or_after = 0
|
||||||
|
|
||||||
|
|
||||||
if not for_me(condition, self.entity_id):
|
if not for_me(condition, self.entity_id):
|
||||||
if not lax:
|
if not lax:
|
||||||
#print condition
|
#print condition
|
||||||
@ -490,7 +501,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown subject confirmation method: %s" % (
|
raise ValueError("Unknown subject confirmation method: %s" % (
|
||||||
subject_confirmation.method,))
|
subject_confirmation.method,))
|
||||||
|
|
||||||
subjconf.append(subject_confirmation)
|
subjconf.append(subject_confirmation)
|
||||||
|
|
||||||
@ -501,7 +512,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
|
|
||||||
# The subject must contain a name_id
|
# The subject must contain a name_id
|
||||||
assert subject.name_id
|
assert subject.name_id
|
||||||
self.name_id = subject.name_id.text.strip()
|
self.name_id = subject.name_id
|
||||||
return self.name_id
|
return self.name_id
|
||||||
|
|
||||||
def _assertion(self, assertion):
|
def _assertion(self, assertion):
|
||||||
@ -561,7 +572,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
def parse_assertion(self):
|
def parse_assertion(self):
|
||||||
try:
|
try:
|
||||||
assert len(self.response.assertion) == 1 or \
|
assert len(self.response.assertion) == 1 or \
|
||||||
len(self.response.encrypted_assertion) == 1
|
len(self.response.encrypted_assertion) == 1
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
raise Exception("No assertion part")
|
raise Exception("No assertion part")
|
||||||
|
|
||||||
@ -571,8 +582,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
else:
|
else:
|
||||||
logger.debug("***Encrypted response***")
|
logger.debug("***Encrypted response***")
|
||||||
return self._encrypted_assertion(
|
return self._encrypted_assertion(
|
||||||
self.response.encrypted_assertion[0])
|
self.response.encrypted_assertion[0])
|
||||||
|
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
""" Verify that the assertion is syntactically correct and
|
""" Verify that the assertion is syntactically correct and
|
||||||
@ -615,7 +625,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def authz_decision_info(self):
|
def authz_decision_info(self):
|
||||||
res = {"permit":[], "deny": [], "indeterminate":[] }
|
res = {"permit": [], "deny": [], "indeterminate": []}
|
||||||
for adstat in self.assertion.authz_decision_statement:
|
for adstat in self.assertion.authz_decision_statement:
|
||||||
# one of 'Permit', 'Deny', 'Indeterminate'
|
# one of 'Permit', 'Deny', 'Indeterminate'
|
||||||
res[adstat.decision.text.lower()] = adstat
|
res[adstat.decision.text.lower()] = adstat
|
||||||
@ -632,19 +642,18 @@ class AuthnResponse(StatusResponse):
|
|||||||
nooa = self.not_on_or_after
|
nooa = self.not_on_or_after
|
||||||
|
|
||||||
if self.context == "AuthzQuery":
|
if self.context == "AuthzQuery":
|
||||||
return {"name_id": self.name_id,
|
return {"name_id": self.name_id, "came_from": self.came_from,
|
||||||
"came_from": self.came_from, "issuer": self.issuer(),
|
"issuer": self.issuer(), "not_on_or_after": nooa,
|
||||||
"not_on_or_after": nooa,
|
|
||||||
"authz_decision_info": self.authz_decision_info() }
|
"authz_decision_info": self.authz_decision_info() }
|
||||||
else:
|
else:
|
||||||
return { "ava": self.ava, "name_id": self.name_id,
|
return {"ava": self.ava, "name_id": self.name_id,
|
||||||
"came_from": self.came_from, "issuer": self.issuer(),
|
"came_from": self.came_from, "issuer": self.issuer(),
|
||||||
"not_on_or_after": nooa,
|
"not_on_or_after": nooa, "authn_info": self.authn_info()}
|
||||||
"authn_info": self.authn_info() }
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s" % self.xmlstr
|
return "%s" % self.xmlstr
|
||||||
|
|
||||||
|
|
||||||
class AuthnQueryResponse(AuthnResponse):
|
class AuthnQueryResponse(AuthnResponse):
|
||||||
msgtype = "authn_query_response"
|
msgtype = "authn_query_response"
|
||||||
|
|
||||||
@ -659,39 +668,44 @@ class AuthnQueryResponse(AuthnResponse):
|
|||||||
self.assertion = None
|
self.assertion = None
|
||||||
self.context = "AuthnQueryResponse"
|
self.context = "AuthnQueryResponse"
|
||||||
|
|
||||||
def condition_ok(self, lax=False): # Should I care about conditions ?
|
def condition_ok(self, lax=False): # Should I care about conditions ?
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class AttributeResponse(AuthnResponse):
|
class AttributeResponse(AuthnResponse):
|
||||||
msgtype = "attribute_response"
|
msgtype = "attribute_response"
|
||||||
|
|
||||||
def __init__(self, sec_context, attribute_converters, entity_id,
|
def __init__(self, sec_context, attribute_converters, entity_id,
|
||||||
return_addr=None, timeslack=0, asynchop=False, test=False):
|
return_addr=None, timeslack=0, asynchop=False, test=False):
|
||||||
|
|
||||||
AuthnResponse.__init__(self, sec_context, attribute_converters,
|
AuthnResponse.__init__(self, sec_context, attribute_converters,
|
||||||
entity_id, return_addr, timeslack=timeslack,
|
entity_id, return_addr, timeslack=timeslack,
|
||||||
asynchop=asynchop, test=test)
|
asynchop=asynchop, test=test)
|
||||||
self.entity_id = entity_id
|
self.entity_id = entity_id
|
||||||
self.attribute_converters = attribute_converters
|
self.attribute_converters = attribute_converters
|
||||||
self.assertion = None
|
self.assertion = None
|
||||||
self.context = "AttrQuery"
|
self.context = "AttrQuery"
|
||||||
|
|
||||||
|
|
||||||
class AuthzResponse(AuthnResponse):
|
class AuthzResponse(AuthnResponse):
|
||||||
""" A successful response will be in the form of assertions containing
|
""" A successful response will be in the form of assertions containing
|
||||||
authorization decision statements."""
|
authorization decision statements."""
|
||||||
msgtype = "authz_decision_response"
|
msgtype = "authz_decision_response"
|
||||||
|
|
||||||
def __init__(self, sec_context, attribute_converters, entity_id,
|
def __init__(self, sec_context, attribute_converters, entity_id,
|
||||||
return_addr=None, timeslack=0, asynchop=False):
|
return_addr=None, timeslack=0, asynchop=False):
|
||||||
AuthnResponse.__init__(self, sec_context, attribute_converters,
|
AuthnResponse.__init__(self, sec_context, attribute_converters,
|
||||||
entity_id, return_addr,
|
entity_id, return_addr, timeslack=timeslack,
|
||||||
timeslack=timeslack, asynchop=asynchop)
|
asynchop=asynchop)
|
||||||
self.entity_id = entity_id
|
self.entity_id = entity_id
|
||||||
self.attribute_converters = attribute_converters
|
self.attribute_converters = attribute_converters
|
||||||
self.assertion = None
|
self.assertion = None
|
||||||
self.context = "AuthzQuery"
|
self.context = "AuthzQuery"
|
||||||
|
|
||||||
|
|
||||||
class ArtifactResponse(AuthnResponse):
|
class ArtifactResponse(AuthnResponse):
|
||||||
msgtype = "artifact_response"
|
msgtype = "artifact_response"
|
||||||
|
|
||||||
def __init__(self, sec_context, attribute_converters, entity_id,
|
def __init__(self, sec_context, attribute_converters, entity_id,
|
||||||
return_addr=None, timeslack=0, asynchop=False, test=False):
|
return_addr=None, timeslack=0, asynchop=False, test=False):
|
||||||
|
|
||||||
@ -704,10 +718,9 @@ class ArtifactResponse(AuthnResponse):
|
|||||||
self.context = "ArtifactResolve"
|
self.context = "ArtifactResolve"
|
||||||
|
|
||||||
|
|
||||||
def response_factory(xmlstr, conf, return_addr=None,
|
def response_factory(xmlstr, conf, return_addr=None, outstanding_queries=None,
|
||||||
outstanding_queries=None,
|
timeslack=0, decode=True, request_id=0, origxml=None,
|
||||||
timeslack=0, decode=True, request_id=0,
|
asynchop=True, allow_unsolicited=False):
|
||||||
origxml=None, asynchop=True, allow_unsolicited=False):
|
|
||||||
sec_context = security_context(conf)
|
sec_context = security_context(conf)
|
||||||
if not timeslack:
|
if not timeslack:
|
||||||
try:
|
try:
|
||||||
@ -724,8 +737,9 @@ def response_factory(xmlstr, conf, return_addr=None,
|
|||||||
response.loads(xmlstr, decode, origxml)
|
response.loads(xmlstr, decode, origxml)
|
||||||
if response.response.assertion or response.response.encrypted_assertion:
|
if response.response.assertion or response.response.encrypted_assertion:
|
||||||
authnresp = AuthnResponse(sec_context, attribute_converters,
|
authnresp = AuthnResponse(sec_context, attribute_converters,
|
||||||
entity_id, return_addr, outstanding_queries,
|
entity_id, return_addr,
|
||||||
timeslack, asynchop, allow_unsolicited)
|
outstanding_queries, timeslack, asynchop,
|
||||||
|
allow_unsolicited)
|
||||||
authnresp.update(response)
|
authnresp.update(response)
|
||||||
return authnresp
|
return authnresp
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@ -741,6 +755,7 @@ def response_factory(xmlstr, conf, return_addr=None,
|
|||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# A class of it's own
|
# A class of it's own
|
||||||
|
|
||||||
|
|
||||||
class AssertionIDResponse(object):
|
class AssertionIDResponse(object):
|
||||||
msgtype = "assertion_id_response"
|
msgtype = "assertion_id_response"
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VirtualOrg(object):
|
class VirtualOrg(object):
|
||||||
def __init__(self, sp, vorg, cnf):
|
def __init__(self, sp, vorg, cnf):
|
||||||
self.sp = sp # The parent SP client instance
|
self.sp = sp # The parent SP client instance
|
||||||
self._name = vorg
|
self._name = vorg
|
||||||
self.common_identifier = cnf["common_identifier"]
|
self.common_identifier = cnf["common_identifier"]
|
||||||
try:
|
try:
|
||||||
@ -28,7 +29,7 @@ class VirtualOrg(object):
|
|||||||
"""
|
"""
|
||||||
return self.sp.config.metadata.vo_members(self._name)
|
return self.sp.config.metadata.vo_members(self._name)
|
||||||
|
|
||||||
def members_to_ask(self, subject_id):
|
def members_to_ask(self, name_id):
|
||||||
"""Find the member of the Virtual Organization that I haven't already
|
"""Find the member of the Virtual Organization that I haven't already
|
||||||
spoken too
|
spoken too
|
||||||
"""
|
"""
|
||||||
@ -40,12 +41,12 @@ class VirtualOrg(object):
|
|||||||
|
|
||||||
# Remove the ones I have cached data from about this subject
|
# Remove the ones I have cached data from about this subject
|
||||||
vo_members = [m for m in vo_members if not self.sp.users.cache.active(
|
vo_members = [m for m in vo_members if not self.sp.users.cache.active(
|
||||||
subject_id, m)]
|
name_id, m)]
|
||||||
logger.info("VO members (not cached): %s" % vo_members)
|
logger.info("VO members (not cached): %s" % vo_members)
|
||||||
return vo_members
|
return vo_members
|
||||||
|
|
||||||
def get_common_identifier(self, subject_id):
|
def get_common_identifier(self, name_id):
|
||||||
(ava, _) = self.sp.users.get_identity(subject_id)
|
(ava, _) = self.sp.users.get_identity(name_id)
|
||||||
if ava == {}:
|
if ava == {}:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -56,36 +57,23 @@ class VirtualOrg(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def do_aggregation(self, subject_id):
|
def do_aggregation(self, name_id):
|
||||||
|
|
||||||
logger.info("** Do VO aggregation **\nSubjectID: %s, VO:%s" % (
|
logger.info("** Do VO aggregation **\nSubjectID: %s, VO:%s" % (
|
||||||
subject_id, self._name))
|
name_id, self._name))
|
||||||
|
|
||||||
to_ask = self.members_to_ask(subject_id)
|
to_ask = self.members_to_ask(name_id)
|
||||||
if to_ask:
|
if to_ask:
|
||||||
# Find the NameIDFormat and the SPNameQualifier
|
com_identifier = self.get_common_identifier(name_id)
|
||||||
if self.nameid_format:
|
|
||||||
name_id_format = self.nameid_format
|
|
||||||
sp_name_qualifier = ""
|
|
||||||
else:
|
|
||||||
sp_name_qualifier = self._name
|
|
||||||
name_id_format = ""
|
|
||||||
|
|
||||||
com_identifier = self.get_common_identifier(subject_id)
|
|
||||||
|
|
||||||
resolver = AttributeResolver(self.sp)
|
resolver = AttributeResolver(self.sp)
|
||||||
# extends returns a list of session_infos
|
# extends returns a list of session_infos
|
||||||
for session_info in resolver.extend(com_identifier,
|
for session_info in resolver.extend(
|
||||||
self.sp.config.entityid,
|
com_identifier, self.sp.config.entityid, to_ask):
|
||||||
to_ask,
|
|
||||||
name_id_format=name_id_format,
|
|
||||||
sp_name_qualifier=sp_name_qualifier,
|
|
||||||
real_id=subject_id):
|
|
||||||
_ = self._cache_session(session_info)
|
_ = self._cache_session(session_info)
|
||||||
|
|
||||||
logger.info(">Issuers: %s" % self.sp.users.issuers_of_info(
|
logger.info(">Issuers: %s" % self.sp.users.issuers_of_info(name_id))
|
||||||
subject_id))
|
logger.info("AVA: %s" % (self.sp.users.get_identity(name_id),))
|
||||||
logger.info("AVA: %s" % (self.sp.users.get_identity(subject_id),))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -163,7 +163,7 @@ class FakeIDP(Server):
|
|||||||
|
|
||||||
req = logout_request_from_string(_str)
|
req = logout_request_from_string(_str)
|
||||||
|
|
||||||
_resp = self.create_logout_response(req, binding)
|
_resp = self.create_logout_response(req, [binding])
|
||||||
|
|
||||||
if binding == BINDING_SOAP:
|
if binding == BINDING_SOAP:
|
||||||
# SOAP packing
|
# SOAP packing
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import py
|
import py
|
||||||
|
from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT
|
||||||
from saml2.cache import Cache
|
from saml2.cache import Cache
|
||||||
from saml2.time_util import in_a_while, str_to_time
|
from saml2.time_util import in_a_while, str_to_time
|
||||||
|
from saml2.ident import code
|
||||||
|
|
||||||
SESSION_INFO_PATTERN = {"ava":{}, "came from":"", "not_on_or_after":0,
|
SESSION_INFO_PATTERN = {"ava":{}, "came from":"", "not_on_or_after":0,
|
||||||
"issuer":"", "session_id":-1}
|
"issuer":"", "session_id":-1}
|
||||||
@ -12,6 +14,13 @@ SESSION_INFO_PATTERN = {"ava":{}, "came from":"", "not_on_or_after":0,
|
|||||||
def _eq(l1,l2):
|
def _eq(l1,l2):
|
||||||
return set(l1) == set(l2)
|
return set(l1) == set(l2)
|
||||||
|
|
||||||
|
def nid_eq(l1, l2):
|
||||||
|
return _eq([code(c) for c in l1], [code(c) for c in l2])
|
||||||
|
|
||||||
|
nid = [
|
||||||
|
NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="1234"),
|
||||||
|
NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="9876"),
|
||||||
|
NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT, text="1000")]
|
||||||
|
|
||||||
class TestClass:
|
class TestClass:
|
||||||
def setup_class(self):
|
def setup_class(self):
|
||||||
@ -22,10 +31,9 @@ class TestClass:
|
|||||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||||
session_info = SESSION_INFO_PATTERN.copy()
|
session_info = SESSION_INFO_PATTERN.copy()
|
||||||
session_info["ava"] = {"givenName":["Derek"]}
|
session_info["ava"] = {"givenName":["Derek"]}
|
||||||
self.cache.set("1234", "abcd", session_info,
|
self.cache.set(nid[0], "abcd", session_info, not_on_or_after)
|
||||||
not_on_or_after)
|
|
||||||
|
|
||||||
(ava, inactive) = self.cache.get_identity("1234")
|
(ava, inactive) = self.cache.get_identity(nid[0])
|
||||||
assert inactive == []
|
assert inactive == []
|
||||||
assert ava.keys() == ["givenName"]
|
assert ava.keys() == ["givenName"]
|
||||||
assert ava["givenName"] == ["Derek"]
|
assert ava["givenName"] == ["Derek"]
|
||||||
@ -34,84 +42,83 @@ class TestClass:
|
|||||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||||
session_info = SESSION_INFO_PATTERN.copy()
|
session_info = SESSION_INFO_PATTERN.copy()
|
||||||
session_info["ava"] = {"surName":["Jeter"]}
|
session_info["ava"] = {"surName":["Jeter"]}
|
||||||
self.cache.set("1234", "bcde", session_info,
|
self.cache.set(nid[0], "bcde", session_info, not_on_or_after)
|
||||||
not_on_or_after)
|
|
||||||
|
|
||||||
(ava, inactive) = self.cache.get_identity("1234")
|
(ava, inactive) = self.cache.get_identity(nid[0])
|
||||||
assert inactive == []
|
assert inactive == []
|
||||||
assert _eq(ava.keys(), ["givenName","surName"])
|
assert _eq(ava.keys(), ["givenName","surName"])
|
||||||
assert ava["givenName"] == ["Derek"]
|
assert ava["givenName"] == ["Derek"]
|
||||||
assert ava["surName"] == ["Jeter"]
|
assert ava["surName"] == ["Jeter"]
|
||||||
|
|
||||||
def test_from_one_target_source(self):
|
def test_from_one_target_source(self):
|
||||||
session_info = self.cache.get("1234","bcde")
|
session_info = self.cache.get(nid[0], "bcde")
|
||||||
ava = session_info["ava"]
|
ava = session_info["ava"]
|
||||||
assert _eq(ava.keys(), ["surName"])
|
assert _eq(ava.keys(), ["surName"])
|
||||||
assert ava["surName"] == ["Jeter"]
|
assert ava["surName"] == ["Jeter"]
|
||||||
session_info = self.cache.get("1234","abcd")
|
session_info = self.cache.get(nid[0], "abcd")
|
||||||
ava = session_info["ava"]
|
ava = session_info["ava"]
|
||||||
assert _eq(ava.keys(), ["givenName"])
|
assert _eq(ava.keys(), ["givenName"])
|
||||||
assert ava["givenName"] == ["Derek"]
|
assert ava["givenName"] == ["Derek"]
|
||||||
|
|
||||||
def test_entities(self):
|
def test_entities(self):
|
||||||
assert _eq(self.cache.entities("1234"), ["abcd", "bcde"])
|
assert _eq(self.cache.entities(nid[0]), ["abcd", "bcde"])
|
||||||
py.test.raises(Exception, "self.cache.entities('6666')")
|
py.test.raises(Exception, "self.cache.entities('6666')")
|
||||||
|
|
||||||
def test_remove_info(self):
|
def test_remove_info(self):
|
||||||
self.cache.reset("1234", "bcde")
|
self.cache.reset(nid[0], "bcde")
|
||||||
assert self.cache.active("1234", "bcde") == False
|
assert self.cache.active(nid[0], "bcde") == False
|
||||||
assert self.cache.active("1234", "abcd")
|
assert self.cache.active(nid[0], "abcd")
|
||||||
|
|
||||||
(ava, inactive) = self.cache.get_identity("1234")
|
(ava, inactive) = self.cache.get_identity(nid[0])
|
||||||
assert inactive == ['bcde']
|
assert inactive == ['bcde']
|
||||||
assert _eq(ava.keys(), ["givenName"])
|
assert _eq(ava.keys(), ["givenName"])
|
||||||
assert ava["givenName"] == ["Derek"]
|
assert ava["givenName"] == ["Derek"]
|
||||||
|
|
||||||
def test_active(self):
|
def test_active(self):
|
||||||
assert self.cache.active("1234", "bcde") == False
|
assert self.cache.active(nid[0], "bcde") == False
|
||||||
assert self.cache.active("1234", "abcd")
|
assert self.cache.active(nid[0], "abcd")
|
||||||
|
|
||||||
def test_subjects(self):
|
def test_subjects(self):
|
||||||
assert self.cache.subjects() == ["1234"]
|
assert nid_eq(self.cache.subjects(), [nid[0]])
|
||||||
|
|
||||||
def test_second_subject(self):
|
def test_second_subject(self):
|
||||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||||
session_info = SESSION_INFO_PATTERN.copy()
|
session_info = SESSION_INFO_PATTERN.copy()
|
||||||
session_info["ava"] = {"givenName":["Ichiro"],
|
session_info["ava"] = {"givenName":["Ichiro"],
|
||||||
"surName":["Suzuki"]}
|
"surName":["Suzuki"]}
|
||||||
self.cache.set("9876", "abcd", session_info,
|
self.cache.set(nid[1], "abcd", session_info,
|
||||||
not_on_or_after)
|
not_on_or_after)
|
||||||
|
|
||||||
(ava, inactive) = self.cache.get_identity("9876")
|
(ava, inactive) = self.cache.get_identity(nid[1])
|
||||||
assert inactive == []
|
assert inactive == []
|
||||||
assert _eq(ava.keys(), ["givenName","surName"])
|
assert _eq(ava.keys(), ["givenName","surName"])
|
||||||
assert ava["givenName"] == ["Ichiro"]
|
assert ava["givenName"] == ["Ichiro"]
|
||||||
assert ava["surName"] == ["Suzuki"]
|
assert ava["surName"] == ["Suzuki"]
|
||||||
assert _eq(self.cache.subjects(), ["1234","9876"])
|
assert nid_eq(self.cache.subjects(), [nid[0], nid[1]])
|
||||||
|
|
||||||
def test_receivers(self):
|
def test_receivers(self):
|
||||||
assert _eq(self.cache.receivers("9876"), ["abcd"])
|
assert _eq(self.cache.receivers(nid[1]), ["abcd"])
|
||||||
|
|
||||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||||
session_info = SESSION_INFO_PATTERN.copy()
|
session_info = SESSION_INFO_PATTERN.copy()
|
||||||
session_info["ava"] = {"givenName":["Ichiro"],
|
session_info["ava"] = {"givenName":["Ichiro"],
|
||||||
"surName":["Suzuki"]}
|
"surName":["Suzuki"]}
|
||||||
self.cache.set("9876", "bcde", session_info,
|
self.cache.set(nid[1], "bcde", session_info,
|
||||||
not_on_or_after)
|
not_on_or_after)
|
||||||
|
|
||||||
assert _eq(self.cache.receivers("9876"), ["abcd", "bcde"])
|
assert _eq(self.cache.receivers(nid[1]), ["abcd", "bcde"])
|
||||||
assert _eq(self.cache.subjects(), ["1234","9876"])
|
assert nid_eq(self.cache.subjects(), nid[0:2])
|
||||||
|
|
||||||
def test_timeout(self):
|
def test_timeout(self):
|
||||||
not_on_or_after = str_to_time(in_a_while(seconds=1))
|
not_on_or_after = str_to_time(in_a_while(seconds=1))
|
||||||
session_info = SESSION_INFO_PATTERN.copy()
|
session_info = SESSION_INFO_PATTERN.copy()
|
||||||
session_info["ava"] = {"givenName":["Alex"],
|
session_info["ava"] = {"givenName":["Alex"],
|
||||||
"surName":["Rodriguez"]}
|
"surName":["Rodriguez"]}
|
||||||
self.cache.set("1000", "bcde", session_info,
|
self.cache.set(nid[2], "bcde", session_info,
|
||||||
not_on_or_after)
|
not_on_or_after)
|
||||||
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
(ava, inactive) = self.cache.get_identity("1000")
|
(ava, inactive) = self.cache.get_identity(nid[2])
|
||||||
assert inactive == ["bcde"]
|
assert inactive == ["bcde"]
|
||||||
assert ava == {}
|
assert ava == {}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
from saml2.ident import code
|
||||||
|
from saml2.saml import NAMEID_FORMAT_TRANSIENT, NameID
|
||||||
|
|
||||||
from saml2.population import Population
|
from saml2.population import Population
|
||||||
from saml2.time_util import in_a_while
|
from saml2.time_util import in_a_while
|
||||||
@ -6,6 +8,14 @@ from saml2.time_util import in_a_while
|
|||||||
IDP_ONE = "urn:mace:example.com:saml:one:idp"
|
IDP_ONE = "urn:mace:example.com:saml:one:idp"
|
||||||
IDP_OTHER = "urn:mace:example.com:saml:other:idp"
|
IDP_OTHER = "urn:mace:example.com:saml:other:idp"
|
||||||
|
|
||||||
|
nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||||
|
text="123456")
|
||||||
|
nida = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||||
|
text="abcdef")
|
||||||
|
|
||||||
|
cnid = code(nid)
|
||||||
|
cnida = code(nida)
|
||||||
|
|
||||||
def _eq(l1, l2):
|
def _eq(l1, l2):
|
||||||
return set(l1) == set(l2)
|
return set(l1) == set(l2)
|
||||||
|
|
||||||
@ -15,7 +25,7 @@ class TestPopulationMemoryBased():
|
|||||||
|
|
||||||
def test_add_person(self):
|
def test_add_person(self):
|
||||||
session_info = {
|
session_info = {
|
||||||
"name_id": "123456",
|
"name_id": nid,
|
||||||
"issuer": IDP_ONE,
|
"issuer": IDP_ONE,
|
||||||
"not_on_or_after": in_a_while(minutes=15),
|
"not_on_or_after": in_a_while(minutes=15),
|
||||||
"ava": {
|
"ava": {
|
||||||
@ -26,34 +36,34 @@ class TestPopulationMemoryBased():
|
|||||||
}
|
}
|
||||||
self.population.add_information_about_person(session_info)
|
self.population.add_information_about_person(session_info)
|
||||||
|
|
||||||
issuers = self.population.issuers_of_info("123456")
|
issuers = self.population.issuers_of_info(nid)
|
||||||
assert issuers == [IDP_ONE]
|
assert issuers == [IDP_ONE]
|
||||||
subjects = self.population.subjects()
|
subjects = [code(c) for c in self.population.subjects()]
|
||||||
assert subjects == ["123456"]
|
assert subjects == [cnid]
|
||||||
# Are any of the sources gone stale
|
# Are any of the sources gone stale
|
||||||
stales = self.population.stale_sources_for_person("123456")
|
stales = self.population.stale_sources_for_person(nid)
|
||||||
assert stales == []
|
assert stales == []
|
||||||
# are any of the possible sources not used or gone stale
|
# are any of the possible sources not used or gone stale
|
||||||
possible = [IDP_ONE, IDP_OTHER]
|
possible = [IDP_ONE, IDP_OTHER]
|
||||||
stales = self.population.stale_sources_for_person("123456", possible)
|
stales = self.population.stale_sources_for_person(nid, possible)
|
||||||
assert stales == [IDP_OTHER]
|
assert stales == [IDP_OTHER]
|
||||||
|
|
||||||
(identity, stale) = self.population.get_identity("123456")
|
(identity, stale) = self.population.get_identity(nid)
|
||||||
assert stale == []
|
assert stale == []
|
||||||
assert identity == {'mail': 'anders.andersson@example.com',
|
assert identity == {'mail': 'anders.andersson@example.com',
|
||||||
'givenName': 'Anders',
|
'givenName': 'Anders',
|
||||||
'surName': 'Andersson'}
|
'surName': 'Andersson'}
|
||||||
|
|
||||||
info = self.population.get_info_from("123456", IDP_ONE)
|
info = self.population.get_info_from(nid, IDP_ONE)
|
||||||
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
||||||
assert info["name_id"] == '123456'
|
assert info["name_id"] == nid
|
||||||
assert info["ava"] == {'mail': 'anders.andersson@example.com',
|
assert info["ava"] == {'mail': 'anders.andersson@example.com',
|
||||||
'givenName': 'Anders',
|
'givenName': 'Anders',
|
||||||
'surName': 'Andersson'}
|
'surName': 'Andersson'}
|
||||||
|
|
||||||
def test_extend_person(self):
|
def test_extend_person(self):
|
||||||
session_info = {
|
session_info = {
|
||||||
"name_id": "123456",
|
"name_id": nid,
|
||||||
"issuer": IDP_OTHER,
|
"issuer": IDP_OTHER,
|
||||||
"not_on_or_after": in_a_while(minutes=15),
|
"not_on_or_after": in_a_while(minutes=15),
|
||||||
"ava": {
|
"ava": {
|
||||||
@ -63,33 +73,33 @@ class TestPopulationMemoryBased():
|
|||||||
|
|
||||||
self.population.add_information_about_person(session_info)
|
self.population.add_information_about_person(session_info)
|
||||||
|
|
||||||
issuers = self.population.issuers_of_info("123456")
|
issuers = self.population.issuers_of_info(nid)
|
||||||
assert _eq(issuers, [IDP_ONE, IDP_OTHER])
|
assert _eq(issuers, [IDP_ONE, IDP_OTHER])
|
||||||
subjects = self.population.subjects()
|
subjects = [code(c) for c in self.population.subjects()]
|
||||||
assert subjects == ["123456"]
|
assert subjects == [cnid]
|
||||||
# Are any of the sources gone stale
|
# Are any of the sources gone stale
|
||||||
stales = self.population.stale_sources_for_person("123456")
|
stales = self.population.stale_sources_for_person(nid)
|
||||||
assert stales == []
|
assert stales == []
|
||||||
# are any of the possible sources not used or gone stale
|
# are any of the possible sources not used or gone stale
|
||||||
possible = [IDP_ONE, IDP_OTHER]
|
possible = [IDP_ONE, IDP_OTHER]
|
||||||
stales = self.population.stale_sources_for_person("123456", possible)
|
stales = self.population.stale_sources_for_person(nid, possible)
|
||||||
assert stales == []
|
assert stales == []
|
||||||
|
|
||||||
(identity, stale) = self.population.get_identity("123456")
|
(identity, stale) = self.population.get_identity(nid)
|
||||||
assert stale == []
|
assert stale == []
|
||||||
assert identity == {'mail': 'anders.andersson@example.com',
|
assert identity == {'mail': 'anders.andersson@example.com',
|
||||||
'givenName': 'Anders',
|
'givenName': 'Anders',
|
||||||
'surName': 'Andersson',
|
'surName': 'Andersson',
|
||||||
"eduPersonEntitlement": "Anka"}
|
"eduPersonEntitlement": "Anka"}
|
||||||
|
|
||||||
info = self.population.get_info_from("123456", IDP_OTHER)
|
info = self.population.get_info_from(nid, IDP_OTHER)
|
||||||
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
||||||
assert info["name_id"] == '123456'
|
assert info["name_id"] == nid
|
||||||
assert info["ava"] == {"eduPersonEntitlement": "Anka"}
|
assert info["ava"] == {"eduPersonEntitlement": "Anka"}
|
||||||
|
|
||||||
def test_add_another_person(self):
|
def test_add_another_person(self):
|
||||||
session_info = {
|
session_info = {
|
||||||
"name_id": "abcdef",
|
"name_id": nida,
|
||||||
"issuer": IDP_ONE,
|
"issuer": IDP_ONE,
|
||||||
"not_on_or_after": in_a_while(minutes=15),
|
"not_on_or_after": in_a_while(minutes=15),
|
||||||
"ava": {
|
"ava": {
|
||||||
@ -100,28 +110,28 @@ class TestPopulationMemoryBased():
|
|||||||
}
|
}
|
||||||
self.population.add_information_about_person(session_info)
|
self.population.add_information_about_person(session_info)
|
||||||
|
|
||||||
issuers = self.population.issuers_of_info("abcdef")
|
issuers = self.population.issuers_of_info(nida)
|
||||||
assert issuers == [IDP_ONE]
|
assert issuers == [IDP_ONE]
|
||||||
subjects = self.population.subjects()
|
subjects = [code(c) for c in self.population.subjects()]
|
||||||
assert _eq(subjects, ["123456", "abcdef"])
|
assert _eq(subjects, [cnid, cnida])
|
||||||
|
|
||||||
stales = self.population.stale_sources_for_person("abcdef")
|
stales = self.population.stale_sources_for_person(nida)
|
||||||
assert stales == []
|
assert stales == []
|
||||||
# are any of the possible sources not used or gone stale
|
# are any of the possible sources not used or gone stale
|
||||||
possible = [IDP_ONE, IDP_OTHER]
|
possible = [IDP_ONE, IDP_OTHER]
|
||||||
stales = self.population.stale_sources_for_person("abcdef", possible)
|
stales = self.population.stale_sources_for_person(nida, possible)
|
||||||
assert stales == [IDP_OTHER]
|
assert stales == [IDP_OTHER]
|
||||||
|
|
||||||
(identity, stale) = self.population.get_identity("abcdef")
|
(identity, stale) = self.population.get_identity(nida)
|
||||||
assert stale == []
|
assert stale == []
|
||||||
assert identity == {"givenName": "Bertil",
|
assert identity == {"givenName": "Bertil",
|
||||||
"surName": "Bertilsson",
|
"surName": "Bertilsson",
|
||||||
"mail": "bertil.bertilsson@example.com"
|
"mail": "bertil.bertilsson@example.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
info = self.population.get_info_from("abcdef", IDP_ONE)
|
info = self.population.get_info_from(nida, IDP_ONE)
|
||||||
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
||||||
assert info["name_id"] == 'abcdef'
|
assert info["name_id"] == nida
|
||||||
assert info["ava"] == {"givenName": "Bertil",
|
assert info["ava"] == {"givenName": "Bertil",
|
||||||
"surName": "Bertilsson",
|
"surName": "Bertilsson",
|
||||||
"mail": "bertil.bertilsson@example.com"
|
"mail": "bertil.bertilsson@example.com"
|
||||||
@ -129,7 +139,7 @@ class TestPopulationMemoryBased():
|
|||||||
|
|
||||||
def test_modify_person(self):
|
def test_modify_person(self):
|
||||||
session_info = {
|
session_info = {
|
||||||
"name_id": "123456",
|
"name_id": nid,
|
||||||
"issuer": IDP_ONE,
|
"issuer": IDP_ONE,
|
||||||
"not_on_or_after": in_a_while(minutes=15),
|
"not_on_or_after": in_a_while(minutes=15),
|
||||||
"ava": {
|
"ava": {
|
||||||
@ -140,26 +150,26 @@ class TestPopulationMemoryBased():
|
|||||||
}
|
}
|
||||||
self.population.add_information_about_person(session_info)
|
self.population.add_information_about_person(session_info)
|
||||||
|
|
||||||
issuers = self.population.issuers_of_info("123456")
|
issuers = self.population.issuers_of_info(nid)
|
||||||
assert _eq(issuers, [IDP_ONE, IDP_OTHER])
|
assert _eq(issuers, [IDP_ONE, IDP_OTHER])
|
||||||
subjects = self.population.subjects()
|
subjects = [code(c) for c in self.population.subjects()]
|
||||||
assert _eq(subjects, ["123456", "abcdef"])
|
assert _eq(subjects, [cnid, cnida])
|
||||||
# Are any of the sources gone stale
|
# Are any of the sources gone stale
|
||||||
stales = self.population.stale_sources_for_person("123456")
|
stales = self.population.stale_sources_for_person(nid)
|
||||||
assert stales == []
|
assert stales == []
|
||||||
# are any of the possible sources not used or gone stale
|
# are any of the possible sources not used or gone stale
|
||||||
possible = [IDP_ONE, IDP_OTHER]
|
possible = [IDP_ONE, IDP_OTHER]
|
||||||
stales = self.population.stale_sources_for_person("123456", possible)
|
stales = self.population.stale_sources_for_person(nid, possible)
|
||||||
assert stales == []
|
assert stales == []
|
||||||
|
|
||||||
(identity, stale) = self.population.get_identity("123456")
|
(identity, stale) = self.population.get_identity(nid)
|
||||||
assert stale == []
|
assert stale == []
|
||||||
assert identity == {'mail': 'arne.andersson@example.com',
|
assert identity == {'mail': 'arne.andersson@example.com',
|
||||||
'givenName': 'Arne',
|
'givenName': 'Arne',
|
||||||
'surName': 'Andersson',
|
'surName': 'Andersson',
|
||||||
"eduPersonEntitlement": "Anka"}
|
"eduPersonEntitlement": "Anka"}
|
||||||
|
|
||||||
info = self.population.get_info_from("123456", IDP_OTHER)
|
info = self.population.get_info_from(nid, IDP_OTHER)
|
||||||
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
assert info.keys() == ["not_on_or_after", "name_id", "ava"]
|
||||||
assert info["name_id"] == '123456'
|
assert info["name_id"] == nid
|
||||||
assert info["ava"] == {"eduPersonEntitlement": "Anka"}
|
assert info["ava"] == {"eduPersonEntitlement": "Anka"}
|
@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import base64
|
import base64
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
from saml2.saml import AUTHN_PASSWORD
|
from saml2.saml import AUTHN_PASSWORD, NameID, NAMEID_FORMAT_TRANSIENT
|
||||||
from saml2.samlp import response_from_string
|
from saml2.samlp import response_from_string
|
||||||
|
|
||||||
from saml2.server import Server
|
from saml2.server import Server
|
||||||
@ -18,6 +18,9 @@ from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
|
|||||||
from py.test import raises
|
from py.test import raises
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||||
|
text="123456")
|
||||||
|
|
||||||
def _eq(l1,l2):
|
def _eq(l1,l2):
|
||||||
return set(l1) == set(l2)
|
return set(l1) == set(l2)
|
||||||
|
|
||||||
@ -150,13 +153,12 @@ class TestServer1():
|
|||||||
|
|
||||||
def test_parse_ok_request(self):
|
def test_parse_ok_request(self):
|
||||||
authn_request = self.client.create_authn_request(
|
authn_request = self.client.create_authn_request(
|
||||||
id = "id1",
|
sid="id1", destination="http://localhost:8088/sso")
|
||||||
destination = "http://localhost:8088/sso")
|
|
||||||
|
|
||||||
print authn_request
|
print authn_request
|
||||||
binding = BINDING_HTTP_REDIRECT
|
binding = BINDING_HTTP_REDIRECT
|
||||||
htargs = self.client.apply_binding(binding, "%s" % authn_request,
|
htargs = self.client.apply_binding(binding, "%s" % authn_request,
|
||||||
"http://www.example.com", "abcd")
|
"http://www.example.com", "abcd")
|
||||||
_dict = parse_qs(htargs["headers"][0][1].split('?')[1])
|
_dict = parse_qs(htargs["headers"][0][1].split('?')[1])
|
||||||
print _dict
|
print _dict
|
||||||
|
|
||||||
@ -176,17 +178,17 @@ class TestServer1():
|
|||||||
"urn:mace:example.com:saml:roland:sp",
|
"urn:mace:example.com:saml:roland:sp",
|
||||||
"id12")
|
"id12")
|
||||||
resp = self.server.create_authn_response(
|
resp = self.server.create_authn_response(
|
||||||
{"eduPersonEntitlement": "Short stop",
|
{"eduPersonEntitlement": "Short stop",
|
||||||
"surName": "Jeter",
|
"surName": "Jeter",
|
||||||
"givenName": "Derek",
|
"givenName": "Derek",
|
||||||
"mail": "derek.jeter@nyy.mlb.com",
|
"mail": "derek.jeter@nyy.mlb.com",
|
||||||
"title": "The man"},
|
"title": "The man"},
|
||||||
"id12", # in_response_to
|
"id12", # in_response_to
|
||||||
"http://localhost:8087/", # destination
|
"http://localhost:8087/", # destination
|
||||||
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
|
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
|
||||||
name_id=name_id,
|
name_id=name_id,
|
||||||
authn=(AUTHN_PASSWORD, "http://www.example.com/login")
|
authn=(AUTHN_PASSWORD, "http://www.example.com/login")
|
||||||
)
|
)
|
||||||
|
|
||||||
print resp.keyswv()
|
print resp.keyswv()
|
||||||
assert _eq(resp.keyswv(),['status', 'destination', 'assertion',
|
assert _eq(resp.keyswv(),['status', 'destination', 'assertion',
|
||||||
@ -335,7 +337,7 @@ class TestServer1():
|
|||||||
def test_slo_http_post(self):
|
def test_slo_http_post(self):
|
||||||
soon = time_util.in_a_while(days=1)
|
soon = time_util.in_a_while(days=1)
|
||||||
sinfo = {
|
sinfo = {
|
||||||
"name_id": "foba0001",
|
"name_id": nid,
|
||||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||||
"not_on_or_after" : soon,
|
"not_on_or_after" : soon,
|
||||||
"user": {
|
"user": {
|
||||||
@ -346,10 +348,9 @@ class TestServer1():
|
|||||||
self.client.users.add_information_about_person(sinfo)
|
self.client.users.add_information_about_person(sinfo)
|
||||||
|
|
||||||
logout_request = self.client.create_logout_request(
|
logout_request = self.client.create_logout_request(
|
||||||
destination = "http://localhost:8088/slop",
|
destination="http://localhost:8088/slop", name_id=nid,
|
||||||
subject_id="foba0001",
|
issuer_entity_id="urn:mace:example.com:saml:roland:idp",
|
||||||
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
reason="I'm tired of this")
|
||||||
reason = "I'm tired of this")
|
|
||||||
|
|
||||||
intermed = base64.b64encode("%s" % logout_request)
|
intermed = base64.b64encode("%s" % logout_request)
|
||||||
|
|
||||||
@ -360,9 +361,9 @@ class TestServer1():
|
|||||||
def test_slo_soap(self):
|
def test_slo_soap(self):
|
||||||
soon = time_util.in_a_while(days=1)
|
soon = time_util.in_a_while(days=1)
|
||||||
sinfo = {
|
sinfo = {
|
||||||
"name_id": "foba0001",
|
"name_id": nid,
|
||||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||||
"not_on_or_after" : soon,
|
"not_on_or_after": soon,
|
||||||
"user": {
|
"user": {
|
||||||
"givenName": "Leo",
|
"givenName": "Leo",
|
||||||
"surName": "Laport",
|
"surName": "Laport",
|
||||||
@ -373,10 +374,9 @@ class TestServer1():
|
|||||||
sp.users.add_information_about_person(sinfo)
|
sp.users.add_information_about_person(sinfo)
|
||||||
|
|
||||||
logout_request = sp.create_logout_request(
|
logout_request = sp.create_logout_request(
|
||||||
subject_id = "foba0001",
|
name_id=nid, destination="http://localhost:8088/slo",
|
||||||
destination = "http://localhost:8088/slo",
|
issuer_entity_id="urn:mace:example.com:saml:roland:idp",
|
||||||
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
reason="I'm tired of this")
|
||||||
reason = "I'm tired of this")
|
|
||||||
|
|
||||||
#_ = s_utils.deflate_and_base64_encode("%s" % (logout_request,))
|
#_ = s_utils.deflate_and_base64_encode("%s" % (logout_request,))
|
||||||
|
|
||||||
@ -429,7 +429,7 @@ def _logout_request(conf_file):
|
|||||||
|
|
||||||
soon = time_util.in_a_while(days=1)
|
soon = time_util.in_a_while(days=1)
|
||||||
sinfo = {
|
sinfo = {
|
||||||
"name_id": "foba0001",
|
"name_id": nid,
|
||||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||||
"not_on_or_after" : soon,
|
"not_on_or_after" : soon,
|
||||||
"user": {
|
"user": {
|
||||||
@ -440,7 +440,7 @@ def _logout_request(conf_file):
|
|||||||
sp.users.add_information_about_person(sinfo)
|
sp.users.add_information_about_person(sinfo)
|
||||||
|
|
||||||
return sp.create_logout_request(
|
return sp.create_logout_request(
|
||||||
subject_id = "foba0001",
|
name_id = nid,
|
||||||
destination = "http://localhost:8088/slo",
|
destination = "http://localhost:8088/slo",
|
||||||
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
||||||
reason = "I'm tired of this")
|
reason = "I'm tired of this")
|
||||||
|
@ -3,14 +3,17 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import urllib
|
import urllib
|
||||||
|
from saml2.response import LogoutResponse
|
||||||
from saml2.samlp import logout_request_from_string
|
from saml2.samlp import logout_request_from_string
|
||||||
|
|
||||||
from saml2.client import Saml2Client
|
from saml2.client import Saml2Client
|
||||||
from saml2 import samlp, BINDING_HTTP_POST
|
from saml2 import samlp, BINDING_HTTP_POST
|
||||||
from saml2 import saml, config, class_name
|
from saml2 import saml, config, class_name
|
||||||
from saml2.config import SPConfig
|
from saml2.config import SPConfig
|
||||||
from saml2.saml import NAMEID_FORMAT_PERSISTENT, NAMEID_FORMAT_TRANSIENT, \
|
from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
||||||
AUTHN_PASSWORD
|
from saml2.saml import NAMEID_FORMAT_TRANSIENT
|
||||||
|
from saml2.saml import AUTHN_PASSWORD
|
||||||
|
from saml2.saml import NameID
|
||||||
from saml2.server import Server
|
from saml2.server import Server
|
||||||
from saml2.time_util import in_a_while
|
from saml2.time_util import in_a_while
|
||||||
|
|
||||||
@ -55,6 +58,9 @@ REQ1 = { "1.2.14": """<?xml version='1.0' encoding='UTF-8'?>
|
|||||||
|
|
||||||
AUTHN = (AUTHN_PASSWORD, "http://www.example.com/login")
|
AUTHN = (AUTHN_PASSWORD, "http://www.example.com/login")
|
||||||
|
|
||||||
|
nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||||
|
text="123456")
|
||||||
|
|
||||||
class TestClient:
|
class TestClient:
|
||||||
def setup_class(self):
|
def setup_class(self):
|
||||||
self.server = Server("idp_conf")
|
self.server = Server("idp_conf")
|
||||||
@ -68,7 +74,7 @@ class TestClient:
|
|||||||
"https://idp.example.com/idp/",
|
"https://idp.example.com/idp/",
|
||||||
"E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
|
"E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
|
||||||
format=saml.NAMEID_FORMAT_PERSISTENT,
|
format=saml.NAMEID_FORMAT_PERSISTENT,
|
||||||
id="id1")
|
sid="id1")
|
||||||
reqstr = "%s" % req.to_string()
|
reqstr = "%s" % req.to_string()
|
||||||
|
|
||||||
assert req.destination == "https://idp.example.com/idp/"
|
assert req.destination == "https://idp.example.com/idp/"
|
||||||
@ -110,7 +116,7 @@ class TestClient:
|
|||||||
"urn:oasis:names:tc:SAML:2.0:attrname-format:uri"):None,
|
"urn:oasis:names:tc:SAML:2.0:attrname-format:uri"):None,
|
||||||
},
|
},
|
||||||
format=saml.NAMEID_FORMAT_PERSISTENT,
|
format=saml.NAMEID_FORMAT_PERSISTENT,
|
||||||
id="id1")
|
sid="id1")
|
||||||
|
|
||||||
print req.to_string()
|
print req.to_string()
|
||||||
assert req.destination == "https://idp.example.com/idp/"
|
assert req.destination == "https://idp.example.com/idp/"
|
||||||
@ -144,7 +150,7 @@ class TestClient:
|
|||||||
"https://aai-demo-idp.switch.ch/idp/shibboleth",
|
"https://aai-demo-idp.switch.ch/idp/shibboleth",
|
||||||
"_e7b68a04488f715cda642fbdd90099f5",
|
"_e7b68a04488f715cda642fbdd90099f5",
|
||||||
format=saml.NAMEID_FORMAT_TRANSIENT,
|
format=saml.NAMEID_FORMAT_TRANSIENT,
|
||||||
id="id1")
|
sid="id1")
|
||||||
|
|
||||||
assert isinstance(req, samlp.AttributeQuery)
|
assert isinstance(req, samlp.AttributeQuery)
|
||||||
assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth"
|
assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth"
|
||||||
@ -178,7 +184,7 @@ class TestClient:
|
|||||||
def test_create_auth_request_0(self):
|
def test_create_auth_request_0(self):
|
||||||
ar_str = "%s" % self.client.create_authn_request(
|
ar_str = "%s" % self.client.create_authn_request(
|
||||||
"http://www.example.com/sso",
|
"http://www.example.com/sso",
|
||||||
id="id1")
|
sid="id1")
|
||||||
ar = samlp.authn_request_from_string(ar_str)
|
ar = samlp.authn_request_from_string(ar_str)
|
||||||
print ar
|
print ar
|
||||||
assert ar.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
|
assert ar.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
|
||||||
@ -199,7 +205,7 @@ class TestClient:
|
|||||||
"http://www.example.com/sso",
|
"http://www.example.com/sso",
|
||||||
"urn:mace:example.com:it:tek", # vo
|
"urn:mace:example.com:it:tek", # vo
|
||||||
nameid_format=NAMEID_FORMAT_PERSISTENT,
|
nameid_format=NAMEID_FORMAT_PERSISTENT,
|
||||||
id="666")
|
sid="666")
|
||||||
|
|
||||||
ar = samlp.authn_request_from_string(ar_str)
|
ar = samlp.authn_request_from_string(ar_str)
|
||||||
print ar
|
print ar
|
||||||
@ -221,7 +227,7 @@ class TestClient:
|
|||||||
ar_str = "%s" % self.client.create_authn_request(
|
ar_str = "%s" % self.client.create_authn_request(
|
||||||
"http://www.example.com/sso",
|
"http://www.example.com/sso",
|
||||||
sign=True,
|
sign=True,
|
||||||
id="id1")
|
sid="id1")
|
||||||
|
|
||||||
ar = samlp.authn_request_from_string(ar_str)
|
ar = samlp.authn_request_from_string(ar_str)
|
||||||
|
|
||||||
@ -343,10 +349,10 @@ class TestClientWithDummy():
|
|||||||
self.client.send = self.server.receive
|
self.client.send = self.server.receive
|
||||||
|
|
||||||
def test_do_authn(self):
|
def test_do_authn(self):
|
||||||
id, http_args = self.client.prepare_for_authenticate(IDP,
|
sid, http_args = self.client.prepare_for_authenticate(IDP,
|
||||||
"http://www.example.com/relay_state")
|
"http://www.example.com/relay_state")
|
||||||
|
|
||||||
assert isinstance(id, basestring)
|
assert isinstance(sid, basestring)
|
||||||
assert len(http_args) == 4
|
assert len(http_args) == 4
|
||||||
assert http_args["headers"][0][0] == "Location"
|
assert http_args["headers"][0][0] == "Location"
|
||||||
assert http_args["data"] == []
|
assert http_args["data"] == []
|
||||||
@ -363,7 +369,7 @@ class TestClientWithDummy():
|
|||||||
|
|
||||||
# information about the user from an IdP
|
# information about the user from an IdP
|
||||||
session_info = {
|
session_info = {
|
||||||
"name_id": "123456",
|
"name_id": nid,
|
||||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||||
"not_on_or_after": in_a_while(minutes=15),
|
"not_on_or_after": in_a_while(minutes=15),
|
||||||
"ava": {
|
"ava": {
|
||||||
@ -373,24 +379,18 @@ class TestClientWithDummy():
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.client.users.add_information_about_person(session_info)
|
self.client.users.add_information_about_person(session_info)
|
||||||
entity_ids = self.client.users.issuers_of_info("123456")
|
entity_ids = self.client.users.issuers_of_info(nid)
|
||||||
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
|
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
|
||||||
resp = self.client.global_logout("123456", "Tired", in_a_while(minutes=5))
|
resp = self.client.global_logout(nid, "Tired", in_a_while(minutes=5))
|
||||||
print resp
|
print resp
|
||||||
assert resp
|
assert resp
|
||||||
assert len(resp) == 1
|
assert len(resp) == 1
|
||||||
assert resp.keys() == entity_ids
|
assert resp.keys() == entity_ids
|
||||||
http_args = resp[entity_ids[0]]
|
response = resp[entity_ids[0]]
|
||||||
assert isinstance(http_args, dict)
|
assert isinstance(response, LogoutResponse)
|
||||||
assert http_args["headers"] == [('Content-type', 'text/html')]
|
|
||||||
info = unpack_form(http_args["data"][3])
|
|
||||||
xml_str = base64.b64decode(info["SAMLRequest"])
|
|
||||||
req = logout_request_from_string(xml_str)
|
|
||||||
print req
|
|
||||||
assert req.reason == "Tired"
|
|
||||||
|
|
||||||
def test_post_sso(self):
|
def test_post_sso(self):
|
||||||
id, http_args = self.client.prepare_for_authenticate(
|
sid, http_args = self.client.prepare_for_authenticate(
|
||||||
"urn:mace:example.com:saml:roland:idp",
|
"urn:mace:example.com:saml:roland:idp",
|
||||||
relay_state="really",
|
relay_state="really",
|
||||||
binding=BINDING_HTTP_POST)
|
binding=BINDING_HTTP_POST)
|
||||||
@ -403,210 +403,17 @@ class TestClientWithDummy():
|
|||||||
http_args["data"] = urllib.urlencode(_dic)
|
http_args["data"] = urllib.urlencode(_dic)
|
||||||
http_args["method"] = "POST"
|
http_args["method"] = "POST"
|
||||||
http_args["dummy"] = _dic["SAMLRequest"]
|
http_args["dummy"] = _dic["SAMLRequest"]
|
||||||
http_args["headers"] = [('Content-type','application/x-www-form-urlencoded')]
|
http_args["headers"] = [('Content-type',
|
||||||
|
'application/x-www-form-urlencoded')]
|
||||||
|
|
||||||
response = self.client.send(**http_args)
|
response = self.client.send(**http_args)
|
||||||
print response.text
|
print response.text
|
||||||
_dic = unpack_form(response.text[3], "SAMLResponse")
|
_dic = unpack_form(response.text[3], "SAMLResponse")
|
||||||
resp = self.client.parse_authn_request_response(_dic["SAMLResponse"],
|
resp = self.client.parse_authn_request_response(_dic["SAMLResponse"],
|
||||||
BINDING_HTTP_POST,
|
BINDING_HTTP_POST,
|
||||||
{id: "/"})
|
{sid: "/"})
|
||||||
ac = resp.assertion.authn_statement[0].authn_context
|
ac = resp.assertion.authn_statement[0].authn_context
|
||||||
assert ac.authenticating_authority[0].text == 'http://www.example.com/login'
|
assert ac.authenticating_authority[0].text == \
|
||||||
|
'http://www.example.com/login'
|
||||||
assert ac.authn_context_class_ref.text == AUTHN_PASSWORD
|
assert ac.authn_context_class_ref.text == AUTHN_PASSWORD
|
||||||
|
|
||||||
# def test_logout_2(self):
|
|
||||||
# """ one IdP/AA with BINDING_SOAP, can't actually send something"""
|
|
||||||
#
|
|
||||||
# conf = config.SPConfig()
|
|
||||||
# conf.load_file("server2_conf")
|
|
||||||
# client = Saml2Client(conf)
|
|
||||||
#
|
|
||||||
# # information about the user from an IdP
|
|
||||||
# session_info = {
|
|
||||||
# "name_id": "123456",
|
|
||||||
# "issuer": "urn:mace:example.com:saml:roland:idp",
|
|
||||||
# "not_on_or_after": in_a_while(minutes=15),
|
|
||||||
# "ava": {
|
|
||||||
# "givenName": "Anders",
|
|
||||||
# "surName": "Andersson",
|
|
||||||
# "mail": "anders.andersson@example.com"
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# client.users.add_information_about_person(session_info)
|
|
||||||
# entity_ids = self.client.users.issuers_of_info("123456")
|
|
||||||
# assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
|
|
||||||
# destinations = client.config.single_logout_services(entity_ids[0],
|
|
||||||
# BINDING_SOAP)
|
|
||||||
# print destinations
|
|
||||||
# assert destinations == ['http://localhost:8088/slo']
|
|
||||||
#
|
|
||||||
# # Will raise an error since there is noone at the other end.
|
|
||||||
# raises(LogoutError, 'client.global_logout("123456", "Tired", in_a_while(minutes=5))')
|
|
||||||
#
|
|
||||||
# def test_logout_3(self):
|
|
||||||
# """ two or more IdP/AA with BINDING_HTTP_REDIRECT"""
|
|
||||||
#
|
|
||||||
# conf = config.SPConfig()
|
|
||||||
# conf.load_file("server3_conf")
|
|
||||||
# client = Saml2Client(conf)
|
|
||||||
#
|
|
||||||
# # information about the user from an IdP
|
|
||||||
# session_info_authn = {
|
|
||||||
# "name_id": "123456",
|
|
||||||
# "issuer": "urn:mace:example.com:saml:roland:idp",
|
|
||||||
# "not_on_or_after": in_a_while(minutes=15),
|
|
||||||
# "ava": {
|
|
||||||
# "givenName": "Anders",
|
|
||||||
# "surName": "Andersson",
|
|
||||||
# "mail": "anders.andersson@example.com"
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# client.users.add_information_about_person(session_info_authn)
|
|
||||||
# session_info_aa = {
|
|
||||||
# "name_id": "123456",
|
|
||||||
# "issuer": "urn:mace:example.com:saml:roland:aa",
|
|
||||||
# "not_on_or_after": in_a_while(minutes=15),
|
|
||||||
# "ava": {
|
|
||||||
# "eduPersonEntitlement": "Foobar",
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# client.users.add_information_about_person(session_info_aa)
|
|
||||||
# entity_ids = client.users.issuers_of_info("123456")
|
|
||||||
# assert _leq(entity_ids, ["urn:mace:example.com:saml:roland:idp",
|
|
||||||
# "urn:mace:example.com:saml:roland:aa"])
|
|
||||||
# resp = client.global_logout("123456", "Tired", in_a_while(minutes=5))
|
|
||||||
# print resp
|
|
||||||
# assert resp
|
|
||||||
# assert resp[0] # a session_id
|
|
||||||
# assert resp[1] == '200 OK'
|
|
||||||
# # HTTP POST
|
|
||||||
# assert resp[2] == [('Content-type', 'text/html')]
|
|
||||||
# assert resp[3][0] == '<head>'
|
|
||||||
# assert resp[3][1] == '<title>SAML 2.0 POST</title>'
|
|
||||||
#
|
|
||||||
# state_info = client.state[resp[0]]
|
|
||||||
# print state_info
|
|
||||||
# assert state_info["entity_id"] == entity_ids[0]
|
|
||||||
# assert state_info["subject_id"] == "123456"
|
|
||||||
# assert state_info["reason"] == "Tired"
|
|
||||||
# assert state_info["operation"] == "SLO"
|
|
||||||
# assert state_info["entity_ids"] == entity_ids
|
|
||||||
# assert state_info["sign"] == True
|
|
||||||
#
|
|
||||||
# def test_authz_decision_query(self):
|
|
||||||
# conf = config.SPConfig()
|
|
||||||
# conf.load_file("server3_conf")
|
|
||||||
# client = Saml2Client(conf)
|
|
||||||
#
|
|
||||||
# AVA = {'mail': u'roland.hedberg@adm.umu.se',
|
|
||||||
# 'eduPersonTargetedID': '95e9ae91dbe62d35198fbbd5e1fb0976',
|
|
||||||
# 'displayName': u'Roland Hedberg',
|
|
||||||
# 'uid': 'http://roland.hedberg.myopenid.com/'}
|
|
||||||
#
|
|
||||||
# sp_entity_id = "sp_entity_id"
|
|
||||||
# in_response_to = "1234"
|
|
||||||
# consumer_url = "http://example.com/consumer"
|
|
||||||
# name_id = saml.NameID(saml.NAMEID_FORMAT_TRANSIENT, text="name_id")
|
|
||||||
# policy = Policy()
|
|
||||||
# ava = Assertion(AVA)
|
|
||||||
# assertion = ava.construct(sp_entity_id, in_response_to,
|
|
||||||
# consumer_url, name_id,
|
|
||||||
# conf.attribute_converters,
|
|
||||||
# policy, issuer=client._issuer())
|
|
||||||
#
|
|
||||||
# adq = client.create_authz_decision_query_using_assertion("entity_id",
|
|
||||||
# assertion,
|
|
||||||
# "read",
|
|
||||||
# "http://example.com/text")
|
|
||||||
#
|
|
||||||
# assert adq
|
|
||||||
# print adq
|
|
||||||
# assert adq.keyswv() != []
|
|
||||||
# assert adq.destination == "entity_id"
|
|
||||||
# assert adq.resource == "http://example.com/text"
|
|
||||||
# assert adq.action[0].text == "read"
|
|
||||||
#
|
|
||||||
# def test_request_to_discovery_service(self):
|
|
||||||
# disc_url = "http://example.com/saml2/idp/disc"
|
|
||||||
# url = discovery_service_request_url("urn:mace:example.com:saml:roland:sp",
|
|
||||||
# disc_url)
|
|
||||||
# print url
|
|
||||||
# assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp"
|
|
||||||
#
|
|
||||||
# url = discovery_service_request_url(
|
|
||||||
# self.client.config.entityid,
|
|
||||||
# disc_url,
|
|
||||||
# return_url= "http://example.org/saml2/sp/ds")
|
|
||||||
#
|
|
||||||
# print url
|
|
||||||
# assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp&return=http%3A%2F%2Fexample.org%2Fsaml2%2Fsp%2Fds"
|
|
||||||
#
|
|
||||||
# def test_get_idp_from_discovery_service(self):
|
|
||||||
# pdir = {"entityID": "http://example.org/saml2/idp/sso"}
|
|
||||||
# params = urllib.urlencode(pdir)
|
|
||||||
# redirect_url = "http://example.com/saml2/sp/disc?%s" % params
|
|
||||||
#
|
|
||||||
# entity_id = discovery_service_response(url=redirect_url)
|
|
||||||
# assert entity_id == "http://example.org/saml2/idp/sso"
|
|
||||||
#
|
|
||||||
# pdir = {"idpID": "http://example.org/saml2/idp/sso"}
|
|
||||||
# params = urllib.urlencode(pdir)
|
|
||||||
# redirect_url = "http://example.com/saml2/sp/disc?%s" % params
|
|
||||||
#
|
|
||||||
# entity_id = discovery_service_response(url=redirect_url,
|
|
||||||
# returnIDParam="idpID")
|
|
||||||
#
|
|
||||||
# assert entity_id == "http://example.org/saml2/idp/sso"
|
|
||||||
# self.server.close_shelve_db()
|
|
||||||
#
|
|
||||||
# def test_unsolicited_response(self):
|
|
||||||
# """
|
|
||||||
#
|
|
||||||
# """
|
|
||||||
# self.server = Server("idp_conf")
|
|
||||||
#
|
|
||||||
# conf = config.SPConfig()
|
|
||||||
# conf.load_file("server_conf")
|
|
||||||
# self.client = Saml2Client(conf)
|
|
||||||
#
|
|
||||||
# for subject in self.client.users.subjects():
|
|
||||||
# self.client.users.remove_person(subject)
|
|
||||||
#
|
|
||||||
# IDP = "urn:mace:example.com:saml:roland:idp"
|
|
||||||
#
|
|
||||||
# ava = { "givenName": ["Derek"], "surName": ["Jeter"],
|
|
||||||
# "mail": ["derek@nyy.mlb.com"], "title": ["The man"]}
|
|
||||||
#
|
|
||||||
# resp_str = "%s" % self.server.create_authn_response(
|
|
||||||
# identity=ava,
|
|
||||||
# in_response_to="id1",
|
|
||||||
# destination="http://lingon.catalogix.se:8087/",
|
|
||||||
# sp_entity_id="urn:mace:example.com:saml:roland:sp",
|
|
||||||
# name_id_policy=samlp.NameIDPolicy(
|
|
||||||
# format=saml.NAMEID_FORMAT_PERSISTENT),
|
|
||||||
# userid="foba0001@example.com")
|
|
||||||
#
|
|
||||||
# resp_str = base64.encodestring(resp_str)
|
|
||||||
#
|
|
||||||
# self.client.allow_unsolicited = True
|
|
||||||
# authn_response = self.client.authn_request_response(
|
|
||||||
# {"SAMLResponse":resp_str}, ())
|
|
||||||
#
|
|
||||||
# assert authn_response is not None
|
|
||||||
# assert authn_response.issuer() == IDP
|
|
||||||
# assert authn_response.response.assertion[0].issuer.text == IDP
|
|
||||||
# session_info = authn_response.session_info()
|
|
||||||
#
|
|
||||||
# print session_info
|
|
||||||
# assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'],
|
|
||||||
# 'givenName': ['Derek'],
|
|
||||||
# 'surName': ['Jeter']}
|
|
||||||
# assert session_info["issuer"] == IDP
|
|
||||||
# assert session_info["came_from"] == ""
|
|
||||||
# response = samlp.response_from_string(authn_response.xmlstr)
|
|
||||||
# assert response.destination == "http://lingon.catalogix.se:8087/"
|
|
||||||
#
|
|
||||||
# # One person in the cache
|
|
||||||
# assert len(self.client.users.subjects()) == 1
|
|
||||||
# self.server.close_shelve_db()
|
|
@ -1,22 +1,31 @@
|
|||||||
|
from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT
|
||||||
|
|
||||||
__author__ = 'rolandh'
|
__author__ = 'rolandh'
|
||||||
|
|
||||||
from saml2 import config
|
from saml2 import config
|
||||||
from saml2.client import Saml2Client
|
from saml2.client import Saml2Client
|
||||||
from saml2.time_util import str_to_time, in_a_while
|
from saml2.time_util import str_to_time, in_a_while
|
||||||
|
|
||||||
SESSION_INFO_PATTERN = {"ava":{}, "came from":"", "not_on_or_after":0,
|
SESSION_INFO_PATTERN = {"ava": {}, "came from": "", "not_on_or_after": 0,
|
||||||
"issuer":"", "session_id":-1}
|
"issuer": "", "session_id": -1}
|
||||||
|
|
||||||
|
nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||||
|
text="abcdefgh")
|
||||||
|
nid0 = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||||
|
text="01234567")
|
||||||
|
|
||||||
|
|
||||||
def add_derek_info(sp):
|
def add_derek_info(sp):
|
||||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||||
session_info = SESSION_INFO_PATTERN.copy()
|
session_info = SESSION_INFO_PATTERN.copy()
|
||||||
session_info["ava"] = {"givenName":["Derek"], "umuselin":["deje0001"]}
|
session_info["ava"] = {"givenName": ["Derek"], "umuselin": ["deje0001"]}
|
||||||
session_info["issuer"] = "urn:mace:example.com:saml:idp"
|
session_info["issuer"] = "urn:mace:example.com:saml:idp"
|
||||||
session_info["name_id"] = "abcdefgh"
|
session_info["name_id"] = nid
|
||||||
session_info["not_on_or_after"] = not_on_or_after
|
session_info["not_on_or_after"] = not_on_or_after
|
||||||
# subject_id, entity_id, info, timestamp
|
# subject_id, entity_id, info, timestamp
|
||||||
sp.users.add_information_about_person(session_info)
|
sp.users.add_information_about_person(session_info)
|
||||||
|
|
||||||
|
|
||||||
class TestVirtualOrg():
|
class TestVirtualOrg():
|
||||||
def setup_class(self):
|
def setup_class(self):
|
||||||
conf = config.SPConfig()
|
conf = config.SPConfig()
|
||||||
@ -28,24 +37,25 @@ class TestVirtualOrg():
|
|||||||
add_derek_info(self.sp)
|
add_derek_info(self.sp)
|
||||||
|
|
||||||
def test_mta(self):
|
def test_mta(self):
|
||||||
aas = self.vo.members_to_ask("abcdefgh")
|
aas = self.vo.members_to_ask(nid)
|
||||||
print aas
|
print aas
|
||||||
assert len(aas) == 1
|
assert len(aas) == 1
|
||||||
assert 'urn:mace:example.com:saml:aa' in aas
|
assert 'urn:mace:example.com:saml:aa' in aas
|
||||||
|
|
||||||
def test_unknown_subject(self):
|
def test_unknown_subject(self):
|
||||||
aas = self.vo.members_to_ask("01234567")
|
aas = self.vo.members_to_ask(nid0)
|
||||||
print aas
|
print aas
|
||||||
assert len(aas) == 2
|
assert len(aas) == 2
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
id = self.vo.get_common_identifier("abcdefgh")
|
cid = self.vo.get_common_identifier(nid)
|
||||||
print id
|
print cid
|
||||||
assert id == "deje0001"
|
assert cid == "deje0001"
|
||||||
|
|
||||||
def test_id_unknown(self):
|
def test_id_unknown(self):
|
||||||
id = self.vo.get_common_identifier("01234567")
|
cid = self.vo.get_common_identifier(nid0)
|
||||||
assert id is None
|
assert cid is None
|
||||||
|
|
||||||
|
|
||||||
class TestVirtualOrg_2():
|
class TestVirtualOrg_2():
|
||||||
def setup_class(self):
|
def setup_class(self):
|
||||||
@ -56,21 +66,21 @@ class TestVirtualOrg_2():
|
|||||||
add_derek_info(self.sp)
|
add_derek_info(self.sp)
|
||||||
|
|
||||||
def test_mta(self):
|
def test_mta(self):
|
||||||
aas = self.sp.vorg.members_to_ask("abcdefgh")
|
aas = self.sp.vorg.members_to_ask(nid)
|
||||||
print aas
|
print aas
|
||||||
assert len(aas) == 1
|
assert len(aas) == 1
|
||||||
assert 'urn:mace:example.com:saml:aa' in aas
|
assert 'urn:mace:example.com:saml:aa' in aas
|
||||||
|
|
||||||
def test_unknown_subject(self):
|
def test_unknown_subject(self):
|
||||||
aas = self.sp.vorg.members_to_ask("01234567")
|
aas = self.sp.vorg.members_to_ask(nid0)
|
||||||
print aas
|
print aas
|
||||||
assert len(aas) == 2
|
assert len(aas) == 2
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
id = self.sp.vorg.get_common_identifier("abcdefgh")
|
cid = self.sp.vorg.get_common_identifier(nid)
|
||||||
print id
|
print cid
|
||||||
assert id == "deje0001"
|
assert cid == "deje0001"
|
||||||
|
|
||||||
def test_id_unknown(self):
|
def test_id_unknown(self):
|
||||||
id = self.sp.vorg.get_common_identifier("01234567")
|
cid = self.sp.vorg.get_common_identifier(nid0)
|
||||||
assert id is None
|
assert cid is None
|
||||||
|
Loading…
Reference in New Issue
Block a user