Even more refactoring
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from cgi import parse_qs
|
from urlparse import parse_qs
|
||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT
|
||||||
|
|
||||||
logger = logging.getLogger("saml2.SP")
|
logger = logging.getLogger("saml2.SP")
|
||||||
@@ -75,19 +75,22 @@ def not_authn(environ, start_response):
|
|||||||
def slo(environ, start_response, user):
|
def slo(environ, start_response, user):
|
||||||
# so here I might get either a LogoutResponse or a LogoutRequest
|
# so here I might get either a LogoutResponse or a LogoutRequest
|
||||||
client = environ['repoze.who.plugins']["saml2auth"]
|
client = environ['repoze.who.plugins']["saml2auth"]
|
||||||
|
sc = client.saml_client
|
||||||
sids = None
|
sids = None
|
||||||
if "QUERY_STRING" in environ:
|
if "QUERY_STRING" in environ:
|
||||||
query = parse_qs(environ["QUERY_STRING"])
|
query = parse_qs(environ["QUERY_STRING"])
|
||||||
logger.info("query: %s" % query)
|
logger.info("query: %s" % query)
|
||||||
try:
|
try:
|
||||||
(sids, code, head, message) = client.saml_client.logout_response(
|
response = sc.logout_request_response(query["SAMLResponse"][0],
|
||||||
query["SAMLResponse"][0],
|
binding=BINDING_HTTP_REDIRECT)
|
||||||
binding=BINDING_HTTP_REDIRECT)
|
if response:
|
||||||
logger.info("LOGOUT reponse parsed OK")
|
logger.info("LOGOUT response parsed OK")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# return error reply
|
# return error reply
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
request = sc.lo
|
||||||
if not sids:
|
if not sids:
|
||||||
start_response("302 Found", [("Location", "/done")])
|
start_response("302 Found", [("Location", "/done")])
|
||||||
return ["Successfull Logout"]
|
return ["Successfull Logout"]
|
||||||
|
@@ -120,7 +120,6 @@ class Base(Entity):
|
|||||||
setattr(self, foo, False)
|
setattr(self, foo, False)
|
||||||
|
|
||||||
# extra randomness
|
# extra randomness
|
||||||
self.seed = rndstr(32)
|
|
||||||
self.logout_requests_signed_default = True
|
self.logout_requests_signed_default = True
|
||||||
self.allow_unsolicited = self.config.getattr("allow_unsolicited", "sp")
|
self.allow_unsolicited = self.config.getattr("allow_unsolicited", "sp")
|
||||||
|
|
||||||
@@ -138,18 +137,6 @@ class Base(Entity):
|
|||||||
vals.append(signature(self.config.secret, vals))
|
vals.append(signature(self.config.secret, vals))
|
||||||
return "|".join(vals)
|
return "|".join(vals)
|
||||||
|
|
||||||
def _issuer(self, entityid=None):
|
|
||||||
""" Return an Issuer instance """
|
|
||||||
if entityid:
|
|
||||||
if isinstance(entityid, saml.Issuer):
|
|
||||||
return entityid
|
|
||||||
else:
|
|
||||||
return saml.Issuer(text=entityid,
|
|
||||||
format=saml.NAMEID_FORMAT_ENTITY)
|
|
||||||
else:
|
|
||||||
return saml.Issuer(text=self.config.entityid,
|
|
||||||
format=saml.NAMEID_FORMAT_ENTITY)
|
|
||||||
|
|
||||||
def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT):
|
def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT):
|
||||||
if entityid:
|
if entityid:
|
||||||
# verify that it's in the metadata
|
# verify that it's in the metadata
|
||||||
@@ -213,45 +200,6 @@ class Base(Entity):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _message(self, request_cls, destination=None, id=0,
|
|
||||||
consent=None, extensions=None, sign=False, **kwargs):
|
|
||||||
"""
|
|
||||||
Some parameters appear in all requests so simplify by doing
|
|
||||||
it in one place
|
|
||||||
|
|
||||||
:param request_cls: The specific request type
|
|
||||||
:param destination: The recipient
|
|
||||||
:param id: A message identifier
|
|
||||||
:param consent: Whether the principal have given her consent
|
|
||||||
:param extensions: Possible extensions
|
|
||||||
:param kwargs: Key word arguments specific to one request type
|
|
||||||
:return: An instance of the request_cls
|
|
||||||
"""
|
|
||||||
if not id:
|
|
||||||
id = sid(self.seed)
|
|
||||||
|
|
||||||
req = request_cls(id=id, version=VERSION, issue_instant=instant(),
|
|
||||||
issuer=self._issuer(), **kwargs)
|
|
||||||
|
|
||||||
if destination:
|
|
||||||
req.destination = destination
|
|
||||||
|
|
||||||
if consent:
|
|
||||||
req.consent = consent
|
|
||||||
|
|
||||||
if extensions:
|
|
||||||
req.extensions = extensions
|
|
||||||
|
|
||||||
if sign:
|
|
||||||
req.signature = pre_signature_part(req.id, self.sec.my_cert, 1)
|
|
||||||
to_sign = [(class_name(req), req.id)]
|
|
||||||
else:
|
|
||||||
to_sign = []
|
|
||||||
|
|
||||||
logger.info("REQUEST: %s" % req)
|
|
||||||
|
|
||||||
return signed_instance_factory(req, self.sec, to_sign)
|
|
||||||
|
|
||||||
def create_authn_request(self, destination, vorg="", scoping=None,
|
def create_authn_request(self, destination, vorg="", scoping=None,
|
||||||
binding=saml2.BINDING_HTTP_POST,
|
binding=saml2.BINDING_HTTP_POST,
|
||||||
nameid_format=NAMEID_FORMAT_TRANSIENT,
|
nameid_format=NAMEID_FORMAT_TRANSIENT,
|
||||||
@@ -360,63 +308,6 @@ class Base(Entity):
|
|||||||
attribute=attribute)
|
attribute=attribute)
|
||||||
|
|
||||||
|
|
||||||
def create_logout_request(self, destination, issuer_entity_id,
|
|
||||||
subject_id=None, name_id=None,
|
|
||||||
reason=None, expire=None,
|
|
||||||
id=0, consent=None, extensions=None, sign=False):
|
|
||||||
""" Constructs a LogoutRequest
|
|
||||||
|
|
||||||
:param destination: Destination of the request
|
|
||||||
:param issuer_entity_id: The entity ID of the IdP the request is
|
|
||||||
target at.
|
|
||||||
:param subject_id: The identifier of the subject
|
|
||||||
:param name_id: A NameID instance identifying the subject
|
|
||||||
:param reason: An indication of the reason for the logout, in the
|
|
||||||
form of a URI reference.
|
|
||||||
:param expire: The time at which the request expires,
|
|
||||||
after which the recipient may discard the message.
|
|
||||||
:param id: Request identifier
|
|
||||||
:param consent: Whether the principal have given her consent
|
|
||||||
:param extensions: Possible extensions
|
|
||||||
:param sign: Whether the query should be signed or not.
|
|
||||||
:return: A LogoutRequest instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
if subject_id:
|
|
||||||
name_id = saml.NameID(
|
|
||||||
text = self.users.get_entityid(subject_id, issuer_entity_id,
|
|
||||||
False))
|
|
||||||
if not name_id:
|
|
||||||
raise Exception("Missing subject identification")
|
|
||||||
|
|
||||||
return self._message(LogoutRequest, destination, id,
|
|
||||||
consent, extensions, sign, name_id=name_id,
|
|
||||||
reason=reason, not_on_or_after=expire)
|
|
||||||
|
|
||||||
def create_logout_response(self, idp_entity_id, request_id,
|
|
||||||
status_code,
|
|
||||||
binding=BINDING_HTTP_REDIRECT):
|
|
||||||
""" Constructs a LogoutResponse
|
|
||||||
|
|
||||||
:param idp_entity_id: The entityid of the IdP that want to do the
|
|
||||||
logout
|
|
||||||
:param request_id: The Id of the request we are replying to
|
|
||||||
:param status_code: The status code of the response
|
|
||||||
:param binding: The type of binding that will be used for the response
|
|
||||||
:return: A LogoutResponse instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
srvs = self.metadata.single_logout_services(idp_entity_id, "idpsso",
|
|
||||||
binding=binding)
|
|
||||||
destination = destinations(srvs)[0]
|
|
||||||
|
|
||||||
status = samlp.Status(
|
|
||||||
status_code=samlp.StatusCode(value=status_code))
|
|
||||||
|
|
||||||
return destination, self._message(LogoutResponse, destination,
|
|
||||||
in_response_to=request_id,
|
|
||||||
status=status)
|
|
||||||
|
|
||||||
# MUST use SOAP for
|
# MUST use SOAP for
|
||||||
# AssertionIDRequest, SubjectQuery,
|
# AssertionIDRequest, SubjectQuery,
|
||||||
# AuthnQuery, AttributeQuery, or AuthzDecisionQuery
|
# AuthnQuery, AttributeQuery, or AuthzDecisionQuery
|
||||||
@@ -597,10 +488,11 @@ class Base(Entity):
|
|||||||
status=status,
|
status=status,
|
||||||
extension_elements=[ee])
|
extension_elements=[ee])
|
||||||
|
|
||||||
# ======== response handling ===========
|
# ======== response handling ===========
|
||||||
|
|
||||||
def _response(self, post, outstanding, decode=True, asynchop=True):
|
def parse_authn_request_response(self, post, outstanding, decode=True,
|
||||||
""" Deal with an AuthnResponse or LogoutResponse
|
asynchop=True):
|
||||||
|
""" Deal with an AuthnResponse
|
||||||
|
|
||||||
:param post: The reply as a dictionary
|
:param post: The reply as a dictionary
|
||||||
:param outstanding: A dictionary with session IDs as keys and
|
:param outstanding: A dictionary with session IDs as keys and
|
||||||
@@ -645,79 +537,26 @@ class Base(Entity):
|
|||||||
saml2.class_name(resp),))
|
saml2.class_name(resp),))
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def authn_request_response(self, post, outstanding, decode=True,
|
|
||||||
asynchop=True):
|
|
||||||
return self._response(post, outstanding, decode, asynchop)
|
|
||||||
|
|
||||||
def logout_request_response(self, xmlstr, binding=BINDING_SOAP):
|
|
||||||
""" Deal with a LogoutResponse
|
|
||||||
|
|
||||||
:param xmlstr: The response as a xml string
|
|
||||||
:param binding: What type of binding this message came through.
|
|
||||||
:return: None if the reply doesn't contain a valid SAML LogoutResponse,
|
|
||||||
otherwise the reponse if the logout was successful and None if it
|
|
||||||
was not.
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = None
|
|
||||||
|
|
||||||
if xmlstr:
|
|
||||||
if binding == BINDING_HTTP_REDIRECT:
|
|
||||||
try:
|
|
||||||
# expected return address
|
|
||||||
return_addr = self.config.endpoint("single_logout_service",
|
|
||||||
binding=binding)[0]
|
|
||||||
except Exception:
|
|
||||||
logger.info("Not supposed to handle this!")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return_addr = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = LogoutResponse(self.sec, return_addr)
|
|
||||||
except Exception, exc:
|
|
||||||
logger.info("%s" % exc)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if binding == BINDING_HTTP_REDIRECT:
|
|
||||||
xmlstr = decode_base64_and_inflate(xmlstr)
|
|
||||||
elif binding == BINDING_HTTP_POST:
|
|
||||||
xmlstr = base64.b64decode(xmlstr)
|
|
||||||
|
|
||||||
logger.debug("XMLSTR: %s" % xmlstr)
|
|
||||||
|
|
||||||
response = response.loads(xmlstr, False)
|
|
||||||
|
|
||||||
if response:
|
|
||||||
response = response.verify()
|
|
||||||
|
|
||||||
if not response:
|
|
||||||
return None
|
|
||||||
|
|
||||||
logger.debug(response)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def authz_decision_query_response(self, response):
|
def parse_authz_decision_query_response(self, response):
|
||||||
""" Verify that the response is OK
|
""" Verify that the response is OK
|
||||||
"""
|
"""
|
||||||
resp = samlp.response_from_string(response)
|
resp = samlp.response_from_string(response)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def assertion_id_request_response(self, response):
|
def parse_assertion_id_request_response(self, response):
|
||||||
""" Verify that the response is OK
|
""" Verify that the response is OK
|
||||||
"""
|
"""
|
||||||
resp = samlp.response_from_string(response)
|
resp = samlp.response_from_string(response)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def authn_query_response(self, response):
|
def parse_authn_query_response(self, response):
|
||||||
""" Verify that the response is OK
|
""" Verify that the response is OK
|
||||||
"""
|
"""
|
||||||
resp = samlp.response_from_string(response)
|
resp = samlp.response_from_string(response)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def attribute_query_response(self, response, **kwargs):
|
def parse_attribute_query_response(self, response, **kwargs):
|
||||||
try:
|
try:
|
||||||
# synchronous operation
|
# synchronous operation
|
||||||
aresp = attribute_response(self.config, self.config.entityid)
|
aresp = attribute_response(self.config, self.config.entityid)
|
||||||
@@ -740,7 +579,7 @@ class Base(Entity):
|
|||||||
logger.info("session: %s" % session_info)
|
logger.info("session: %s" % session_info)
|
||||||
return session_info
|
return session_info
|
||||||
|
|
||||||
def artifact_resolve_response(self, txt, **kwargs):
|
def parse_artifact_resolve_response(self, txt, **kwargs):
|
||||||
"""
|
"""
|
||||||
Always done over SOAP
|
Always done over SOAP
|
||||||
|
|
||||||
|
@@ -16,7 +16,8 @@ def _dummy(_arg):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
class Request(object):
|
class Request(object):
|
||||||
def __init__(self, sec_context, receiver_addrs, timeslack=0):
|
def __init__(self, sec_context, receiver_addrs, attribute_converters=None,
|
||||||
|
timeslack=0):
|
||||||
self.sec = sec_context
|
self.sec = sec_context
|
||||||
self.receiver_addrs = receiver_addrs
|
self.receiver_addrs = receiver_addrs
|
||||||
self.timeslack = timeslack
|
self.timeslack = timeslack
|
||||||
@@ -24,6 +25,7 @@ class Request(object):
|
|||||||
self.name_id = ""
|
self.name_id = ""
|
||||||
self.message = None
|
self.message = None
|
||||||
self.not_on_or_after = 0
|
self.not_on_or_after = 0
|
||||||
|
self.attribute_converters = attribute_converters
|
||||||
|
|
||||||
self.signature_check = _dummy # has to be set !!!
|
self.signature_check = _dummy # has to be set !!!
|
||||||
|
|
||||||
@@ -123,14 +125,18 @@ class Request(object):
|
|||||||
return self.message.issuer.text()
|
return self.message.issuer.text()
|
||||||
|
|
||||||
class LogoutRequest(Request):
|
class LogoutRequest(Request):
|
||||||
def __init__(self, sec_context, receiver_addrs, timeslack=0):
|
def __init__(self, sec_context, receiver_addrs, attribute_converters=None,
|
||||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
timeslack=0):
|
||||||
|
Request.__init__(self, sec_context, receiver_addrs,
|
||||||
|
attribute_converters, timeslack)
|
||||||
self.signature_check = self.sec.correctly_signed_logout_request
|
self.signature_check = self.sec.correctly_signed_logout_request
|
||||||
|
|
||||||
|
|
||||||
class AttributeQuery(Request):
|
class AttributeQuery(Request):
|
||||||
def __init__(self, sec_context, receiver_addrs, timeslack=0):
|
def __init__(self, sec_context, receiver_addrs, attribute_converters=None,
|
||||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
timeslack=0):
|
||||||
|
Request.__init__(self, sec_context, receiver_addrs,
|
||||||
|
attribute_converters, timeslack)
|
||||||
self.signature_check = self.sec.correctly_signed_attribute_query
|
self.signature_check = self.sec.correctly_signed_attribute_query
|
||||||
|
|
||||||
def attribute(self):
|
def attribute(self):
|
||||||
@@ -138,10 +144,10 @@ class AttributeQuery(Request):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
class AuthnRequest(Request):
|
class AuthnRequest(Request):
|
||||||
def __init__(self, sec_context, attribute_converters, receiver_addrs,
|
def __init__(self, sec_context, receiver_addrs, attribute_converters,
|
||||||
timeslack=0):
|
timeslack=0):
|
||||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
Request.__init__(self, sec_context, receiver_addrs,
|
||||||
self.attribute_converters = attribute_converters
|
attribute_converters, timeslack)
|
||||||
self.signature_check = self.sec.correctly_signed_authn_request
|
self.signature_check = self.sec.correctly_signed_authn_request
|
||||||
|
|
||||||
|
|
||||||
@@ -150,8 +156,10 @@ class AuthnRequest(Request):
|
|||||||
|
|
||||||
|
|
||||||
class AuthzRequest(Request):
|
class AuthzRequest(Request):
|
||||||
def __init__(self, sec_context, receiver_addrs, timeslack=0):
|
def __init__(self, sec_context, receiver_addrs,
|
||||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
attribute_converters=None, timeslack=0):
|
||||||
|
Request.__init__(self, sec_context, receiver_addrs,
|
||||||
|
attribute_converters, timeslack)
|
||||||
self.signature_check = self.sec.correctly_signed_logout_request
|
self.signature_check = self.sec.correctly_signed_logout_request
|
||||||
|
|
||||||
def action(self):
|
def action(self):
|
||||||
|
@@ -23,31 +23,26 @@ import logging
|
|||||||
import shelve
|
import shelve
|
||||||
import sys
|
import sys
|
||||||
import memcache
|
import memcache
|
||||||
|
from saml2.samlp import AuthzDecisionQuery
|
||||||
from saml2.entity import Entity
|
from saml2.entity import Entity
|
||||||
from saml2.samlp import LogoutResponse
|
|
||||||
|
|
||||||
from saml2 import saml, VERSION
|
from saml2 import saml
|
||||||
from saml2 import class_name
|
from saml2 import class_name
|
||||||
from saml2 import soap
|
|
||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT
|
||||||
from saml2 import BINDING_SOAP
|
|
||||||
|
|
||||||
from saml2.request import AuthnRequest
|
from saml2.request import AuthnRequest
|
||||||
from saml2.request import AttributeQuery
|
from saml2.request import AttributeQuery
|
||||||
from saml2.request import LogoutRequest
|
|
||||||
|
|
||||||
from saml2.s_utils import sid
|
from saml2.s_utils import sid
|
||||||
from saml2.s_utils import MissingValue
|
from saml2.s_utils import MissingValue
|
||||||
from saml2.s_utils import success_status_factory
|
|
||||||
from saml2.s_utils import error_status_factory
|
from saml2.s_utils import error_status_factory
|
||||||
|
|
||||||
from saml2.time_util import instant
|
|
||||||
|
|
||||||
from saml2.sigver import signed_instance_factory
|
|
||||||
from saml2.sigver import pre_signature_part
|
from saml2.sigver import pre_signature_part
|
||||||
from saml2.sigver import response_factory
|
|
||||||
|
|
||||||
from saml2.assertion import Assertion, Policy, restriction_from_attribute_spec, filter_attribute_value_assertions
|
from saml2.assertion import Assertion
|
||||||
|
from saml2.assertion import Policy
|
||||||
|
from saml2.assertion import restriction_from_attribute_spec
|
||||||
|
from saml2.assertion import filter_attribute_value_assertions
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -256,15 +251,18 @@ class Server(Entity):
|
|||||||
if self.ident:
|
if self.ident:
|
||||||
self.ident.map.close()
|
self.ident.map.close()
|
||||||
|
|
||||||
def issuer(self, entityid=None):
|
def wants(self, sp_entity_id, index=None):
|
||||||
""" Return an Issuer precursor """
|
""" Returns what attributes the SP requires and which are optional
|
||||||
if entityid:
|
if any such demands are registered in the Metadata.
|
||||||
return saml.Issuer(text=entityid,
|
|
||||||
format=saml.NAMEID_FORMAT_ENTITY)
|
:param sp_entity_id: The entity id of the SP
|
||||||
else:
|
:param index: which of the attribute consumer services its all about
|
||||||
return saml.Issuer(text=self.config.entityid,
|
:return: 2-tuple, list of required and list of optional attributes
|
||||||
format=saml.NAMEID_FORMAT_ENTITY)
|
"""
|
||||||
|
return self.metadata.attribute_requirement(sp_entity_id, index)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT):
|
def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT):
|
||||||
"""Parse a Authentication Request
|
"""Parse a Authentication Request
|
||||||
|
|
||||||
@@ -277,120 +275,35 @@ class Server(Entity):
|
|||||||
sp_entity_id - the entity id of the SP
|
sp_entity_id - the entity id of the SP
|
||||||
request - The verified request
|
request - The verified request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = {}
|
|
||||||
_log_info = logger.info
|
|
||||||
_log_debug = logger.debug
|
|
||||||
|
|
||||||
# The addresses I should receive messages like this on
|
return self._parse_request(enc_request, AuthnRequest,
|
||||||
receiver_addresses = self.config.endpoint("single_sign_on_service",
|
"single_sign_on_service", binding,
|
||||||
binding)
|
"authentication_request")
|
||||||
_log_info("receiver addresses: %s" % receiver_addresses)
|
|
||||||
_log_info("Binding: %s" % binding)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
timeslack = self.config.accepted_time_diff
|
|
||||||
if not timeslack:
|
|
||||||
timeslack = 0
|
|
||||||
except AttributeError:
|
|
||||||
timeslack = 0
|
|
||||||
|
|
||||||
authn_request = AuthnRequest(self.sec,
|
|
||||||
self.config.attribute_converters,
|
|
||||||
receiver_addresses, timeslack=timeslack)
|
|
||||||
|
|
||||||
authn_request = authn_request.loads(enc_request, binding)
|
|
||||||
|
|
||||||
_log_debug("Loaded authn_request")
|
|
||||||
|
|
||||||
if authn_request:
|
|
||||||
authn_request = authn_request.verify()
|
|
||||||
_log_debug("Verified authn_request")
|
|
||||||
|
|
||||||
if not authn_request:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return authn_request
|
|
||||||
|
|
||||||
def wants(self, sp_entity_id, index=None):
|
|
||||||
""" Returns what attributes the SP requires and which are optional
|
|
||||||
if any such demands are registered in the Metadata.
|
|
||||||
|
|
||||||
:param sp_entity_id: The entity id of the SP
|
|
||||||
:param index: which of the attribute consumer services its all about
|
|
||||||
:return: 2-tuple, list of required and list of optional attributes
|
|
||||||
"""
|
|
||||||
return self.metadata.attribute_requirement(sp_entity_id, index)
|
|
||||||
|
|
||||||
def parse_attribute_query(self, xml_string, binding):
|
def parse_attribute_query(self, xml_string, binding):
|
||||||
""" Parse an attribute query
|
""" Parse an attribute query
|
||||||
|
|
||||||
:param xml_string: The Attribute Query as an XML string
|
:param xml_string: The Attribute Query as an XML string
|
||||||
:param binding: Which binding that was used for the request
|
:param binding: Which binding that was used for the request
|
||||||
:return: 3-Tuple containing:
|
:return: A query instance
|
||||||
subject - identifier of the subject
|
|
||||||
attribute - which attributes that the requestor wants back
|
|
||||||
query - the whole query
|
|
||||||
"""
|
|
||||||
receiver_addresses = self.config.endpoint("attribute_service")
|
|
||||||
attribute_query = AttributeQuery( self.sec, receiver_addresses)
|
|
||||||
|
|
||||||
attribute_query = attribute_query.loads(xml_string, binding)
|
|
||||||
attribute_query = attribute_query.verify()
|
|
||||||
|
|
||||||
logger.info("KEYS: %s" % attribute_query.message.keys())
|
|
||||||
# Subject is described in the a saml.Subject instance
|
|
||||||
subject = attribute_query.subject_id()
|
|
||||||
attribute = attribute_query.attribute()
|
|
||||||
|
|
||||||
return subject, attribute, attribute_query.message
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _response(self, in_response_to, consumer_url=None, status=None,
|
|
||||||
issuer=None, sign=False, to_sign=None,
|
|
||||||
**kwargs):
|
|
||||||
""" Create a Response that adhers to the ??? profile.
|
|
||||||
|
|
||||||
:param in_response_to: The session identifier of the request
|
|
||||||
:param consumer_url: The URL which should receive the response
|
|
||||||
:param status: The status of the response
|
|
||||||
:param issuer: The issuer of the response
|
|
||||||
:param sign: Whether the response should be signed or not
|
|
||||||
:param to_sign: What other parts to sign
|
|
||||||
:param kwargs: Extra key word arguments
|
|
||||||
:return: A Response instance
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not status:
|
return self._parse_request(xml_string, AttributeQuery,
|
||||||
status = success_status_factory()
|
"attribute_service", binding)
|
||||||
|
|
||||||
_issuer = self.issuer(issuer)
|
|
||||||
|
|
||||||
response = response_factory(
|
|
||||||
issuer=_issuer,
|
|
||||||
in_response_to = in_response_to,
|
|
||||||
status = status,
|
|
||||||
)
|
|
||||||
|
|
||||||
if consumer_url:
|
|
||||||
response.destination = consumer_url
|
|
||||||
|
|
||||||
for key, val in kwargs.items():
|
|
||||||
setattr(response, key, val)
|
|
||||||
|
|
||||||
if sign:
|
|
||||||
try:
|
|
||||||
to_sign.append((class_name(response), response.id))
|
|
||||||
except AttributeError:
|
|
||||||
to_sign = [(class_name(response), response.id)]
|
|
||||||
|
|
||||||
|
|
||||||
return signed_instance_factory(response, self.sec, to_sign)
|
def parse_authz_decision_query(self, xml_string, binding):
|
||||||
|
""" Parse an attribute query
|
||||||
|
|
||||||
|
:param xml_string: The Authz decision Query as an XML string
|
||||||
|
:return: Query instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._parse_request(xml_string, AuthzDecisionQuery,
|
||||||
|
"authz_service", binding)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
def _authn_response(self, in_response_to, consumer_url,
|
def _authn_response(self, in_response_to, consumer_url,
|
||||||
sp_entity_id, identity=None, name_id=None,
|
sp_entity_id, identity=None, name_id=None,
|
||||||
status=None, authn=None,
|
status=None, authn=None,
|
||||||
@@ -417,7 +330,7 @@ class Server(Entity):
|
|||||||
to_sign = []
|
to_sign = []
|
||||||
args = {}
|
args = {}
|
||||||
if identity:
|
if identity:
|
||||||
_issuer = self.issuer(issuer)
|
_issuer = self._issuer(issuer)
|
||||||
ast = Assertion(identity)
|
ast = Assertion(identity)
|
||||||
if policy is None:
|
if policy is None:
|
||||||
policy = Policy()
|
policy = Policy()
|
||||||
@@ -486,6 +399,7 @@ class Server(Entity):
|
|||||||
sign)
|
sign)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def create_aa_response(self, in_response_to, consumer_url, sp_entity_id,
|
def create_aa_response(self, in_response_to, consumer_url, sp_entity_id,
|
||||||
identity=None, userid="", name_id=None, status=None,
|
identity=None, userid="", name_id=None, status=None,
|
||||||
@@ -517,7 +431,7 @@ class Server(Entity):
|
|||||||
to_sign = []
|
to_sign = []
|
||||||
args = {}
|
args = {}
|
||||||
if identity:
|
if identity:
|
||||||
_issuer = self.issuer(issuer)
|
_issuer = self._issuer(issuer)
|
||||||
ast = Assertion(identity)
|
ast = Assertion(identity)
|
||||||
policy = self.config.getattr("policy", "aa")
|
policy = self.config.getattr("policy", "aa")
|
||||||
if policy:
|
if policy:
|
||||||
@@ -607,119 +521,3 @@ class Server(Entity):
|
|||||||
except MissingValue, exc:
|
except MissingValue, exc:
|
||||||
return self.create_error_response(in_response_to, destination,
|
return self.create_error_response(in_response_to, destination,
|
||||||
sp_entity_id, exc, name_id)
|
sp_entity_id, exc, name_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse_logout_request(self, text, binding=BINDING_SOAP):
|
|
||||||
"""Parse a Logout Request
|
|
||||||
|
|
||||||
:param text: The request in its transport format, if the binding is
|
|
||||||
HTTP-Redirect or HTTP-Post the text *must* be the value of the
|
|
||||||
SAMLRequest attribute.
|
|
||||||
:return: A validated LogoutRequest instance or None if validation
|
|
||||||
failed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
slo = self.config.endpoint("single_logout_service", binding, "idp")
|
|
||||||
except IndexError:
|
|
||||||
logger.info("enpoints: %s" % self.config.getattr("endpoints", "idp"))
|
|
||||||
logger.info("binding wanted: %s" % (binding,))
|
|
||||||
raise
|
|
||||||
|
|
||||||
if not slo:
|
|
||||||
raise Exception("No single_logout_server for that binding")
|
|
||||||
|
|
||||||
logger.info("Endpoint: %s" % slo)
|
|
||||||
req = LogoutRequest(self.sec, slo)
|
|
||||||
if binding == BINDING_SOAP:
|
|
||||||
lreq = soap.parse_soap_enveloped_saml_logout_request(text)
|
|
||||||
try:
|
|
||||||
req = req.loads(lreq, binding)
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
req = req.loads(text, binding)
|
|
||||||
except Exception, exc:
|
|
||||||
logger.error("%s" % (exc,))
|
|
||||||
return None
|
|
||||||
|
|
||||||
req = req.verify()
|
|
||||||
|
|
||||||
if not req: # Not a valid request
|
|
||||||
# return a error message with status code element set to
|
|
||||||
# urn:oasis:names:tc:SAML:2.0:status:Requester
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return req
|
|
||||||
|
|
||||||
|
|
||||||
def _status_response(self, response_class, issuer, status, sign=False,
|
|
||||||
**kwargs):
|
|
||||||
""" Create a StatusResponse.
|
|
||||||
|
|
||||||
:param response_class: Which subclass of StatusResponse that should be
|
|
||||||
used
|
|
||||||
:param issuer: The issuer of the response message
|
|
||||||
:param status: The return status of the response operation
|
|
||||||
:param sign: Whether the response should be signed or not
|
|
||||||
:param kwargs: Extra arguments to the response class
|
|
||||||
:return: Class instance or string representation of the instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
mid = sid()
|
|
||||||
|
|
||||||
if not status:
|
|
||||||
status = success_status_factory()
|
|
||||||
|
|
||||||
response = response_class(issuer=issuer, id=mid, version=VERSION,
|
|
||||||
issue_instant=instant(),
|
|
||||||
status=status, **kwargs)
|
|
||||||
|
|
||||||
if sign:
|
|
||||||
response.signature = pre_signature_part(mid)
|
|
||||||
to_sign = [(class_name(response), mid)]
|
|
||||||
response = signed_instance_factory(response, self.sec, to_sign)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def create_logout_response(self, request, bindings, status=None,
|
|
||||||
sign=False, issuer=None):
|
|
||||||
""" Create a LogoutResponse.
|
|
||||||
|
|
||||||
:param request: The request this is a response to
|
|
||||||
:param bindings: Which bindings that can be used for the response
|
|
||||||
:param status: The return status of the response operation
|
|
||||||
:param issuer: The issuer of the message
|
|
||||||
:return: HTTP args
|
|
||||||
"""
|
|
||||||
|
|
||||||
rinfo = self.response_args(request, bindings, descr_type="spsso")
|
|
||||||
response = self._status_response(LogoutResponse, issuer, status,
|
|
||||||
sign=False, **rinfo)
|
|
||||||
|
|
||||||
logger.info("Response: %s" % (response,))
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def parse_authz_decision_query(self, xml_string, binding):
|
|
||||||
""" Parse an attribute query
|
|
||||||
|
|
||||||
:param xml_string: The Authz decision Query as an XML string
|
|
||||||
:return: 3-Tuple containing:
|
|
||||||
subject - identifier of the subject
|
|
||||||
attribute - which attributes that the requestor wants back
|
|
||||||
query - the whole query
|
|
||||||
"""
|
|
||||||
receiver_addresses = self.config.endpoint("attribute_service", "idp")
|
|
||||||
attribute_query = AttributeQuery( self.sec, receiver_addresses)
|
|
||||||
|
|
||||||
attribute_query = attribute_query.loads(xml_string, binding)
|
|
||||||
attribute_query = attribute_query.verify()
|
|
||||||
|
|
||||||
# Subject name is a BaseID,NameID or EncryptedID instance
|
|
||||||
subject = attribute_query.subject_id()
|
|
||||||
attribute = attribute_query.attribute()
|
|
||||||
|
|
||||||
return subject, attribute, attribute_query.message
|
|
||||||
|
@@ -72,7 +72,7 @@ class TestServer1():
|
|||||||
self.server.close_shelve_db()
|
self.server.close_shelve_db()
|
||||||
|
|
||||||
def test_issuer(self):
|
def test_issuer(self):
|
||||||
issuer = self.server.issuer()
|
issuer = self.server._issuer()
|
||||||
assert isinstance(issuer, saml.Issuer)
|
assert isinstance(issuer, saml.Issuer)
|
||||||
assert _eq(issuer.keyswv(), ["text","format"])
|
assert _eq(issuer.keyswv(), ["text","format"])
|
||||||
assert issuer.format == saml.NAMEID_FORMAT_ENTITY
|
assert issuer.format == saml.NAMEID_FORMAT_ENTITY
|
||||||
@@ -88,7 +88,7 @@ class TestServer1():
|
|||||||
("","","surName"): ("Jeter",""),
|
("","","surName"): ("Jeter",""),
|
||||||
("","","givenName") :("Derek",""),
|
("","","givenName") :("Derek",""),
|
||||||
}),
|
}),
|
||||||
issuer=self.server.issuer(),
|
issuer=self.server._issuer(),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert _eq(assertion.keyswv(),['attribute_statement', 'issuer', 'id',
|
assert _eq(assertion.keyswv(),['attribute_statement', 'issuer', 'id',
|
||||||
@@ -128,9 +128,9 @@ class TestServer1():
|
|||||||
("","","surName"): ("Jeter",""),
|
("","","surName"): ("Jeter",""),
|
||||||
("","","givenName") :("Derek",""),
|
("","","givenName") :("Derek",""),
|
||||||
}),
|
}),
|
||||||
issuer=self.server.issuer(),
|
issuer=self.server._issuer(),
|
||||||
),
|
),
|
||||||
issuer=self.server.issuer(),
|
issuer=self.server._issuer(),
|
||||||
)
|
)
|
||||||
|
|
||||||
print response.keyswv()
|
print response.keyswv()
|
||||||
|
Reference in New Issue
Block a user