Fixed manage_name_id request-response

Added assert_id request-response
This commit is contained in:
Roland Hedberg 2013-01-14 12:09:17 +01:00
parent 05d514f10e
commit 85b3fc307c
9 changed files with 262 additions and 19 deletions

View File

@ -24,7 +24,6 @@ from saml2.mdstore import destinations
from saml2.saml import AssertionIDRef from saml2.saml import AssertionIDRef
from saml2.saml import NAMEID_FORMAT_TRANSIENT from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2.samlp import AuthnQuery from saml2.samlp import AuthnQuery
from saml2.samlp import Response
from saml2.samlp import AssertionIDRequest from saml2.samlp import AssertionIDRequest
from saml2.samlp import NameIDMappingRequest from saml2.samlp import NameIDMappingRequest
from saml2.samlp import AttributeQuery from saml2.samlp import AttributeQuery
@ -48,6 +47,9 @@ from saml2 import saml
from saml2.population import Population from saml2.population import Population
from saml2.response import AttributeResponse from saml2.response import AttributeResponse
from saml2.response import AuthzResponse
from saml2.response import AssertionIDResponse
from saml2.response import AuthnQueryResponse
from saml2.response import NameIDMappingResponse from saml2.response import NameIDMappingResponse
from saml2.response import AuthnResponse from saml2.response import AuthnResponse
@ -491,17 +493,21 @@ class Base(Entity):
""" Verify that the response is OK """ Verify that the response is OK
""" """
return self._parse_response(response, Response, "", binding) return self._parse_response(response, AuthzResponse, "", binding)
def parse_authn_query_response(self, response, binding=BINDING_SOAP): def parse_authn_query_response(self, response, binding=BINDING_SOAP):
""" Verify that the response is OK """ Verify that the response is OK
""" """
return self._parse_response(response, Response, "", binding) return self._parse_response(response, AuthnQueryResponse, "", binding)
def parse_assertion_id_request_response(self, response, binding): def parse_assertion_id_request_response(self, response, binding):
""" Verify that the response is OK """ Verify that the response is OK
""" """
return self._parse_response(response, Response, "", binding) kwargs = {"entity_id": self.config.entityid,
"attribute_converters": self.config.attribute_converters}
return self._parse_response(response, AssertionIDResponse, "", binding,
**kwargs)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------

View File

