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")
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
logger.debug("- SOAP -")
|
||||
_dict = self.unpack_soap()
|
||||
logger.debug("_dict: %s" % _dict)
|
||||
return self.operation(_dict, BINDING_SOAP)
|
||||
|
||||
def uri(self):
|
||||
@ -424,7 +425,9 @@ class SLO(Service):
|
||||
def do(self, request, binding, relay_state=""):
|
||||
logger.info("--- Single Log Out Service ---")
|
||||
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:
|
||||
logger.error("Bad request: %s" % exc)
|
||||
resp = BadRequest("%s" % exc)
|
||||
|
@ -79,7 +79,7 @@ CONFIG={
|
||||
"name_form": NAME_FORMAT_URI
|
||||
},
|
||||
},
|
||||
"subject_data": "./idp.subject.db",
|
||||
"subject_data": "./idp.subject",
|
||||
"name_id_format": [NAMEID_FORMAT_TRANSIENT,
|
||||
NAMEID_FORMAT_PERSISTENT]
|
||||
},
|
||||
@ -88,7 +88,7 @@ CONFIG={
|
||||
"key_file" : "pki/mykey.pem",
|
||||
"cert_file" : "pki/mycert.pem",
|
||||
"metadata" : {
|
||||
"local": ["../sp.xml"],
|
||||
"local": ["../sp/sp.xml"],
|
||||
},
|
||||
"organization": {
|
||||
"display_name": "Rolands Identiteter",
|
||||
|
@ -14,6 +14,8 @@ from saml2.httputil import Redirect
|
||||
logger = logging.getLogger("saml2.SP")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def dict_to_table(ava, lev=0, width=1):
|
||||
txt = ['<table border=%s bordercolor="black">\n' % width]
|
||||
for prop, valarr in ava.items():
|
||||
@ -29,12 +31,12 @@ def dict_to_table(ava, lev=0, width=1):
|
||||
n = len(valarr)
|
||||
for val in valarr:
|
||||
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:
|
||||
txt.append("<tr>\n")
|
||||
if isinstance(val, dict):
|
||||
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")
|
||||
else:
|
||||
try:
|
||||
@ -48,12 +50,13 @@ def dict_to_table(ava, lev=0, width=1):
|
||||
elif isinstance(valarr, dict):
|
||||
txt.append("<th>%s</th>\n" % prop)
|
||||
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("</tr>\n")
|
||||
txt.append('</table>\n')
|
||||
return txt
|
||||
|
||||
|
||||
def _expiration(timeout, tformat=None):
|
||||
if timeout == "now":
|
||||
return time_util.instant(tformat)
|
||||
@ -61,6 +64,7 @@ def _expiration(timeout, tformat=None):
|
||||
# validity time should match lifetime of assertions
|
||||
return time_util.in_a_while(minutes=timeout, format=tformat)
|
||||
|
||||
|
||||
def delete_cookie(environ, name):
|
||||
kaka = environ.get("HTTP_COOKIE", '')
|
||||
if kaka:
|
||||
@ -68,13 +72,14 @@ def delete_cookie(environ, name):
|
||||
morsel = cookie_obj.get(name, None)
|
||||
cookie = SimpleCookie()
|
||||
cookie[name] = morsel
|
||||
cookie[name]["expires"] =\
|
||||
_expiration("now", "%a, %d-%b-%Y %H:%M:%S CET")
|
||||
cookie[name]["expires"] = _expiration("now",
|
||||
"%a, %d-%b-%Y %H:%M:%S CET")
|
||||
return tuple(cookie.output().split(": ", 1))
|
||||
return None
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def whoami(environ, start_response, user):
|
||||
identity = environ["repoze.who.identity"]["user"]
|
||||
@ -86,17 +91,20 @@ def whoami(environ, start_response, user):
|
||||
resp = Response(response)
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def not_found(environ, start_response):
|
||||
"""Called if no URL matches."""
|
||||
resp = NotFound('Not Found')
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def not_authn(environ, start_response):
|
||||
resp = Unauthorized('Unknown user')
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def slo(environ, start_response, user):
|
||||
# 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"])
|
||||
logger.info("query: %s" % query)
|
||||
try:
|
||||
response = sc.parse_logout_request_response(query["SAMLResponse"][0],
|
||||
binding=BINDING_HTTP_REDIRECT)
|
||||
response = sc.parse_logout_request_response(
|
||||
query["SAMLResponse"][0], binding=BINDING_HTTP_REDIRECT)
|
||||
if response:
|
||||
logger.info("LOGOUT response parsed OK")
|
||||
except KeyError:
|
||||
@ -125,6 +133,7 @@ def slo(environ, start_response, user):
|
||||
resp = Redirect("Successful Logout", headers=headers)
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def logout(environ, start_response, user):
|
||||
# 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")
|
||||
# return []
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def done(environ, start_response, user):
|
||||
# remove cookie and stored info
|
||||
@ -175,6 +185,7 @@ urls = [
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def application(environ, start_response):
|
||||
"""
|
||||
The main WSGI application. Dispatch the current request to
|
||||
@ -208,20 +219,22 @@ def application(environ, start_response):
|
||||
return callback(environ, start_response, user)
|
||||
else:
|
||||
return not_authn(environ, start_response)
|
||||
|
||||
return not_found(environ, start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
from repoze.who.config import make_middleware_with_config
|
||||
|
||||
app_with_auth = make_middleware_with_config(application, {"here":"."},
|
||||
'./who.ini', log_file="repoze_who.log")
|
||||
app_with_auth = make_middleware_with_config(application, {"here": "."},
|
||||
'./who.ini',
|
||||
log_file="repoze_who.log")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
PORT = 8087
|
||||
|
||||
if __name__ == '__main__':
|
||||
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
|
||||
srv.serve_forever()
|
@ -1,75 +1,18 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||
xmlns:ns1="http://www.w3.org/2000/09/xmldsig#">
|
||||
<ns0:EntityDescriptor entityID="urn:mace:umu.se:saml:roland:sp">
|
||||
<ns0:SPSSODescriptor AuthnRequestsSigned="false"
|
||||
WantAssertionsSigned="true"
|
||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<ns0:KeyDescriptor>
|
||||
<ns1:KeyInfo>
|
||||
<ns1:X509Data>
|
||||
<ns1:X509Certificate>
|
||||
MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
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>
|
||||
<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
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
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: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>
|
||||
|
@ -1,23 +1,23 @@
|
||||
from saml2 import BINDING_HTTP_REDIRECT
|
||||
from saml2.saml import NAME_FORMAT_URI
|
||||
|
||||
BASE= "http://localhost:8087/"
|
||||
BASE= "http://localhost:8087"
|
||||
#BASE= "http://lingon.catalogix.se:8087"
|
||||
|
||||
CONFIG = {
|
||||
"entityid" : "urn:mace:umu.se:saml:roland:sp",
|
||||
"entityid" : "%s/sp.xml" % BASE,
|
||||
"description": "My SP",
|
||||
"service": {
|
||||
"sp":{
|
||||
"name" : "Rolands SP",
|
||||
"endpoints":{
|
||||
"assertion_consumer_service": [BASE],
|
||||
"single_logout_service" : [(BASE+"slo",
|
||||
"single_logout_service" : [(BASE+"/slo",
|
||||
BINDING_HTTP_REDIRECT)],
|
||||
},
|
||||
"required_attributes": ["surname", "givenname",
|
||||
"edupersonaffiliation"],
|
||||
"optional_attributes": ["title"],
|
||||
"idp": [ "urn:mace:umu.se:saml:roland:idp"],
|
||||
}
|
||||
},
|
||||
"debug" : 1,
|
||||
@ -25,7 +25,7 @@ CONFIG = {
|
||||
"cert_file" : "pki/mycert.pem",
|
||||
"attribute_map_dir" : "./attributemaps",
|
||||
"metadata" : {
|
||||
"local": ["../idp/idp.xml"],
|
||||
"local": ["../idp2/idp.xml"],
|
||||
},
|
||||
# -- below used by make_metadata --
|
||||
"organization": {
|
||||
|
@ -35,16 +35,13 @@ class AttributeResolver(object):
|
||||
self.saml2client = saml2client
|
||||
self.metadata = saml2client.config.metadata
|
||||
|
||||
def extend(self, subject_id, issuer, vo_members, name_id_format=None,
|
||||
sp_name_qualifier=None, real_id=None):
|
||||
def extend(self, name_id, issuer, vo_members):
|
||||
"""
|
||||
: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
|
||||
:param issuer: Who am I the poses the query
|
||||
:param vo_members: The entity IDs of the IdP who I'm going to ask
|
||||
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
|
||||
subject
|
||||
"""
|
||||
@ -58,12 +55,8 @@ class AttributeResolver(object):
|
||||
continue
|
||||
# attribute query assumes SOAP binding
|
||||
session_info = self.saml2client.attribute_query(
|
||||
subject_id,
|
||||
attr_serv.location,
|
||||
issuer_id=issuer,
|
||||
sp_name_qualifier=sp_name_qualifier,
|
||||
nameid_format=name_id_format,
|
||||
real_id=real_id)
|
||||
name_id, attr_serv.location, issuer_id=issuer,
|
||||
)
|
||||
if session_info:
|
||||
result.append(session_info)
|
||||
return result
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import shelve
|
||||
from saml2.ident import code, decode
|
||||
from saml2 import time_util
|
||||
import logging
|
||||
|
||||
@ -10,12 +11,15 @@ logger = logging.getLogger(__name__)
|
||||
# gathered from several different sources, all with their own
|
||||
# timeout time.
|
||||
|
||||
|
||||
class ToOld(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CacheError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Cache(object):
|
||||
def __init__(self, filename=None):
|
||||
if filename:
|
||||
@ -25,18 +29,25 @@ class Cache(object):
|
||||
self._db = {}
|
||||
self._sync = False
|
||||
|
||||
def delete(self, subject_id):
|
||||
del self._db[subject_id]
|
||||
def delete(self, name_id):
|
||||
"""
|
||||
|
||||
:param name_id: The subject identifier, a NameID instance
|
||||
"""
|
||||
del self._db[code(name_id)]
|
||||
|
||||
if self._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):
|
||||
""" Get all the identity information that has been received and
|
||||
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
|
||||
interesting. If the list is empty all entities are interesting.
|
||||
:return: A 2-tuple consisting of the identity information (a
|
||||
@ -45,7 +56,8 @@ class Cache(object):
|
||||
"""
|
||||
if not entities:
|
||||
try:
|
||||
entities = self._db[subject_id].keys()
|
||||
cni = code(name_id)
|
||||
entities = self._db[cni].keys()
|
||||
except KeyError:
|
||||
return {}, []
|
||||
|
||||
@ -53,7 +65,7 @@ class Cache(object):
|
||||
oldees = []
|
||||
for entity_id in entities:
|
||||
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:
|
||||
oldees.append(entity_id)
|
||||
continue
|
||||
@ -70,74 +82,81 @@ class Cache(object):
|
||||
res[key] = vals
|
||||
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
|
||||
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 check_not_on_or_after: if True it will check if this
|
||||
subject is still valid or if it is too old. Otherwise it
|
||||
will not check this. True by default.
|
||||
: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):
|
||||
raise ToOld("past %s" % timestamp)
|
||||
|
||||
return info or None
|
||||
|
||||
def set(self, subject_id, entity_id, info, not_on_or_after=0):
|
||||
""" Stores session information in the cache. Assumes that the subject_id
|
||||
def set(self, name_id, entity_id, info, not_on_or_after=0):
|
||||
""" Stores session information in the cache. Assumes that the name_id
|
||||
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
|
||||
assertion
|
||||
: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.
|
||||
"""
|
||||
if subject_id not in self._db:
|
||||
self._db[subject_id] = {}
|
||||
cni = code(name_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:
|
||||
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
|
||||
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
|
||||
: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
|
||||
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 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
|
||||
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.
|
||||
|
||||
: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
|
||||
:return: True or False depending on if the assertion is still
|
||||
valid or not.
|
||||
"""
|
||||
try:
|
||||
(timestamp, info) = self._db[subject_id][entity_id]
|
||||
cni = code(name_id)
|
||||
(timestamp, info) = self._db[cni][entity_id]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
@ -151,4 +170,4 @@ class Cache(object):
|
||||
|
||||
: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.s_utils import sid
|
||||
from saml2.samlp import logout_response_from_string
|
||||
import saml2
|
||||
|
||||
try:
|
||||
@ -46,6 +45,7 @@ from saml2 import BINDING_SOAP
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Saml2Client(Base):
|
||||
""" The basic pySAML2 service provider class """
|
||||
|
||||
@ -81,12 +81,12 @@ class Saml2Client(Base):
|
||||
|
||||
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 :-/
|
||||
Bootstrapping the whole thing by finding all the IdPs that should
|
||||
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.
|
||||
:param reason: Why the subject wants to log out
|
||||
:param expire: The latest the log out should happen.
|
||||
@ -99,17 +99,17 @@ class Saml2Client(Base):
|
||||
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
|
||||
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
|
||||
information concerning the subject
|
||||
:param reason: The reason for doing the logout
|
||||
@ -120,32 +120,31 @@ class Saml2Client(Base):
|
||||
# check time
|
||||
if not not_on_or_after(expire): # I've run out of time
|
||||
# Do the local logout anyway
|
||||
self.local_logout(subject_id)
|
||||
self.local_logout(name_id)
|
||||
return 0, "504 Gateway Timeout", [], []
|
||||
|
||||
# for all where I can use the SOAP binding, do those first
|
||||
not_done = entity_ids[:]
|
||||
responses = {}
|
||||
|
||||
for entity_id in entity_ids:
|
||||
response = False
|
||||
|
||||
for binding in [BINDING_SOAP,
|
||||
BINDING_HTTP_POST,
|
||||
logger.debug("Logout from '%s'" % entity_id)
|
||||
# for all where I can use the SOAP binding, do those first
|
||||
for binding in [BINDING_SOAP, BINDING_HTTP_POST,
|
||||
BINDING_HTTP_REDIRECT]:
|
||||
srvs = self.metadata.single_logout_service(entity_id, binding,
|
||||
"idpsso")
|
||||
if not srvs:
|
||||
logger.debug("No SLO '%s' service" % binding)
|
||||
continue
|
||||
|
||||
destination = destinations(srvs)[0]
|
||||
|
||||
logger.info("destination to provider: %s" % destination)
|
||||
request = self.create_logout_request(destination, entity_id,
|
||||
subject_id, reason=reason,
|
||||
name_id=name_id,
|
||||
reason=reason,
|
||||
expire=expire)
|
||||
|
||||
to_sign = []
|
||||
#to_sign = []
|
||||
if binding.startswith("http://"):
|
||||
sign = True
|
||||
|
||||
@ -163,14 +162,14 @@ class Saml2Client(Base):
|
||||
relay_state)
|
||||
|
||||
if binding == BINDING_SOAP:
|
||||
if response:
|
||||
logger.info("Verifying response")
|
||||
response = self.send(**http_info)
|
||||
|
||||
if response:
|
||||
if response and response.status_code == 200:
|
||||
not_done.remove(entity_id)
|
||||
logger.info("OK response from %s" % destination)
|
||||
responses[entity_id] = logout_response_from_string(response)
|
||||
response = response.text
|
||||
logger.info("Response: %s" % response)
|
||||
res = self.parse_logout_request_response(response)
|
||||
responses[entity_id] = res
|
||||
else:
|
||||
logger.info("NOT OK response from %s" % destination)
|
||||
|
||||
@ -178,7 +177,7 @@ class Saml2Client(Base):
|
||||
self.state[request.id] = {"entity_id": entity_id,
|
||||
"operation": "SLO",
|
||||
"entity_ids": entity_ids,
|
||||
"subject_id": subject_id,
|
||||
"name_id": name_id,
|
||||
"reason": reason,
|
||||
"not_on_of_after": expire,
|
||||
"sign": sign}
|
||||
@ -219,7 +218,7 @@ class Saml2Client(Base):
|
||||
del self.state[response.in_response_to]
|
||||
if status["entity_ids"] == [issuer]: # done
|
||||
self.local_logout(status["subject_id"])
|
||||
return 0, "200 Ok", [("Content-type","text/html")], []
|
||||
return 0, "200 Ok", [("Content-type", "text/html")], []
|
||||
else:
|
||||
status["entity_ids"].remove(issuer)
|
||||
return self.do_logout(status["subject_id"], status["entity_ids"],
|
||||
@ -277,8 +276,7 @@ class Saml2Client(Base):
|
||||
consent=None, extensions=None, sign=False):
|
||||
|
||||
subject = saml.Subject(
|
||||
name_id = saml.NameID(text=subject_id,
|
||||
format=nameid_format,
|
||||
name_id=saml.NameID(text=subject_id, format=nameid_format,
|
||||
sp_name_qualifier=sp_name_qualifier,
|
||||
name_qualifier=name_qualifier))
|
||||
|
||||
@ -321,9 +319,8 @@ class Saml2Client(Base):
|
||||
srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP)
|
||||
|
||||
for destination in destinations(srvs):
|
||||
resp = self._use_soap(destination, "authn_query",
|
||||
consent=consent, extensions=extensions,
|
||||
sign=sign)
|
||||
resp = self._use_soap(destination, "authn_query", consent=consent,
|
||||
extensions=extensions, sign=sign)
|
||||
if resp:
|
||||
return resp
|
||||
|
||||
@ -339,7 +336,8 @@ class Saml2Client(Base):
|
||||
|
||||
:param entityid: To whom the query should be sent
|
||||
: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
|
||||
service provider or affiliation of providers for whom the
|
||||
identifier was generated.
|
||||
@ -353,7 +351,6 @@ class Saml2Client(Base):
|
||||
HTTP args if BINDING_HTT_POST was used.
|
||||
"""
|
||||
|
||||
|
||||
if real_id:
|
||||
response_args = {"real_id": real_id}
|
||||
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"
|
||||
MIME_PAOS = "application/vnd.paos+xml"
|
||||
|
||||
|
||||
class IdpUnspecified(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class VerifyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LogoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoServiceDefined(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Base(Entity):
|
||||
""" The basic pySAML2 service provider class """
|
||||
|
||||
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 identity_cache: Where the class should store identity information
|
||||
@ -166,25 +171,25 @@ class Base(Entity):
|
||||
# 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
|
||||
for Virtual organizations.
|
||||
|
||||
:param subject_id: The subject identifier
|
||||
:param name_id: The subject identifier
|
||||
:return: A possibly extended knowledge.
|
||||
"""
|
||||
|
||||
ava = {}
|
||||
try:
|
||||
(ava, _) = self.users.get_identity(subject_id)
|
||||
(ava, _) = self.users.get_identity(name_id)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# is this a Virtual Organization situation
|
||||
if self.vorg:
|
||||
if self.vorg.do_aggregation(subject_id):
|
||||
if self.vorg.do_aggregation(name_id):
|
||||
# Get the extended identity
|
||||
ava = self.users.get_identity(subject_id)[0]
|
||||
ava = self.users.get_identity(name_id)[0]
|
||||
return ava
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
@ -228,7 +233,8 @@ class Base(Entity):
|
||||
|
||||
args = {}
|
||||
try:
|
||||
args["assertion_consumer_service_url"] = kwargs["assertion_consumer_service_url"]
|
||||
args["assertion_consumer_service_url"] = kwargs[
|
||||
"assertion_consumer_service_url"]
|
||||
except KeyError:
|
||||
if service_url_binding is None:
|
||||
service_url = self.service_url(binding)
|
||||
@ -247,16 +253,17 @@ class Base(Entity):
|
||||
try:
|
||||
args["name_id_policy"] = kwargs["name_id_policy"]
|
||||
del kwargs["name_id_policy"]
|
||||
except:
|
||||
except KeyError:
|
||||
if allow_create:
|
||||
allow_create="true"
|
||||
allow_create = "true"
|
||||
else:
|
||||
allow_create="false"
|
||||
allow_create = "false"
|
||||
|
||||
# Profile stuff, should be configurable
|
||||
if nameid_format is None or nameid_format == NAMEID_FORMAT_TRANSIENT:
|
||||
name_id_policy = samlp.NameIDPolicy(allow_create=allow_create,
|
||||
format=NAMEID_FORMAT_TRANSIENT)
|
||||
if nameid_format is None or \
|
||||
nameid_format == NAMEID_FORMAT_TRANSIENT:
|
||||
name_id_policy = samlp.NameIDPolicy(
|
||||
allow_create=allow_create, format=NAMEID_FORMAT_TRANSIENT)
|
||||
else:
|
||||
name_id_policy = samlp.NameIDPolicy(allow_create=allow_create,
|
||||
format=nameid_format)
|
||||
@ -272,28 +279,31 @@ class Base(Entity):
|
||||
if kwargs:
|
||||
if extensions is None:
|
||||
extensions = []
|
||||
fargs = [p for p,c,r in AuthnRequest.c_attributes.values()]
|
||||
fargs.extend([p for p,c in AuthnRequest.c_children.values()])
|
||||
for key,val in kwargs.items():
|
||||
fargs = [p for p, c, r in AuthnRequest.c_attributes.values()]
|
||||
fargs.extend([p for p, c in AuthnRequest.c_children.values()])
|
||||
for key, val in kwargs.items():
|
||||
if key not in fargs:
|
||||
# extension elements allowed
|
||||
extensions.append(saml2.element_to_extension_element(val))
|
||||
else:
|
||||
args[key] = val
|
||||
try:
|
||||
del args["id"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return self._message(AuthnRequest, destination, sid, consent,
|
||||
extensions, sign,
|
||||
protocol_binding=binding,
|
||||
scoping=scoping, **args)
|
||||
|
||||
|
||||
def create_attribute_query(self, destination, name_id=None,
|
||||
attribute=None, sid=0, consent=None,
|
||||
extensions=None, sign=False, **kwargs):
|
||||
""" Constructs an AttributeQuery
|
||||
|
||||
: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
|
||||
asked for. The key are one of 4 variants:
|
||||
3-tuple of name_format,name and friendly_name,
|
||||
@ -333,7 +343,7 @@ class Base(Entity):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
subject = saml.Subject(name_id = name_id)
|
||||
subject = saml.Subject(name_id=name_id)
|
||||
|
||||
if attribute:
|
||||
attribute = do_attributes(attribute)
|
||||
@ -342,11 +352,9 @@ class Base(Entity):
|
||||
extensions, sign, subject=subject,
|
||||
attribute=attribute)
|
||||
|
||||
|
||||
# MUST use SOAP for
|
||||
# AssertionIDRequest, SubjectQuery,
|
||||
# AuthnQuery, AttributeQuery, or AuthzDecisionQuery
|
||||
|
||||
def create_authz_decision_query(self, destination, action,
|
||||
evidence=None, resource=None, subject=None,
|
||||
sid=0, consent=None, extensions=None,
|
||||
@ -369,8 +377,9 @@ class Base(Entity):
|
||||
extensions, sign, action=action, evidence=evidence,
|
||||
resource=resource, subject=subject)
|
||||
|
||||
def create_authz_decision_query_using_assertion(self, destination, assertion,
|
||||
action=None, resource=None,
|
||||
def create_authz_decision_query_using_assertion(self, destination,
|
||||
assertion, action=None,
|
||||
resource=None,
|
||||
subject=None, sid=0,
|
||||
consent=None,
|
||||
extensions=None,
|
||||
@ -397,13 +406,9 @@ class Base(Entity):
|
||||
else:
|
||||
_action = None
|
||||
|
||||
return self.create_authz_decision_query(destination,
|
||||
_action,
|
||||
saml.Evidence(assertion=assertion),
|
||||
resource, subject,
|
||||
sid=sid,
|
||||
consent=consent,
|
||||
extensions=extensions,
|
||||
return self.create_authz_decision_query(
|
||||
destination, _action, saml.Evidence(assertion=assertion),
|
||||
resource, subject, sid=sid, consent=consent, extensions=extensions,
|
||||
sign=sign)
|
||||
|
||||
def create_assertion_id_request(self, assertion_id_refs, **kwargs):
|
||||
@ -464,16 +469,17 @@ class Base(Entity):
|
||||
assert name_id or base_id or encrypted_id
|
||||
|
||||
if name_id:
|
||||
return self._message(NameIDMappingRequest, destination, sid, consent,
|
||||
extensions, sign, name_id_policy=name_id_policy,
|
||||
name_id=name_id)
|
||||
return self._message(NameIDMappingRequest, destination, sid,
|
||||
consent, extensions, sign,
|
||||
name_id_policy=name_id_policy, name_id=name_id)
|
||||
elif base_id:
|
||||
return self._message(NameIDMappingRequest, destination, sid, consent,
|
||||
extensions, sign, name_id_policy=name_id_policy,
|
||||
base_id=base_id)
|
||||
return self._message(NameIDMappingRequest, destination, sid,
|
||||
consent, extensions, sign,
|
||||
name_id_policy=name_id_policy, base_id=base_id)
|
||||
else:
|
||||
return self._message(NameIDMappingRequest, destination, sid, consent,
|
||||
extensions, sign, name_id_policy=name_id_policy,
|
||||
return self._message(NameIDMappingRequest, destination, sid,
|
||||
consent, extensions, sign,
|
||||
name_id_policy=name_id_policy,
|
||||
encrypted_id=encrypted_id)
|
||||
|
||||
# ======== response handling ===========
|
||||
@ -594,7 +600,7 @@ class Base(Entity):
|
||||
#
|
||||
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
||||
response_consumer_url=my_url,
|
||||
service = ECP_SERVICE)
|
||||
service=ECP_SERVICE)
|
||||
|
||||
# ----------------------------------------
|
||||
# <ecp:RelayState>
|
||||
@ -622,15 +628,15 @@ class Base(Entity):
|
||||
# SingleSignOnService
|
||||
_, location = self.pick_binding("single_sign_on_service",
|
||||
[_binding], entity_id=entityid)
|
||||
authn_req = self.create_authn_request(location,
|
||||
service_url_binding=BINDING_PAOS,
|
||||
**kwargs)
|
||||
authn_req = self.create_authn_request(
|
||||
location, service_url_binding=BINDING_PAOS, **kwargs)
|
||||
|
||||
# ----------------------------------------
|
||||
# The SOAP envelope
|
||||
# ----------------------------------------
|
||||
|
||||
soap_envelope = make_soap_enveloped_saml_thingy(authn_req,[paos_request,
|
||||
soap_envelope = make_soap_enveloped_saml_thingy(authn_req,
|
||||
[paos_request,
|
||||
relay_state])
|
||||
|
||||
return authn_req.id, "%s" % soap_envelope
|
||||
|
@ -140,9 +140,9 @@ class HTTPBase(object):
|
||||
if 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]:
|
||||
std_attr[set] = True
|
||||
std_attr[item] = True
|
||||
|
||||
if std_attr["domain"] and std_attr["domain"].startswith("."):
|
||||
std_attr["domain_initial_dot"] = True
|
||||
|
@ -19,9 +19,11 @@ logger = logging.getLogger(__name__)
|
||||
ATTR = ["name_qualifier", "sp_name_qualifier", "format", "sp_provided_id",
|
||||
"text"]
|
||||
|
||||
|
||||
class Unknown(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def code(item):
|
||||
_res = []
|
||||
i = 0
|
||||
@ -32,13 +34,15 @@ def code(item):
|
||||
i += 1
|
||||
return ",".join(_res)
|
||||
|
||||
def decode(str):
|
||||
|
||||
def decode(txt):
|
||||
_nid = NameID()
|
||||
for part in str.split(","):
|
||||
for part in txt.split(","):
|
||||
i, val = part.split("=")
|
||||
setattr(_nid, ATTR[int(i)], unquote(val))
|
||||
return _nid
|
||||
|
||||
|
||||
class IdentDB(object):
|
||||
""" A class that handles identifiers of entities
|
||||
Keeps a list of all nameIDs returned per SP
|
||||
@ -51,19 +55,19 @@ class IdentDB(object):
|
||||
self.domain = domain
|
||||
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.update(format)
|
||||
_id.update(nformat)
|
||||
if name_qualifier:
|
||||
_id.update(name_qualifier)
|
||||
if sp_name_qualifier:
|
||||
_id.update(sp_name_qualifier)
|
||||
return _id.hexdigest()
|
||||
|
||||
def create_id(self, format, name_qualifier="", sp_name_qualifier=""):
|
||||
_id = self._create_id(format, name_qualifier, sp_name_qualifier)
|
||||
def create_id(self, nformat, name_qualifier="", sp_name_qualifier=""):
|
||||
_id = self._create_id(nformat, name_qualifier, sp_name_qualifier)
|
||||
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
|
||||
|
||||
def store(self, ident, name_id):
|
||||
@ -92,30 +96,30 @@ class IdentDB(object):
|
||||
|
||||
del self.db[_cn]
|
||||
|
||||
def remove_local(self, id):
|
||||
if isinstance(id, unicode):
|
||||
id = id.encode("utf-8")
|
||||
def remove_local(self, sid):
|
||||
if isinstance(sid, unicode):
|
||||
sid = sid.encode("utf-8")
|
||||
|
||||
try:
|
||||
for val in self.db[id].split(" "):
|
||||
for val in self.db[sid].split(" "):
|
||||
try:
|
||||
del self.db[val]
|
||||
except KeyError:
|
||||
pass
|
||||
del self.db[id]
|
||||
del self.db[sid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_nameid(self, userid, format, sp_name_qualifier, name_qualifier):
|
||||
_id = self.create_id(format, name_qualifier, sp_name_qualifier)
|
||||
def get_nameid(self, userid, nformat, sp_name_qualifier, 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:
|
||||
raise Exception("Can't issue email nameids, unknown 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)
|
||||
|
||||
self.store(userid, nameid)
|
||||
@ -150,8 +154,9 @@ class IdentDB(object):
|
||||
if not name_qualifier:
|
||||
name_qualifier = self.name_qualifier
|
||||
|
||||
return {"format":nameid_format, "sp_name_qualifier": sp_name_qualifier,
|
||||
"name_qualifier":name_qualifier}
|
||||
return {"nformat": nameid_format,
|
||||
"sp_name_qualifier": sp_name_qualifier,
|
||||
"name_qualifier": name_qualifier}
|
||||
|
||||
def construct_nameid(self, userid, local_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,
|
||||
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)
|
||||
if nameid:
|
||||
return nameid
|
||||
@ -194,6 +200,8 @@ class IdentDB(object):
|
||||
try:
|
||||
return self.db[code(name_id)]
|
||||
except KeyError:
|
||||
logger.debug("name: %s" % code(name_id))
|
||||
logger.debug("id keys: %s" % self.db.keys())
|
||||
return None
|
||||
|
||||
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):
|
||||
# remove the first XML version/encoding line
|
||||
logger.debug("thingy0: %s" % thingy)
|
||||
_part = thingy.split("\n")
|
||||
thingy = _part[1]
|
||||
thingy = "".join(_part[1:])
|
||||
thingy = thingy.replace(PREFIX, "")
|
||||
logger.debug("thingy: %s" % thingy)
|
||||
_child = ElementTree.Element('')
|
||||
_child.tag = '{%s}FuddleMuddle' % DUMMY_NAMESPACE
|
||||
body.append(_child)
|
||||
@ -165,12 +167,12 @@ def make_soap_enveloped_saml_thingy(thingy, header_parts=None):
|
||||
# find an remove the namespace definition
|
||||
i = _str.find(DUMMY_NAMESPACE)
|
||||
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, "")
|
||||
first = _str.find("<%s:FuddleMuddle" % (cut1[6:9],))
|
||||
last = _str.find(">", first+14)
|
||||
cut2 = _str[first:last+1]
|
||||
return _str.replace(cut2,thingy)
|
||||
return _str.replace(cut2, thingy)
|
||||
else:
|
||||
thingy.become_child_element_of(body)
|
||||
return ElementTree.tostring(envelope, encoding="UTF-8")
|
||||
|
@ -3,6 +3,7 @@ from saml2.cache import Cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Population(object):
|
||||
def __init__(self, cache=None):
|
||||
if cache:
|
||||
@ -17,45 +18,48 @@ class Population(object):
|
||||
"""If there already are information from this source in the cache
|
||||
this function will overwrite that information"""
|
||||
|
||||
subject_id = session_info["name_id"]
|
||||
name_id = session_info["name_id"]
|
||||
issuer = 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"])
|
||||
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):
|
||||
"""
|
||||
|
||||
:param name_id: Identifier of the subject, a NameID instance
|
||||
:param sources: Sources for information about the subject
|
||||
: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(subject_id)
|
||||
sources = [m for m in sources \
|
||||
if not self.cache.active(subject_id, m)]
|
||||
sources = self.cache.entities(name_id)
|
||||
sources = [m for m in sources if not self.cache.active(name_id, m)]
|
||||
return sources
|
||||
|
||||
def issuers_of_info(self, subject_id):
|
||||
return self.cache.entities(subject_id)
|
||||
def issuers_of_info(self, name_id):
|
||||
return self.cache.entities(name_id)
|
||||
|
||||
def get_identity(self, subject_id, entities=None,
|
||||
check_not_on_or_after=True):
|
||||
return self.cache.get_identity(subject_id, entities,
|
||||
check_not_on_or_after)
|
||||
def get_identity(self, name_id, entities=None, check_not_on_or_after=True):
|
||||
return self.cache.get_identity(name_id, entities, check_not_on_or_after)
|
||||
|
||||
def get_info_from(self, subject_id, entity_id):
|
||||
return self.cache.get(subject_id, entity_id)
|
||||
def get_info_from(self, name_id, entity_id):
|
||||
return self.cache.get(name_id, entity_id)
|
||||
|
||||
def subjects(self):
|
||||
"""Returns the name id's for all the persons in the cache"""
|
||||
return self.cache.subjects()
|
||||
|
||||
def remove_person(self, subject_id):
|
||||
self.cache.delete(subject_id)
|
||||
def remove_person(self, name_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:
|
||||
return self.cache.get(subject_id, source_id,
|
||||
check_not_on_or_after)["name_id"]
|
||||
return self.cache.get(name_id, source_id, check_not_on_or_after)[
|
||||
"name_id"]
|
||||
except (KeyError, ValueError):
|
||||
return ""
|
||||
|
||||
def sources(self, subject_id):
|
||||
return self.cache.entities(subject_id)
|
||||
def sources(self, name_id):
|
||||
return self.cache.entities(name_id)
|
||||
|
@ -49,17 +49,21 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class IncorrectlySigned(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class VerificationError(Exception):
|
||||
pass
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _dummy(_):
|
||||
return None
|
||||
|
||||
|
||||
def for_me(condition, myself):
|
||||
# Am I among the intended audiences
|
||||
for restriction in condition.audience_restriction:
|
||||
@ -72,6 +76,7 @@ def for_me(condition, myself):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def authn_response(conf, return_addr, outstanding_queries=None, timeslack=0,
|
||||
asynchop=True, allow_unsolicited=False):
|
||||
sec = security_context(conf)
|
||||
@ -85,6 +90,7 @@ def authn_response(conf, return_addr, outstanding_queries=None, timeslack=0,
|
||||
return_addr, outstanding_queries, timeslack,
|
||||
asynchop=asynchop, allow_unsolicited=allow_unsolicited)
|
||||
|
||||
|
||||
# comes in over SOAP so synchronous
|
||||
def attribute_response(conf, return_addr, timeslack=0, asynchop=False,
|
||||
test=False):
|
||||
@ -99,6 +105,7 @@ def attribute_response(conf, return_addr, timeslack=0, asynchop=False,
|
||||
return_addr, timeslack, asynchop=asynchop,
|
||||
test=test)
|
||||
|
||||
|
||||
class StatusResponse(object):
|
||||
msgtype = "status_response"
|
||||
|
||||
@ -111,7 +118,7 @@ class StatusResponse(object):
|
||||
self.request_id = request_id
|
||||
|
||||
self.xmlstr = ""
|
||||
self.name_id = ""
|
||||
self.name_id = None
|
||||
self.response = None
|
||||
self.not_on_or_after = 0
|
||||
self.in_response_to = None
|
||||
@ -121,7 +128,7 @@ class StatusResponse(object):
|
||||
|
||||
def _clear(self):
|
||||
self.xmlstr = ""
|
||||
self.name_id = ""
|
||||
self.name_id = None
|
||||
self.response = None
|
||||
self.not_on_or_after = 0
|
||||
|
||||
@ -149,9 +156,10 @@ class StatusResponse(object):
|
||||
# This will check signature on Assertion which is the default
|
||||
try:
|
||||
self.response = self.sec.check_signature(instance)
|
||||
except SignatureError: # The response as a whole might be signed or not
|
||||
self.response = self.sec.check_signature(instance,
|
||||
samlp.NAMESPACE+":Response")
|
||||
except SignatureError:
|
||||
# The response as a whole might be signed or not
|
||||
self.response = self.sec.check_signature(
|
||||
instance, samlp.NAMESPACE + ":Response")
|
||||
else:
|
||||
self.not_signed = True
|
||||
self.response = instance
|
||||
@ -202,8 +210,7 @@ class StatusResponse(object):
|
||||
if self.request_id and self.in_response_to and \
|
||||
self.in_response_to != self.request_id:
|
||||
logger.error("Not the id I expected: %s != %s" % (
|
||||
self.in_response_to,
|
||||
self.request_id))
|
||||
self.in_response_to, self.request_id))
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -244,14 +251,17 @@ class StatusResponse(object):
|
||||
def issuer(self):
|
||||
return self.response.issuer.text.strip()
|
||||
|
||||
|
||||
class LogoutResponse(StatusResponse):
|
||||
msgtype = "logout_response"
|
||||
|
||||
def __init__(self, sec_context, return_addr=None, timeslack=0,
|
||||
asynchop=True):
|
||||
StatusResponse.__init__(self, sec_context, return_addr, timeslack,
|
||||
asynchop=asynchop)
|
||||
self.signature_check = self.sec.correctly_signed_logout_response
|
||||
|
||||
|
||||
class NameIDMappingResponse(StatusResponse):
|
||||
msgtype = "name_id_mapping_response"
|
||||
|
||||
@ -261,6 +271,7 @@ class NameIDMappingResponse(StatusResponse):
|
||||
request_id, asynchop)
|
||||
self.signature_check = self.sec.correctly_signed_name_id_mapping_response
|
||||
|
||||
|
||||
class ManageNameIDResponse(StatusResponse):
|
||||
msgtype = "manage_name_id_response"
|
||||
|
||||
@ -273,6 +284,7 @@ class ManageNameIDResponse(StatusResponse):
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AuthnResponse(StatusResponse):
|
||||
""" This is where all the profile compliance is checked.
|
||||
This one does saml2int compliance. """
|
||||
@ -335,7 +347,8 @@ class AuthnResponse(StatusResponse):
|
||||
if validate_on_or_after(authn_statement.session_not_on_or_after,
|
||||
self.timeslack):
|
||||
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:
|
||||
return False
|
||||
return True
|
||||
@ -364,8 +377,7 @@ class AuthnResponse(StatusResponse):
|
||||
try:
|
||||
if condition.not_on_or_after:
|
||||
self.not_on_or_after = validate_on_or_after(
|
||||
condition.not_on_or_after,
|
||||
self.timeslack)
|
||||
condition.not_on_or_after, self.timeslack)
|
||||
if condition.not_before:
|
||||
validate_before(condition.not_before, self.timeslack)
|
||||
except Exception, excp:
|
||||
@ -375,7 +387,6 @@ class AuthnResponse(StatusResponse):
|
||||
else:
|
||||
self.not_on_or_after = 0
|
||||
|
||||
|
||||
if not for_me(condition, self.entity_id):
|
||||
if not lax:
|
||||
#print condition
|
||||
@ -501,7 +512,7 @@ class AuthnResponse(StatusResponse):
|
||||
|
||||
# The subject must contain a name_id
|
||||
assert subject.name_id
|
||||
self.name_id = subject.name_id.text.strip()
|
||||
self.name_id = subject.name_id
|
||||
return self.name_id
|
||||
|
||||
def _assertion(self, assertion):
|
||||
@ -573,7 +584,6 @@ class AuthnResponse(StatusResponse):
|
||||
return self._encrypted_assertion(
|
||||
self.response.encrypted_assertion[0])
|
||||
|
||||
|
||||
def verify(self):
|
||||
""" Verify that the assertion is syntactically correct and
|
||||
the signature is correct if present."""
|
||||
@ -615,7 +625,7 @@ class AuthnResponse(StatusResponse):
|
||||
return res
|
||||
|
||||
def authz_decision_info(self):
|
||||
res = {"permit":[], "deny": [], "indeterminate":[] }
|
||||
res = {"permit": [], "deny": [], "indeterminate": []}
|
||||
for adstat in self.assertion.authz_decision_statement:
|
||||
# one of 'Permit', 'Deny', 'Indeterminate'
|
||||
res[adstat.decision.text.lower()] = adstat
|
||||
@ -632,19 +642,18 @@ class AuthnResponse(StatusResponse):
|
||||
nooa = self.not_on_or_after
|
||||
|
||||
if self.context == "AuthzQuery":
|
||||
return {"name_id": self.name_id,
|
||||
"came_from": self.came_from, "issuer": self.issuer(),
|
||||
"not_on_or_after": nooa,
|
||||
return {"name_id": self.name_id, "came_from": self.came_from,
|
||||
"issuer": self.issuer(), "not_on_or_after": nooa,
|
||||
"authz_decision_info": self.authz_decision_info() }
|
||||
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(),
|
||||
"not_on_or_after": nooa,
|
||||
"authn_info": self.authn_info() }
|
||||
"not_on_or_after": nooa, "authn_info": self.authn_info()}
|
||||
|
||||
def __str__(self):
|
||||
return "%s" % self.xmlstr
|
||||
|
||||
|
||||
class AuthnQueryResponse(AuthnResponse):
|
||||
msgtype = "authn_query_response"
|
||||
|
||||
@ -662,6 +671,7 @@ class AuthnQueryResponse(AuthnResponse):
|
||||
def condition_ok(self, lax=False): # Should I care about conditions ?
|
||||
return True
|
||||
|
||||
|
||||
class AttributeResponse(AuthnResponse):
|
||||
msgtype = "attribute_response"
|
||||
|
||||
@ -676,22 +686,26 @@ class AttributeResponse(AuthnResponse):
|
||||
self.assertion = None
|
||||
self.context = "AttrQuery"
|
||||
|
||||
|
||||
class AuthzResponse(AuthnResponse):
|
||||
""" A successful response will be in the form of assertions containing
|
||||
authorization decision statements."""
|
||||
msgtype = "authz_decision_response"
|
||||
|
||||
def __init__(self, sec_context, attribute_converters, entity_id,
|
||||
return_addr=None, timeslack=0, asynchop=False):
|
||||
AuthnResponse.__init__(self, sec_context, attribute_converters,
|
||||
entity_id, return_addr,
|
||||
timeslack=timeslack, asynchop=asynchop)
|
||||
entity_id, return_addr, timeslack=timeslack,
|
||||
asynchop=asynchop)
|
||||
self.entity_id = entity_id
|
||||
self.attribute_converters = attribute_converters
|
||||
self.assertion = None
|
||||
self.context = "AuthzQuery"
|
||||
|
||||
|
||||
class ArtifactResponse(AuthnResponse):
|
||||
msgtype = "artifact_response"
|
||||
|
||||
def __init__(self, sec_context, attribute_converters, entity_id,
|
||||
return_addr=None, timeslack=0, asynchop=False, test=False):
|
||||
|
||||
@ -704,10 +718,9 @@ class ArtifactResponse(AuthnResponse):
|
||||
self.context = "ArtifactResolve"
|
||||
|
||||
|
||||
def response_factory(xmlstr, conf, return_addr=None,
|
||||
outstanding_queries=None,
|
||||
timeslack=0, decode=True, request_id=0,
|
||||
origxml=None, asynchop=True, allow_unsolicited=False):
|
||||
def response_factory(xmlstr, conf, return_addr=None, outstanding_queries=None,
|
||||
timeslack=0, decode=True, request_id=0, origxml=None,
|
||||
asynchop=True, allow_unsolicited=False):
|
||||
sec_context = security_context(conf)
|
||||
if not timeslack:
|
||||
try:
|
||||
@ -724,8 +737,9 @@ def response_factory(xmlstr, conf, return_addr=None,
|
||||
response.loads(xmlstr, decode, origxml)
|
||||
if response.response.assertion or response.response.encrypted_assertion:
|
||||
authnresp = AuthnResponse(sec_context, attribute_converters,
|
||||
entity_id, return_addr, outstanding_queries,
|
||||
timeslack, asynchop, allow_unsolicited)
|
||||
entity_id, return_addr,
|
||||
outstanding_queries, timeslack, asynchop,
|
||||
allow_unsolicited)
|
||||
authnresp.update(response)
|
||||
return authnresp
|
||||
except TypeError:
|
||||
@ -741,6 +755,7 @@ def response_factory(xmlstr, conf, return_addr=None,
|
||||
# ===========================================================================
|
||||
# A class of it's own
|
||||
|
||||
|
||||
class AssertionIDResponse(object):
|
||||
msgtype = "assertion_id_response"
|
||||
|
||||
|
@ -4,6 +4,7 @@ from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VirtualOrg(object):
|
||||
def __init__(self, sp, vorg, cnf):
|
||||
self.sp = sp # The parent SP client instance
|
||||
@ -28,7 +29,7 @@ class VirtualOrg(object):
|
||||
"""
|
||||
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
|
||||
spoken too
|
||||
"""
|
||||
@ -40,12 +41,12 @@ class VirtualOrg(object):
|
||||
|
||||
# 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(
|
||||
subject_id, m)]
|
||||
name_id, m)]
|
||||
logger.info("VO members (not cached): %s" % vo_members)
|
||||
return vo_members
|
||||
|
||||
def get_common_identifier(self, subject_id):
|
||||
(ava, _) = self.sp.users.get_identity(subject_id)
|
||||
def get_common_identifier(self, name_id):
|
||||
(ava, _) = self.sp.users.get_identity(name_id)
|
||||
if ava == {}:
|
||||
return None
|
||||
|
||||
@ -56,36 +57,23 @@ class VirtualOrg(object):
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def do_aggregation(self, subject_id):
|
||||
def do_aggregation(self, name_id):
|
||||
|
||||
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:
|
||||
# Find the NameIDFormat and the SPNameQualifier
|
||||
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)
|
||||
com_identifier = self.get_common_identifier(name_id)
|
||||
|
||||
resolver = AttributeResolver(self.sp)
|
||||
# extends returns a list of session_infos
|
||||
for session_info in resolver.extend(com_identifier,
|
||||
self.sp.config.entityid,
|
||||
to_ask,
|
||||
name_id_format=name_id_format,
|
||||
sp_name_qualifier=sp_name_qualifier,
|
||||
real_id=subject_id):
|
||||
for session_info in resolver.extend(
|
||||
com_identifier, self.sp.config.entityid, to_ask):
|
||||
_ = self._cache_session(session_info)
|
||||
|
||||
logger.info(">Issuers: %s" % self.sp.users.issuers_of_info(
|
||||
subject_id))
|
||||
logger.info("AVA: %s" % (self.sp.users.get_identity(subject_id),))
|
||||
logger.info(">Issuers: %s" % self.sp.users.issuers_of_info(name_id))
|
||||
logger.info("AVA: %s" % (self.sp.users.get_identity(name_id),))
|
||||
|
||||
return True
|
||||
else:
|
||||
|
@ -163,7 +163,7 @@ class FakeIDP(Server):
|
||||
|
||||
req = logout_request_from_string(_str)
|
||||
|
||||
_resp = self.create_logout_response(req, binding)
|
||||
_resp = self.create_logout_response(req, [binding])
|
||||
|
||||
if binding == BINDING_SOAP:
|
||||
# SOAP packing
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
import time
|
||||
import py
|
||||
from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT
|
||||
from saml2.cache import Cache
|
||||
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,
|
||||
"issuer":"", "session_id":-1}
|
||||
@ -12,6 +14,13 @@ SESSION_INFO_PATTERN = {"ava":{}, "came from":"", "not_on_or_after":0,
|
||||
def _eq(l1,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:
|
||||
def setup_class(self):
|
||||
@ -22,10 +31,9 @@ class TestClass:
|
||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||
session_info = SESSION_INFO_PATTERN.copy()
|
||||
session_info["ava"] = {"givenName":["Derek"]}
|
||||
self.cache.set("1234", "abcd", session_info,
|
||||
not_on_or_after)
|
||||
self.cache.set(nid[0], "abcd", session_info, not_on_or_after)
|
||||
|
||||
(ava, inactive) = self.cache.get_identity("1234")
|
||||
(ava, inactive) = self.cache.get_identity(nid[0])
|
||||
assert inactive == []
|
||||
assert ava.keys() == ["givenName"]
|
||||
assert ava["givenName"] == ["Derek"]
|
||||
@ -34,84 +42,83 @@ class TestClass:
|
||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||
session_info = SESSION_INFO_PATTERN.copy()
|
||||
session_info["ava"] = {"surName":["Jeter"]}
|
||||
self.cache.set("1234", "bcde", session_info,
|
||||
not_on_or_after)
|
||||
self.cache.set(nid[0], "bcde", session_info, not_on_or_after)
|
||||
|
||||
(ava, inactive) = self.cache.get_identity("1234")
|
||||
(ava, inactive) = self.cache.get_identity(nid[0])
|
||||
assert inactive == []
|
||||
assert _eq(ava.keys(), ["givenName","surName"])
|
||||
assert ava["givenName"] == ["Derek"]
|
||||
assert ava["surName"] == ["Jeter"]
|
||||
|
||||
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"]
|
||||
assert _eq(ava.keys(), ["surName"])
|
||||
assert ava["surName"] == ["Jeter"]
|
||||
session_info = self.cache.get("1234","abcd")
|
||||
session_info = self.cache.get(nid[0], "abcd")
|
||||
ava = session_info["ava"]
|
||||
assert _eq(ava.keys(), ["givenName"])
|
||||
assert ava["givenName"] == ["Derek"]
|
||||
|
||||
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')")
|
||||
|
||||
def test_remove_info(self):
|
||||
self.cache.reset("1234", "bcde")
|
||||
assert self.cache.active("1234", "bcde") == False
|
||||
assert self.cache.active("1234", "abcd")
|
||||
self.cache.reset(nid[0], "bcde")
|
||||
assert self.cache.active(nid[0], "bcde") == False
|
||||
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 _eq(ava.keys(), ["givenName"])
|
||||
assert ava["givenName"] == ["Derek"]
|
||||
|
||||
def test_active(self):
|
||||
assert self.cache.active("1234", "bcde") == False
|
||||
assert self.cache.active("1234", "abcd")
|
||||
assert self.cache.active(nid[0], "bcde") == False
|
||||
assert self.cache.active(nid[0], "abcd")
|
||||
|
||||
def test_subjects(self):
|
||||
assert self.cache.subjects() == ["1234"]
|
||||
assert nid_eq(self.cache.subjects(), [nid[0]])
|
||||
|
||||
def test_second_subject(self):
|
||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||
session_info = SESSION_INFO_PATTERN.copy()
|
||||
session_info["ava"] = {"givenName":["Ichiro"],
|
||||
"surName":["Suzuki"]}
|
||||
self.cache.set("9876", "abcd", session_info,
|
||||
self.cache.set(nid[1], "abcd", session_info,
|
||||
not_on_or_after)
|
||||
|
||||
(ava, inactive) = self.cache.get_identity("9876")
|
||||
(ava, inactive) = self.cache.get_identity(nid[1])
|
||||
assert inactive == []
|
||||
assert _eq(ava.keys(), ["givenName","surName"])
|
||||
assert ava["givenName"] == ["Ichiro"]
|
||||
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):
|
||||
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))
|
||||
session_info = SESSION_INFO_PATTERN.copy()
|
||||
session_info["ava"] = {"givenName":["Ichiro"],
|
||||
"surName":["Suzuki"]}
|
||||
self.cache.set("9876", "bcde", session_info,
|
||||
self.cache.set(nid[1], "bcde", session_info,
|
||||
not_on_or_after)
|
||||
|
||||
assert _eq(self.cache.receivers("9876"), ["abcd", "bcde"])
|
||||
assert _eq(self.cache.subjects(), ["1234","9876"])
|
||||
assert _eq(self.cache.receivers(nid[1]), ["abcd", "bcde"])
|
||||
assert nid_eq(self.cache.subjects(), nid[0:2])
|
||||
|
||||
def test_timeout(self):
|
||||
not_on_or_after = str_to_time(in_a_while(seconds=1))
|
||||
session_info = SESSION_INFO_PATTERN.copy()
|
||||
session_info["ava"] = {"givenName":["Alex"],
|
||||
"surName":["Rodriguez"]}
|
||||
self.cache.set("1000", "bcde", session_info,
|
||||
self.cache.set(nid[2], "bcde", session_info,
|
||||
not_on_or_after)
|
||||
|
||||
time.sleep(2)
|
||||
(ava, inactive) = self.cache.get_identity("1000")
|
||||
(ava, inactive) = self.cache.get_identity(nid[2])
|
||||
assert inactive == ["bcde"]
|
||||
assert ava == {}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from saml2.ident import code
|
||||
from saml2.saml import NAMEID_FORMAT_TRANSIENT, NameID
|
||||
|
||||
from saml2.population import Population
|
||||
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_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):
|
||||
return set(l1) == set(l2)
|
||||
|
||||
@ -15,7 +25,7 @@ class TestPopulationMemoryBased():
|
||||
|
||||
def test_add_person(self):
|
||||
session_info = {
|
||||
"name_id": "123456",
|
||||
"name_id": nid,
|
||||
"issuer": IDP_ONE,
|
||||
"not_on_or_after": in_a_while(minutes=15),
|
||||
"ava": {
|
||||
@ -26,34 +36,34 @@ class TestPopulationMemoryBased():
|
||||
}
|
||||
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]
|
||||
subjects = self.population.subjects()
|
||||
assert subjects == ["123456"]
|
||||
subjects = [code(c) for c in self.population.subjects()]
|
||||
assert subjects == [cnid]
|
||||
# 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 == []
|
||||
# are any of the possible sources not used or gone stale
|
||||
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]
|
||||
|
||||
(identity, stale) = self.population.get_identity("123456")
|
||||
(identity, stale) = self.population.get_identity(nid)
|
||||
assert stale == []
|
||||
assert identity == {'mail': 'anders.andersson@example.com',
|
||||
'givenName': 'Anders',
|
||||
'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["name_id"] == '123456'
|
||||
assert info["name_id"] == nid
|
||||
assert info["ava"] == {'mail': 'anders.andersson@example.com',
|
||||
'givenName': 'Anders',
|
||||
'surName': 'Andersson'}
|
||||
|
||||
def test_extend_person(self):
|
||||
session_info = {
|
||||
"name_id": "123456",
|
||||
"name_id": nid,
|
||||
"issuer": IDP_OTHER,
|
||||
"not_on_or_after": in_a_while(minutes=15),
|
||||
"ava": {
|
||||
@ -63,33 +73,33 @@ class TestPopulationMemoryBased():
|
||||
|
||||
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])
|
||||
subjects = self.population.subjects()
|
||||
assert subjects == ["123456"]
|
||||
subjects = [code(c) for c in self.population.subjects()]
|
||||
assert subjects == [cnid]
|
||||
# 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 == []
|
||||
# are any of the possible sources not used or gone stale
|
||||
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 == []
|
||||
|
||||
(identity, stale) = self.population.get_identity("123456")
|
||||
(identity, stale) = self.population.get_identity(nid)
|
||||
assert stale == []
|
||||
assert identity == {'mail': 'anders.andersson@example.com',
|
||||
'givenName': 'Anders',
|
||||
'surName': 'Andersson',
|
||||
"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["name_id"] == '123456'
|
||||
assert info["name_id"] == nid
|
||||
assert info["ava"] == {"eduPersonEntitlement": "Anka"}
|
||||
|
||||
def test_add_another_person(self):
|
||||
session_info = {
|
||||
"name_id": "abcdef",
|
||||
"name_id": nida,
|
||||
"issuer": IDP_ONE,
|
||||
"not_on_or_after": in_a_while(minutes=15),
|
||||
"ava": {
|
||||
@ -100,28 +110,28 @@ class TestPopulationMemoryBased():
|
||||
}
|
||||
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]
|
||||
subjects = self.population.subjects()
|
||||
assert _eq(subjects, ["123456", "abcdef"])
|
||||
subjects = [code(c) for c in self.population.subjects()]
|
||||
assert _eq(subjects, [cnid, cnida])
|
||||
|
||||
stales = self.population.stale_sources_for_person("abcdef")
|
||||
stales = self.population.stale_sources_for_person(nida)
|
||||
assert stales == []
|
||||
# are any of the possible sources not used or gone stale
|
||||
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]
|
||||
|
||||
(identity, stale) = self.population.get_identity("abcdef")
|
||||
(identity, stale) = self.population.get_identity(nida)
|
||||
assert stale == []
|
||||
assert identity == {"givenName": "Bertil",
|
||||
"surName": "Bertilsson",
|
||||
"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["name_id"] == 'abcdef'
|
||||
assert info["name_id"] == nida
|
||||
assert info["ava"] == {"givenName": "Bertil",
|
||||
"surName": "Bertilsson",
|
||||
"mail": "bertil.bertilsson@example.com"
|
||||
@ -129,7 +139,7 @@ class TestPopulationMemoryBased():
|
||||
|
||||
def test_modify_person(self):
|
||||
session_info = {
|
||||
"name_id": "123456",
|
||||
"name_id": nid,
|
||||
"issuer": IDP_ONE,
|
||||
"not_on_or_after": in_a_while(minutes=15),
|
||||
"ava": {
|
||||
@ -140,26 +150,26 @@ class TestPopulationMemoryBased():
|
||||
}
|
||||
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])
|
||||
subjects = self.population.subjects()
|
||||
assert _eq(subjects, ["123456", "abcdef"])
|
||||
subjects = [code(c) for c in self.population.subjects()]
|
||||
assert _eq(subjects, [cnid, cnida])
|
||||
# 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 == []
|
||||
# are any of the possible sources not used or gone stale
|
||||
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 == []
|
||||
|
||||
(identity, stale) = self.population.get_identity("123456")
|
||||
(identity, stale) = self.population.get_identity(nid)
|
||||
assert stale == []
|
||||
assert identity == {'mail': 'arne.andersson@example.com',
|
||||
'givenName': 'Arne',
|
||||
'surName': 'Andersson',
|
||||
"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["name_id"] == '123456'
|
||||
assert info["name_id"] == nid
|
||||
assert info["ava"] == {"eduPersonEntitlement": "Anka"}
|
@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
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.server import Server
|
||||
@ -18,6 +18,9 @@ from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
|
||||
from py.test import raises
|
||||
import os
|
||||
|
||||
nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||
text="123456")
|
||||
|
||||
def _eq(l1,l2):
|
||||
return set(l1) == set(l2)
|
||||
|
||||
@ -150,8 +153,7 @@ class TestServer1():
|
||||
|
||||
def test_parse_ok_request(self):
|
||||
authn_request = self.client.create_authn_request(
|
||||
id = "id1",
|
||||
destination = "http://localhost:8088/sso")
|
||||
sid="id1", destination="http://localhost:8088/sso")
|
||||
|
||||
print authn_request
|
||||
binding = BINDING_HTTP_REDIRECT
|
||||
@ -335,7 +337,7 @@ class TestServer1():
|
||||
def test_slo_http_post(self):
|
||||
soon = time_util.in_a_while(days=1)
|
||||
sinfo = {
|
||||
"name_id": "foba0001",
|
||||
"name_id": nid,
|
||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||
"not_on_or_after" : soon,
|
||||
"user": {
|
||||
@ -346,10 +348,9 @@ class TestServer1():
|
||||
self.client.users.add_information_about_person(sinfo)
|
||||
|
||||
logout_request = self.client.create_logout_request(
|
||||
destination = "http://localhost:8088/slop",
|
||||
subject_id="foba0001",
|
||||
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
||||
reason = "I'm tired of this")
|
||||
destination="http://localhost:8088/slop", name_id=nid,
|
||||
issuer_entity_id="urn:mace:example.com:saml:roland:idp",
|
||||
reason="I'm tired of this")
|
||||
|
||||
intermed = base64.b64encode("%s" % logout_request)
|
||||
|
||||
@ -360,9 +361,9 @@ class TestServer1():
|
||||
def test_slo_soap(self):
|
||||
soon = time_util.in_a_while(days=1)
|
||||
sinfo = {
|
||||
"name_id": "foba0001",
|
||||
"name_id": nid,
|
||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||
"not_on_or_after" : soon,
|
||||
"not_on_or_after": soon,
|
||||
"user": {
|
||||
"givenName": "Leo",
|
||||
"surName": "Laport",
|
||||
@ -373,10 +374,9 @@ class TestServer1():
|
||||
sp.users.add_information_about_person(sinfo)
|
||||
|
||||
logout_request = sp.create_logout_request(
|
||||
subject_id = "foba0001",
|
||||
destination = "http://localhost:8088/slo",
|
||||
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
||||
reason = "I'm tired of this")
|
||||
name_id=nid, destination="http://localhost:8088/slo",
|
||||
issuer_entity_id="urn:mace:example.com:saml:roland:idp",
|
||||
reason="I'm tired of this")
|
||||
|
||||
#_ = 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)
|
||||
sinfo = {
|
||||
"name_id": "foba0001",
|
||||
"name_id": nid,
|
||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||
"not_on_or_after" : soon,
|
||||
"user": {
|
||||
@ -440,7 +440,7 @@ def _logout_request(conf_file):
|
||||
sp.users.add_information_about_person(sinfo)
|
||||
|
||||
return sp.create_logout_request(
|
||||
subject_id = "foba0001",
|
||||
name_id = nid,
|
||||
destination = "http://localhost:8088/slo",
|
||||
issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
|
||||
reason = "I'm tired of this")
|
||||
|
@ -3,14 +3,17 @@
|
||||
|
||||
import base64
|
||||
import urllib
|
||||
from saml2.response import LogoutResponse
|
||||
from saml2.samlp import logout_request_from_string
|
||||
|
||||
from saml2.client import Saml2Client
|
||||
from saml2 import samlp, BINDING_HTTP_POST
|
||||
from saml2 import saml, config, class_name
|
||||
from saml2.config import SPConfig
|
||||
from saml2.saml import NAMEID_FORMAT_PERSISTENT, NAMEID_FORMAT_TRANSIENT, \
|
||||
AUTHN_PASSWORD
|
||||
from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
||||
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.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")
|
||||
|
||||
nid = NameID(name_qualifier="foo", format=NAMEID_FORMAT_TRANSIENT,
|
||||
text="123456")
|
||||
|
||||
class TestClient:
|
||||
def setup_class(self):
|
||||
self.server = Server("idp_conf")
|
||||
@ -68,7 +74,7 @@ class TestClient:
|
||||
"https://idp.example.com/idp/",
|
||||
"E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
|
||||
format=saml.NAMEID_FORMAT_PERSISTENT,
|
||||
id="id1")
|
||||
sid="id1")
|
||||
reqstr = "%s" % req.to_string()
|
||||
|
||||
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,
|
||||
},
|
||||
format=saml.NAMEID_FORMAT_PERSISTENT,
|
||||
id="id1")
|
||||
sid="id1")
|
||||
|
||||
print req.to_string()
|
||||
assert req.destination == "https://idp.example.com/idp/"
|
||||
@ -144,7 +150,7 @@ class TestClient:
|
||||
"https://aai-demo-idp.switch.ch/idp/shibboleth",
|
||||
"_e7b68a04488f715cda642fbdd90099f5",
|
||||
format=saml.NAMEID_FORMAT_TRANSIENT,
|
||||
id="id1")
|
||||
sid="id1")
|
||||
|
||||
assert isinstance(req, samlp.AttributeQuery)
|
||||
assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth"
|
||||
@ -178,7 +184,7 @@ class TestClient:
|
||||
def test_create_auth_request_0(self):
|
||||
ar_str = "%s" % self.client.create_authn_request(
|
||||
"http://www.example.com/sso",
|
||||
id="id1")
|
||||
sid="id1")
|
||||
ar = samlp.authn_request_from_string(ar_str)
|
||||
print ar
|
||||
assert ar.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
|
||||
@ -199,7 +205,7 @@ class TestClient:
|
||||
"http://www.example.com/sso",
|
||||
"urn:mace:example.com:it:tek", # vo
|
||||
nameid_format=NAMEID_FORMAT_PERSISTENT,
|
||||
id="666")
|
||||
sid="666")
|
||||
|
||||
ar = samlp.authn_request_from_string(ar_str)
|
||||
print ar
|
||||
@ -221,7 +227,7 @@ class TestClient:
|
||||
ar_str = "%s" % self.client.create_authn_request(
|
||||
"http://www.example.com/sso",
|
||||
sign=True,
|
||||
id="id1")
|
||||
sid="id1")
|
||||
|
||||
ar = samlp.authn_request_from_string(ar_str)
|
||||
|
||||
@ -343,10 +349,10 @@ class TestClientWithDummy():
|
||||
self.client.send = self.server.receive
|
||||
|
||||
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")
|
||||
|
||||
assert isinstance(id, basestring)
|
||||
assert isinstance(sid, basestring)
|
||||
assert len(http_args) == 4
|
||||
assert http_args["headers"][0][0] == "Location"
|
||||
assert http_args["data"] == []
|
||||
@ -363,7 +369,7 @@ class TestClientWithDummy():
|
||||
|
||||
# information about the user from an IdP
|
||||
session_info = {
|
||||
"name_id": "123456",
|
||||
"name_id": nid,
|
||||
"issuer": "urn:mace:example.com:saml:roland:idp",
|
||||
"not_on_or_after": in_a_while(minutes=15),
|
||||
"ava": {
|
||||
@ -373,24 +379,18 @@ class TestClientWithDummy():
|
||||
}
|
||||
}
|
||||
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"]
|
||||
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
|
||||
assert resp
|
||||
assert len(resp) == 1
|
||||
assert resp.keys() == entity_ids
|
||||
http_args = resp[entity_ids[0]]
|
||||
assert isinstance(http_args, dict)
|
||||
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"
|
||||
response = resp[entity_ids[0]]
|
||||
assert isinstance(response, LogoutResponse)
|
||||
|
||||
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",
|
||||
relay_state="really",
|
||||
binding=BINDING_HTTP_POST)
|
||||
@ -403,210 +403,17 @@ class TestClientWithDummy():
|
||||
http_args["data"] = urllib.urlencode(_dic)
|
||||
http_args["method"] = "POST"
|
||||
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)
|
||||
print response.text
|
||||
_dic = unpack_form(response.text[3], "SAMLResponse")
|
||||
resp = self.client.parse_authn_request_response(_dic["SAMLResponse"],
|
||||
BINDING_HTTP_POST,
|
||||
{id: "/"})
|
||||
{sid: "/"})
|
||||
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
|
||||
|
||||
# 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'
|
||||
|
||||
from saml2 import config
|
||||
from saml2.client import Saml2Client
|
||||
from saml2.time_util import str_to_time, in_a_while
|
||||
|
||||
SESSION_INFO_PATTERN = {"ava":{}, "came from":"", "not_on_or_after":0,
|
||||
"issuer":"", "session_id":-1}
|
||||
SESSION_INFO_PATTERN = {"ava": {}, "came from": "", "not_on_or_after": 0,
|
||||
"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):
|
||||
not_on_or_after = str_to_time(in_a_while(days=1))
|
||||
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["name_id"] = "abcdefgh"
|
||||
session_info["name_id"] = nid
|
||||
session_info["not_on_or_after"] = not_on_or_after
|
||||
# subject_id, entity_id, info, timestamp
|
||||
sp.users.add_information_about_person(session_info)
|
||||
|
||||
|
||||
class TestVirtualOrg():
|
||||
def setup_class(self):
|
||||
conf = config.SPConfig()
|
||||
@ -28,24 +37,25 @@ class TestVirtualOrg():
|
||||
add_derek_info(self.sp)
|
||||
|
||||
def test_mta(self):
|
||||
aas = self.vo.members_to_ask("abcdefgh")
|
||||
aas = self.vo.members_to_ask(nid)
|
||||
print aas
|
||||
assert len(aas) == 1
|
||||
assert 'urn:mace:example.com:saml:aa' in aas
|
||||
|
||||
def test_unknown_subject(self):
|
||||
aas = self.vo.members_to_ask("01234567")
|
||||
aas = self.vo.members_to_ask(nid0)
|
||||
print aas
|
||||
assert len(aas) == 2
|
||||
|
||||
def test_id(self):
|
||||
id = self.vo.get_common_identifier("abcdefgh")
|
||||
print id
|
||||
assert id == "deje0001"
|
||||
cid = self.vo.get_common_identifier(nid)
|
||||
print cid
|
||||
assert cid == "deje0001"
|
||||
|
||||
def test_id_unknown(self):
|
||||
id = self.vo.get_common_identifier("01234567")
|
||||
assert id is None
|
||||
cid = self.vo.get_common_identifier(nid0)
|
||||
assert cid is None
|
||||
|
||||
|
||||
class TestVirtualOrg_2():
|
||||
def setup_class(self):
|
||||
@ -56,21 +66,21 @@ class TestVirtualOrg_2():
|
||||
add_derek_info(self.sp)
|
||||
|
||||
def test_mta(self):
|
||||
aas = self.sp.vorg.members_to_ask("abcdefgh")
|
||||
aas = self.sp.vorg.members_to_ask(nid)
|
||||
print aas
|
||||
assert len(aas) == 1
|
||||
assert 'urn:mace:example.com:saml:aa' in aas
|
||||
|
||||
def test_unknown_subject(self):
|
||||
aas = self.sp.vorg.members_to_ask("01234567")
|
||||
aas = self.sp.vorg.members_to_ask(nid0)
|
||||
print aas
|
||||
assert len(aas) == 2
|
||||
|
||||
def test_id(self):
|
||||
id = self.sp.vorg.get_common_identifier("abcdefgh")
|
||||
print id
|
||||
assert id == "deje0001"
|
||||
cid = self.sp.vorg.get_common_identifier(nid)
|
||||
print cid
|
||||
assert cid == "deje0001"
|
||||
|
||||
def test_id_unknown(self):
|
||||
id = self.sp.vorg.get_common_identifier("01234567")
|
||||
assert id is None
|
||||
cid = self.sp.vorg.get_common_identifier(nid0)
|
||||
assert cid is None
|
||||
|
Loading…
Reference in New Issue
Block a user