@ -146,6 +146,7 @@ class Entity(HTTPBase):
entity_id = request.issuer.text.strip() entity_id = request.issuer.text.strip()
sfunc = getattr(self.metadata, service) sfunc = getattr(self.metadata, service)
if bindings is None: if bindings is None:
bindings = self.config.preferred_binding[service] bindings = self.config.preferred_binding[service]
@ -175,8 +176,9 @@ class Entity(HTTPBase):
return {"id":id, "version":VERSION, return {"id":id, "version":VERSION,
"issue_instant":instant(), "issuer":self._issuer()} "issue_instant":instant(), "issuer":self._issuer()}
def response_args(self, message, bindings, descr_type=""): def response_args(self, message, bindings=None, descr_type=""):
info = {"in_response_to": message.id} info = {"in_response_to": message.id}
if isinstance(message, AuthnRequest): if isinstance(message, AuthnRequest):
rsrv = "assertion_consumer_service" rsrv = "assertion_consumer_service"
descr_type = "sp_sso" descr_type = "sp_sso"
@ -189,7 +191,9 @@ class Entity(HTTPBase):
descr_type = "sp_sso" descr_type = "sp_sso"
elif isinstance(message, ManageNameIDRequest): elif isinstance(message, ManageNameIDRequest):
rsrv = "manage_name_id_service" rsrv = "manage_name_id_service"
# The once below are solely SOAP # The once below are solely SOAP so no return destination needed
elif isinstance(message, AssertionIDRequest):
rsrv = ""
elif isinstance(message, ArtifactResolve): elif isinstance(message, ArtifactResolve):
rsrv = "" rsrv = ""
elif isinstance(message, AssertionIDRequest): elif isinstance(message, AssertionIDRequest):
@ -209,6 +213,7 @@ class Entity(HTTPBase):
binding, destination = self.pick_binding(rsrv, bindings, binding, destination = self.pick_binding(rsrv, bindings,
descr_type=descr_type, descr_type=descr_type,
request=message) request=message)
#info["binding"] = binding
info["destination"] = destination info["destination"] = destination
return info return info
@ -488,7 +493,8 @@ class Entity(HTTPBase):
def create_manage_name_id_request(self, destination, id=0, consent=None, def create_manage_name_id_request(self, destination, id=0, consent=None,
extensions=None, sign=False, extensions=None, sign=False,
name_id=None, new_id=None, name_id=None, new_id=None,
encrypted_id=None, new_encrypted_id=None): encrypted_id=None, new_encrypted_id=None,
terminate=None):
""" """
:param destination: :param destination:
@ -500,6 +506,7 @@ class Entity(HTTPBase):
:param new_id: :param new_id:
:param encrypted_id: :param encrypted_id:
:param new_encrypted_id: :param new_encrypted_id:
:param terminate:
:return: :return:
""" """
kwargs = self.message_args(id) kwargs = self.message_args(id)
@ -515,8 +522,10 @@ class Entity(HTTPBase):
kwargs["new_id"] = new_id kwargs["new_id"] = new_id
elif new_encrypted_id: elif new_encrypted_id:
kwargs["new_encrypted_id"] = new_encrypted_id kwargs["new_encrypted_id"] = new_encrypted_id
elif terminate:
kwargs["terminate"] = terminate
else: else:
kwargs["terminate"] = "" raise AttributeError("One of NewID, NewEncryptedNameID or Terminate has to be provided")
return self._message(ManageNameIDRequest, destination, consent=consent, return self._message(ManageNameIDRequest, destination, consent=consent,
extensions=extensions, sign=sign, **kwargs) extensions=extensions, sign=sign, **kwargs)
@ -534,13 +543,13 @@ class Entity(HTTPBase):
return self._parse_request(xmlstr, request.ManageNameIDRequest, return self._parse_request(xmlstr, request.ManageNameIDRequest,
"manage_name_id_service", binding) "manage_name_id_service", binding)
def create_manage_name_id_response(self, request, bindings, status=None, def create_manage_name_id_response(self, request, bindings=None,
sign=False, issuer=None): status=None, sign=False, issuer=None):
rinfo = self.response_args(request, bindings) rinfo = self.response_args(request, bindings)
response = self._status_response(samlp.ManageNameIDResponse, issuer, response = self._status_response(samlp.ManageNameIDResponse, issuer,
status, sign=False, **rinfo) status, sign, **rinfo)
logger.info("Response: %s" % (response,)) logger.info("Response: %s" % (response,))
@ -582,7 +591,7 @@ class Entity(HTTPBase):
response = response_cls(self.sec, **kwargs) response = response_cls(self.sec, **kwargs)
except Exception, exc: except Exception, exc:
logger.info("%s" % exc) logger.info("%s" % exc)
return None raise
xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype) xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype)
@ -698,4 +707,8 @@ class Entity(HTTPBase):
# should just be one # should just be one
elems = extension_elements_to_elements(resp.response.extension_elements, elems = extension_elements_to_elements(resp.response.extension_elements,
[samlp, saml]) [samlp, saml])
return elems[0] return elems[0]
def parse_manage_name_id_response(self, xmlstr, binding=BINDING_SOAP):
return self._parse_response(xmlstr, response.ManageNameIDResponse,
"manage_name_id_service", binding)

View File

@ -155,7 +155,19 @@ class AuthnRequest(Request):
def attributes(self): def attributes(self):
return to_local(self.attribute_converters, self.message) return to_local(self.attribute_converters, self.message)
class AssertionIDRequest(Request):
msgtype = "assertion_id_request"
def __init__(self, sec_context, receiver_addrs, attribute_converters,
timeslack=0):
Request.__init__(self, sec_context, receiver_addrs,
attribute_converters, timeslack)
self.signature_check = self.sec.correctly_signed_assertion_id_request
def attributes(self):
return to_local(self.attribute_converters, self.message)
class AuthzDecisionQuery(Request): class AuthzDecisionQuery(Request):
msgtype = "authz_decision_query" msgtype = "authz_decision_query"

View File

@ -256,6 +256,16 @@ class NameIDMappingResponse(StatusResponse):
request_id, asynchop) request_id, asynchop)
self.signature_check = self.sec.correctly_signed_name_id_mapping_response self.signature_check = self.sec.correctly_signed_name_id_mapping_response
class ManageNameIDResponse(StatusResponse):
msgtype = "manage_name_id_response"
def __init__(self, sec_context, return_addr=None, timeslack=0,
request_id=0, asynchop=True):
StatusResponse.__init__(self, sec_context, return_addr, timeslack,
request_id, asynchop)
self.signature_check = self.sec.correctly_signed_manage_name_id_response
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
class AuthnResponse(StatusResponse): class AuthnResponse(StatusResponse):
@ -632,6 +642,35 @@ class AuthnResponse(StatusResponse):
def __str__(self): def __str__(self):
return "%s" % self.xmlstr return "%s" % self.xmlstr
class AssertionIDResponse(AuthnResponse):
msgtype = "assertion_id_response"
def __init__(self, sec_context, attribute_converters, entity_id,
return_addr=None, timeslack=0, asynchop=False, test=False):
AuthnResponse.__init__(self, sec_context, attribute_converters,
entity_id, return_addr, timeslack=timeslack,
asynchop=asynchop, test=test)
self.entity_id = entity_id
self.attribute_converters = attribute_converters
self.assertion = None
self.context = "AssertionIdResponse"
class AuthnQueryResponse(AuthnResponse):
msgtype = "authn_query_response"
def __init__(self, sec_context, attribute_converters, entity_id,
return_addr=None, timeslack=0, asynchop=False, test=False):
AuthnResponse.__init__(self, sec_context, attribute_converters,
entity_id, return_addr, timeslack=timeslack,
asynchop=asynchop, test=test)
self.entity_id = entity_id
self.attribute_converters = attribute_converters
self.assertion = None
self.context = "AuthnQueryResponse"
class AttributeResponse(AuthnResponse): class AttributeResponse(AuthnResponse):
msgtype = "attribute_response" msgtype = "attribute_response"

View File

@ -23,9 +23,8 @@ import logging
import shelve import shelve
import sys import sys
import memcache import memcache
from saml2.soap import parse_soap_enveloped_saml_name_id_mapping_request from saml2.samlp import AuthzDecisionQuery
from saml2.samlp import AuthzDecisionQuery, NameIDMappingResponse from saml2.samlp import NameIDMappingResponse
from saml2.samlp import AssertionIDRequest
from saml2.samlp import AuthnQuery from saml2.samlp import AuthnQuery
from saml2.entity import Entity from saml2.entity import Entity
@ -34,6 +33,7 @@ from saml2 import class_name
from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_REDIRECT
from saml2.request import AuthnRequest from saml2.request import AuthnRequest
from saml2.request import AssertionIDRequest
from saml2.request import AttributeQuery from saml2.request import AttributeQuery
from saml2.request import NameIDMappingRequest from saml2.request import NameIDMappingRequest
@ -619,3 +619,4 @@ class Server(Entity):
else: else:
logger.info("Message: %s" % _resp) logger.info("Message: %s" % _resp)
return _resp return _resp

View File

@ -822,6 +822,18 @@ class SecurityContext(object):
"manage_name_id_request", "manage_name_id_request",
must, origdoc) must, origdoc)
def correctly_signed_manage_name_id_response(self, decoded_xml, must=False,
origdoc=None):
return self.correctly_signed_message(decoded_xml,
"manage_name_id_response", must,
origdoc)
def correctly_signed_assertion_id_request(self, decoded_xml, must=False,
origdoc=None):
return self.correctly_signed_message(decoded_xml,
"assertion_id_request", must,
origdoc)
def correctly_signed_response(self, decoded_xml, must=False, origdoc=None): def correctly_signed_response(self, decoded_xml, must=False, origdoc=None):
""" Check if a instance is correctly signed, if we have metadata for """ Check if a instance is correctly signed, if we have metadata for
the IdP that sent the info use that, if not use the key that are in the IdP that sent the info use that, if not use the key that are in

View File

@ -79,6 +79,19 @@ def parse_soap_enveloped_saml_manage_name_id_request(text):
expected_tag = '{%s}ManageNameIDRequest' % SAMLP_NAMESPACE expected_tag = '{%s}ManageNameIDRequest' % SAMLP_NAMESPACE
return parse_soap_enveloped_saml_thingy(text, [expected_tag]) return parse_soap_enveloped_saml_thingy(text, [expected_tag])
def parse_soap_enveloped_saml_manage_name_id_response(text):
expected_tag = '{%s}ManageNameIDResponse' % SAMLP_NAMESPACE
return parse_soap_enveloped_saml_thingy(text, [expected_tag])
def parse_soap_enveloped_saml_assertion_id_request(text):
expected_tag = '{%s}AssertionIDRequest' % SAMLP_NAMESPACE
return parse_soap_enveloped_saml_thingy(text, [expected_tag])
def parse_soap_enveloped_saml_assertion_id_response(text):
tags = ['{%s}Response' % SAMLP_NAMESPACE,
'{%s}AssertionIDResponse' % SAMLP_NAMESPACE]
return parse_soap_enveloped_saml_thingy(text, tags)
#def parse_soap_enveloped_saml_logout_response(text): #def parse_soap_enveloped_saml_logout_response(text):
# expected_tag = '{%s}LogoutResponse' % SAMLP_NAMESPACE # expected_tag = '{%s}LogoutResponse' % SAMLP_NAMESPACE
# return parse_soap_enveloped_saml_thingy(text, [expected_tag]) # return parse_soap_enveloped_saml_thingy(text, [expected_tag])
@ -110,8 +123,8 @@ def parse_soap_enveloped_saml_thingy(text, expected_tags):
if saml_part.tag in expected_tags: if saml_part.tag in expected_tags:
return ElementTree.tostring(saml_part, encoding="UTF-8") return ElementTree.tostring(saml_part, encoding="UTF-8")
else: else:
raise WrongMessageType("Was '%s' expected '%s'" % (saml_part.tag, raise WrongMessageType("Was '%s' expected one of %s" % (saml_part.tag,
expected_tags)) expected_tags))
import re import re

View File

@ -1,3 +1,4 @@
from saml2 import BINDING_SOAP
from saml2.samlp import NewID from saml2.samlp import NewID
from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT
from saml2.client import Saml2Client from saml2.client import Saml2Client
@ -29,3 +30,45 @@ def test_basic():
print _req.message print _req.message
assert mid.id == _req.message.id assert mid.id == _req.message.id
def test_flow():
sp = Saml2Client(config_file="servera_conf")
idp = Server(config_file="idp_all_conf")
binding, destination = sp.pick_binding("manage_name_id_service",
entity_id=idp.config.entityid)
nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar")
newid = NewID(text="Barfoo")
mid = sp.create_manage_name_id_request(destination, name_id=nameid,
new_id=newid)
print mid
rargs = sp.apply_binding(binding, "%s" % mid, destination, "")
# --------- @IDP --------------
_req = idp.parse_manage_name_id_request(rargs["data"], binding)
print _req.message
mnir = idp.create_manage_name_id_response(_req.message, None)
if binding != BINDING_SOAP:
binding, destination = idp.pick_binding("manage_name_id_service",
entity_id=sp.config.entityid)
else:
destination = ""
respargs = idp.apply_binding(binding, "%s" % mnir, destination, "")
print respargs
# ---------- @SP ---------------
_response = sp.parse_manage_name_id_response(respargs["data"], binding)
print _response.response
assert _response.response.id == mnir.id

View File

@ -0,0 +1,104 @@
from urlparse import parse_qs
from urlparse import urlparse
from saml2.samlp import AuthnRequest
from saml2.samlp import NameIDPolicy
from saml2.saml import AUTHN_PASSWORD
from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_SOAP
from saml2.client import Saml2Client
from saml2.server import Server
__author__ = 'rolandh'
TAG1 = "name=\"SAMLRequest\" value="
def get_msg(hinfo, binding):
if binding == BINDING_SOAP:
xmlstr = hinfo["data"]
elif binding == BINDING_HTTP_POST:
_inp = hinfo["data"][3]
i = _inp.find(TAG1)
i += len(TAG1) + 1
j = _inp.find('"', i)
xmlstr = _inp[i:j]
else: # BINDING_HTTP_REDIRECT
parts = urlparse(hinfo["headers"][0][1])
xmlstr = parse_qs(parts.query)["SAMLRequest"][0]
return xmlstr
def test_basic_flow():
sp = Saml2Client(config_file="servera_conf")
idp = Server(config_file="idp_all_conf")
# -------- @IDP -------------
relay_state = "FOO"
# -- dummy request ---
orig_req = AuthnRequest(issuer=sp._issuer(),
name_id_policy=NameIDPolicy(allow_create="true",
format=NAMEID_FORMAT_TRANSIENT))
# == Create an AuthnRequest response
name_id = idp.ident.transient_nameid(sp.config.entityid, "id12")
binding, destination = idp.pick_binding("assertion_consumer_service",
entity_id=sp.config.entityid)
resp = idp.create_authn_response({"eduPersonEntitlement": "Short stop",
"surName": "Jeter",
"givenName": "Derek",
"mail": "derek.jeter@nyy.mlb.com",
"title": "The man"},
"id-123456789",
destination,
sp.config.entityid,
name_id=name_id,
authn=(AUTHN_PASSWORD,
"http://www.example.com/login"))
hinfo = idp.apply_binding(binding, "%s" % resp, destination, relay_state)
# --------- @SP -------------
xmlstr = get_msg(hinfo, binding)
aresp = sp.parse_authn_request_response(xmlstr, binding,
{resp.in_response_to :"/"})
# == Look for assertion X
asid = aresp.assertion.id
binding, destination = sp.pick_binding("assertion_id_request_service",
entity_id=idp.config.entityid)
_req = sp.create_assertion_id_request([asid], destination)
hinfo = sp.apply_binding(binding, "%s" % _req, destination,
"realy_stat")
# ---------- @IDP ------------
xmlstr = get_msg(hinfo, binding)
rr = idp.parse_assertion_id_request(xmlstr, binding)
print rr
# == construct response
aids = [x.text for x in rr.message.assertion_id_ref]
resp_args = idp.response_args(rr.message)
resp = idp.create_assertion_id_request_response(aids, **resp_args)
hinfo = idp.apply_binding(binding, "%s" % resp, None, "", "SAMLResponse")
# ----------- @SP -------------
xmlstr = get_msg(hinfo, binding)
final = sp.parse_assertion_id_request_response(xmlstr, binding)
print final