2
# -*- coding: utf-8 -*-
4
# Copyright (C) 2010-2011 UmeƄ University
6
# Licensed under the Apache License, Version 2.0 (the "License");
7
# you may not use this file except in compliance with the License.
8
# You may obtain a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS,
14
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
# See the License for the specific language governing permissions and
16
# limitations under the License.
20
from saml2.samlp import STATUS_VERSION_MISMATCH
21
from saml2.samlp import STATUS_AUTHN_FAILED
22
from saml2.samlp import STATUS_INVALID_ATTR_NAME_OR_VALUE
23
from saml2.samlp import STATUS_INVALID_NAMEID_POLICY
24
from saml2.samlp import STATUS_NO_AUTHN_CONTEXT
25
from saml2.samlp import STATUS_NO_AVAILABLE_IDP
26
from saml2.samlp import STATUS_NO_PASSIVE
27
from saml2.samlp import STATUS_NO_SUPPORTED_IDP
28
from saml2.samlp import STATUS_PARTIAL_LOGOUT
29
from saml2.samlp import STATUS_PROXY_COUNT_EXCEEDED
30
from saml2.samlp import STATUS_REQUEST_DENIED
31
from saml2.samlp import STATUS_REQUEST_UNSUPPORTED
32
from saml2.samlp import STATUS_REQUEST_VERSION_DEPRECATED
33
from saml2.samlp import STATUS_REQUEST_VERSION_TOO_HIGH
34
from saml2.samlp import STATUS_REQUEST_VERSION_TOO_LOW
35
from saml2.samlp import STATUS_RESOURCE_NOT_RECOGNIZED
36
from saml2.samlp import STATUS_TOO_MANY_RESPONSES
37
from saml2.samlp import STATUS_UNKNOWN_ATTR_PROFILE
38
from saml2.samlp import STATUS_UNKNOWN_PRINCIPAL
39
from saml2.samlp import STATUS_UNSUPPORTED_BINDING
44
from saml2 import samlp
45
from saml2 import saml
46
from saml2 import extension_element_to_element
47
from saml2 import extension_elements_to_elements
48
from saml2 import SAMLError
49
from saml2 import time_util
51
from saml2.s_utils import RequestVersionTooLow
52
from saml2.s_utils import RequestVersionTooHigh
53
from saml2.saml import attribute_from_string, XSI_TYPE
54
from saml2.saml import SCM_BEARER
55
from saml2.saml import SCM_HOLDER_OF_KEY
56
from saml2.saml import SCM_SENDER_VOUCHES
57
from saml2.saml import encrypted_attribute_from_string
58
from saml2.sigver import security_context
59
from saml2.sigver import SignatureError
60
from saml2.sigver import signed
61
from saml2.attribute_converter import to_local
62
from saml2.time_util import str_to_time, later_than
64
from saml2.validate import validate_on_or_after
65
from saml2.validate import validate_before
66
from saml2.validate import valid_instance
67
from saml2.validate import valid_address
68
from saml2.validate import NotValid
70
logger = logging.getLogger(__name__)
72
# ---------------------------------------------------------------------------
75
class IncorrectlySigned(SAMLError):
79
class DecryptionFailed(SAMLError):
83
class VerificationError(SAMLError):
87
class StatusError(SAMLError):
91
class UnsolicitedResponse(SAMLError):
95
class StatusVersionMismatch(StatusError):
99
class StatusAuthnFailed(StatusError):
103
class StatusInvalidAttrNameOrValue(StatusError):
107
class StatusInvalidNameidPolicy(StatusError):
111
class StatusNoAuthnContext(StatusError):
115
class StatusNoAvailableIdp(StatusError):
119
class StatusNoPassive(StatusError):
123
class StatusNoSupportedIdp(StatusError):
127
class StatusPartialLogout(StatusError):
131
class StatusProxyCountExceeded(StatusError):
135
class StatusRequestDenied(StatusError):
139
class StatusRequestUnsupported(StatusError):
143
class StatusRequestVersionDeprecated(StatusError):
147
class StatusRequestVersionTooHigh(StatusError):
151
class StatusRequestVersionTooLow(StatusError):
155
class StatusResourceNotRecognized(StatusError):
159
class StatusTooManyResponses(StatusError):
163
class StatusUnknownAttrProfile(StatusError):
167
class StatusUnknownPrincipal(StatusError):
171
class StatusUnsupportedBinding(StatusError):
175
STATUSCODE2EXCEPTION = {
176
STATUS_VERSION_MISMATCH: StatusVersionMismatch,
177
STATUS_AUTHN_FAILED: StatusAuthnFailed,
178
STATUS_INVALID_ATTR_NAME_OR_VALUE: StatusInvalidAttrNameOrValue,
179
STATUS_INVALID_NAMEID_POLICY: StatusInvalidNameidPolicy,
180
STATUS_NO_AUTHN_CONTEXT: StatusNoAuthnContext,
181
STATUS_NO_AVAILABLE_IDP: StatusNoAvailableIdp,
182
STATUS_NO_PASSIVE: StatusNoPassive,
183
STATUS_NO_SUPPORTED_IDP: StatusNoSupportedIdp,
184
STATUS_PARTIAL_LOGOUT: StatusPartialLogout,
185
STATUS_PROXY_COUNT_EXCEEDED: StatusProxyCountExceeded,
186
STATUS_REQUEST_DENIED: StatusRequestDenied,
187
STATUS_REQUEST_UNSUPPORTED: StatusRequestUnsupported,
188
STATUS_REQUEST_VERSION_DEPRECATED: StatusRequestVersionDeprecated,
189
STATUS_REQUEST_VERSION_TOO_HIGH: StatusRequestVersionTooHigh,
190
STATUS_REQUEST_VERSION_TOO_LOW: StatusRequestVersionTooLow,
191
STATUS_RESOURCE_NOT_RECOGNIZED: StatusResourceNotRecognized,
192
STATUS_TOO_MANY_RESPONSES: StatusTooManyResponses,
193
STATUS_UNKNOWN_ATTR_PROFILE: StatusUnknownAttrProfile,
194
STATUS_UNKNOWN_PRINCIPAL: StatusUnknownPrincipal,
195
STATUS_UNSUPPORTED_BINDING: StatusUnsupportedBinding,
197
# ---------------------------------------------------------------------------
204
def for_me(conditions, myself):
205
""" Am I among the intended audiences """
207
if not conditions.audience_restriction: # No audience restriction
210
for restriction in conditions.audience_restriction:
211
if not restriction.audience:
213
for audience in restriction.audience:
214
if audience.text.strip() == myself:
217
#print "Not for me: %s != %s" % (audience.text.strip(), myself)
223
def authn_response(conf, return_addrs, outstanding_queries=None, timeslack=0,
224
asynchop=True, allow_unsolicited=False, want_assertions_signed=False):
225
sec = security_context(conf)
228
timeslack = int(conf.accepted_time_diff)
232
return AuthnResponse(sec, conf.attribute_converters, conf.entityid,
233
return_addrs, outstanding_queries, timeslack,
234
asynchop=asynchop, allow_unsolicited=allow_unsolicited,
235
want_assertions_signed=want_assertions_signed)
238
# comes in over SOAP so synchronous
239
def attribute_response(conf, return_addrs, timeslack=0, asynchop=False,
241
sec = security_context(conf)
244
timeslack = int(conf.accepted_time_diff)
248
return AttributeResponse(sec, conf.attribute_converters, conf.entityid,
249
return_addrs, timeslack, asynchop=asynchop,
253
class StatusResponse(object):
254
msgtype = "status_response"
256
def __init__(self, sec_context, return_addrs=None, timeslack=0,
257
request_id=0, asynchop=True):
258
self.sec = sec_context
259
self.return_addrs = return_addrs
261
self.timeslack = timeslack
262
self.request_id = request_id
267
self.not_on_or_after = 0
268
self.in_response_to = None
269
self.signature_check = self.sec.correctly_signed_response
270
self.require_signature = False
271
self.require_response_signature = False
272
self.not_signed = False
273
self.asynchop = asynchop
279
self.not_on_or_after = 0
281
def _postamble(self):
282
if not self.response:
283
logger.error("Response was not correctly signed")
285
logger.info(self.xmlstr)
286
raise IncorrectlySigned()
288
logger.debug("response: %s" % (self.response,))
291
valid_instance(self.response)
292
except NotValid, exc:
293
logger.error("Not valid response: %s" % exc.args[0])
297
self.in_response_to = self.response.in_response_to
300
def load_instance(self, instance):
302
# This will check signature on Assertion which is the default
304
self.response = self.sec.check_signature(instance)
305
except SignatureError:
306
# The response as a whole might be signed or not
307
self.response = self.sec.check_signature(
308
instance, samlp.NAMESPACE + ":Response")
310
self.not_signed = True
311
self.response = instance
313
return self._postamble()
315
def _loads(self, xmldata, decode=True, origxml=None):
318
self.xmlstr = xmldata[:]
319
logger.debug("xmlstr: %s" % (self.xmlstr,))
322
self.response = self.signature_check(xmldata, origdoc=origxml, must=self.require_signature,
323
require_response_signature=self.require_response_signature)
327
except SignatureError:
329
except Exception, excp:
330
#logger.exception("EXCEPTION: %s", excp)
333
#print "<", self.response
335
return self._postamble()
338
if self.response.status:
339
status = self.response.status
340
logger.info("status: %s" % (status,))
341
if status.status_code.value != samlp.STATUS_SUCCESS:
342
logger.info("Not successful operation: %s" % status)
343
if status.status_code.status_code:
344
excep = STATUSCODE2EXCEPTION[
345
status.status_code.status_code.value]
348
if status.status_message:
349
msg = status.status_message.text
352
msg = status.status_code.status_code.value
354
msg = "Unknown error"
356
"%s from %s" % (msg, status.status_code.value,))
359
def issue_instant_ok(self):
360
""" Check that the response was issued at a reasonable time """
361
upper = time_util.shift_time(time_util.time_in_a_while(days=1),
362
self.timeslack).timetuple()
363
lower = time_util.shift_time(time_util.time_a_while_ago(days=1),
364
-self.timeslack).timetuple()
365
# print "issue_instant: %s" % self.response.issue_instant
366
# print "%s < x < %s" % (lower, upper)
367
issued_at = str_to_time(self.response.issue_instant)
368
return lower < issued_at < upper
371
if self.request_id and self.in_response_to and \
372
self.in_response_to != self.request_id:
373
logger.error("Not the id I expected: %s != %s" % (
374
self.in_response_to, self.request_id))
378
assert self.response.version == "2.0"
379
except AssertionError:
380
_ver = float(self.response.version)
382
raise RequestVersionTooLow()
384
raise RequestVersionTooHigh()
387
if self.response.destination and \
388
self.response.destination not in self.return_addrs:
389
logger.error("%s not in %s" % (self.response.destination,
393
assert self.issue_instant_ok()
394
assert self.status_ok()
397
def loads(self, xmldata, decode=True, origxml=None):
398
return self._loads(xmldata, decode, origxml)
402
return self._verify()
403
except AssertionError:
404
logger.exception("verify")
407
def update(self, mold):
408
self.xmlstr = mold.xmlstr
409
self.in_response_to = mold.in_response_to
410
self.response = mold.response
413
return self.response.issuer.text.strip()
416
class LogoutResponse(StatusResponse):
417
msgtype = "logout_response"
419
def __init__(self, sec_context, return_addrs=None, timeslack=0,
421
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
423
self.signature_check = self.sec.correctly_signed_logout_response
426
class NameIDMappingResponse(StatusResponse):
427
msgtype = "name_id_mapping_response"
429
def __init__(self, sec_context, return_addrs=None, timeslack=0,
430
request_id=0, asynchop=True):
431
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
432
request_id, asynchop)
433
self.signature_check = self.sec.correctly_signed_name_id_mapping_response
436
class ManageNameIDResponse(StatusResponse):
437
msgtype = "manage_name_id_response"
439
def __init__(self, sec_context, return_addrs=None, timeslack=0,
440
request_id=0, asynchop=True):
441
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
442
request_id, asynchop)
443
self.signature_check = self.sec.correctly_signed_manage_name_id_response
446
# ----------------------------------------------------------------------------
449
class AuthnResponse(StatusResponse):
450
""" This is where all the profile compliance is checked.
451
This one does saml2int compliance. """
452
msgtype = "authn_response"
454
def __init__(self, sec_context, attribute_converters, entity_id,
455
return_addrs=None, outstanding_queries=None,
456
timeslack=0, asynchop=True, allow_unsolicited=False,
457
test=False, allow_unknown_attributes=False,
458
want_assertions_signed=False, want_response_signed=False, **kwargs):
460
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
462
self.entity_id = entity_id
463
self.attribute_converters = attribute_converters
464
if outstanding_queries:
465
self.outstanding_queries = outstanding_queries
467
self.outstanding_queries = {}
468
self.context = "AuthnReq"
471
self.assertion = None
472
self.session_not_on_or_after = 0
473
self.allow_unsolicited = allow_unsolicited
474
self.require_signature = want_assertions_signed
475
self.require_response_signature = want_response_signed
477
self.allow_unknown_attributes = allow_unknown_attributes
480
self.extension_schema = kwargs["extension_schema"]
482
self.extension_schema = {}
484
def loads(self, xmldata, decode=True, origxml=None):
485
self._loads(xmldata, decode, origxml)
488
if self.in_response_to in self.outstanding_queries:
489
self.came_from = self.outstanding_queries[self.in_response_to]
490
del self.outstanding_queries[self.in_response_to]
491
elif self.allow_unsolicited:
494
logger.exception("Unsolicited response %s" % self.in_response_to)
495
raise UnsolicitedResponse("Unsolicited response: %s" % self.in_response_to)
503
self.assertion = None
505
def authn_statement_ok(self, optional=False):
507
# the assertion MUST contain one AuthNStatement
508
assert len(self.assertion.authn_statement) == 1
509
except AssertionError:
515
authn_statement = self.assertion.authn_statement[0]
516
if authn_statement.session_not_on_or_after:
517
if validate_on_or_after(authn_statement.session_not_on_or_after,
519
self.session_not_on_or_after = calendar.timegm(
520
time_util.str_to_time(
521
authn_statement.session_not_on_or_after))
525
# check authn_statement.session_index
527
def condition_ok(self, lax=False):
531
# The Identity Provider MUST include a <saml:Conditions> element
532
assert self.assertion.conditions
533
conditions = self.assertion.conditions
535
logger.debug("conditions: %s" % conditions)
537
# if no sub-elements or elements are supplied, then the
538
# assertion is considered to be valid.
539
if not conditions.keyswv():
542
# if both are present NotBefore must be earlier than NotOnOrAfter
543
if conditions.not_before and conditions.not_on_or_after:
544
if not later_than(conditions.not_on_or_after, conditions.not_before):
548
if conditions.not_on_or_after:
549
self.not_on_or_after = validate_on_or_after(
550
conditions.not_on_or_after, self.timeslack)
551
if conditions.not_before:
552
validate_before(conditions.not_before, self.timeslack)
553
except Exception, excp:
554
logger.error("Exception on conditions: %s" % (excp,))
558
self.not_on_or_after = 0
560
if not self.allow_unsolicited:
561
if not for_me(conditions, self.entity_id):
563
raise Exception("Not for me!!!")
565
if conditions.condition: # extra conditions
566
for cond in conditions.condition:
568
if cond.extension_attributes[XSI_TYPE] in self.extension_schema:
571
raise Exception("Unknown condition")
573
raise Exception("Missing xsi:type specification")
577
def decrypt_attributes(self, attribute_statement):
579
Decrypts possible encrypted attributes and adds the decrypts to the
582
:param attribute_statement: A SAML.AttributeStatement which might
583
contain both encrypted attributes and attributes.
586
# "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedData",
587
# "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedAttribute"]
589
for encattr in attribute_statement.encrypted_attribute:
590
if not encattr.encrypted_key:
591
_decr = self.sec.decrypt(encattr.encrypted_data)
592
_attr = attribute_from_string(_decr)
593
attribute_statement.attribute.append(_attr)
595
_decr = self.sec.decrypt(encattr)
596
enc_attr = encrypted_attribute_from_string(_decr)
597
attrlist = enc_attr.extensions_as_elements("Attribute", saml)
598
attribute_statement.attribute.extend(attrlist)
600
def get_identity(self):
601
""" The assertion can contain zero or one attributeStatements
604
if not self.assertion.attribute_statement:
605
logger.error("Missing Attribute Statement")
608
assert len(self.assertion.attribute_statement) == 1
609
_attr_statem = self.assertion.attribute_statement[0]
611
logger.debug("Attribute Statement: %s" % (_attr_statem,))
612
for aconv in self.attribute_converters:
613
logger.debug("Converts name format: %s" % (aconv.name_format,))
615
self.decrypt_attributes(_attr_statem)
616
ava = to_local(self.attribute_converters, _attr_statem,
617
self.allow_unknown_attributes)
620
def _bearer_confirmed(self, data):
625
if not valid_address(data.address):
627
# verify that I got it from the correct sender
629
# These two will raise exception if untrue
630
validate_on_or_after(data.not_on_or_after, self.timeslack)
631
validate_before(data.not_before, self.timeslack)
633
# not_before must be < not_on_or_after
634
if not later_than(data.not_on_or_after, data.not_before):
637
if self.asynchop and not self.came_from:
638
if data.in_response_to:
639
if data.in_response_to in self.outstanding_queries:
640
self.came_from = self.outstanding_queries[
642
del self.outstanding_queries[data.in_response_to]
643
elif self.allow_unsolicited:
646
# This is where I don't allow unsolicited reponses
647
# Either in_response_to == None or has a value I don't
649
logger.debug("in response to: '%s'" % data.in_response_to)
650
logger.info("outstanding queries: %s" % (
651
self.outstanding_queries.keys(),))
653
"Combination of session id and requestURI I don't recall")
656
def _holder_of_key_confirmed(self, data):
661
for element in extension_elements_to_elements(data,
662
[samlp, saml, xenc, ds]):
663
if isinstance(element, ds.KeyInfo):
668
def get_subject(self):
669
""" The assertion must contain a Subject
671
assert self.assertion.subject
672
subject = self.assertion.subject
674
for subject_confirmation in subject.subject_confirmation:
675
_data = subject_confirmation.subject_confirmation_data
677
if subject_confirmation.method == SCM_BEARER:
678
if not self._bearer_confirmed(_data):
680
elif subject_confirmation.method == SCM_HOLDER_OF_KEY:
681
if not self._holder_of_key_confirmed(_data):
683
elif subject_confirmation.method == SCM_SENDER_VOUCHES:
686
raise ValueError("Unknown subject confirmation method: %s" % (
687
subject_confirmation.method,))
689
subjconf.append(subject_confirmation)
692
raise VerificationError("No valid subject confirmation")
694
subject.subject_confirmation = subjconf
696
# The subject must contain a name_id
698
assert subject.name_id
699
self.name_id = subject.name_id
700
except AssertionError:
701
if subject.encrypted_id:
702
# decrypt encrypted ID
703
_name_id_str = self.sec.decrypt(
704
subject.encrypted_id.encrypted_data.to_string())
705
_name_id = saml.name_id_from_string(_name_id_str)
706
self.name_id = _name_id
708
raise VerificationError("Missing NameID")
710
logger.info("Subject NameID: %s" % self.name_id)
713
def _assertion(self, assertion):
714
self.assertion = assertion
716
logger.debug("assertion context: %s" % (self.context,))
717
logger.debug("assertion keys: %s" % (assertion.keyswv()))
718
logger.debug("outstanding_queries: %s" % (self.outstanding_queries,))
720
#if self.context == "AuthnReq" or self.context == "AttrQuery":
721
if self.context == "AuthnReq":
722
self.authn_statement_ok()
723
# elif self.context == "AttrQuery":
724
# self.authn_statement_ok(True)
726
if not self.condition_ok():
727
raise VerificationError("Condition not OK")
729
logger.debug("--- Getting Identity ---")
731
if self.context == "AuthnReq" or self.context == "AttrQuery":
732
self.ava = self.get_identity()
734
logger.debug("--- AVA: %s" % (self.ava,))
739
if self.allow_unsolicited:
741
elif not self.came_from:
742
raise VerificationError("Came from")
745
logger.exception("get subject")
748
def _encrypted_assertion(self, xmlstr):
749
if xmlstr.encrypted_data:
750
assertion_str = self.sec.decrypt(xmlstr.encrypted_data.to_string())
751
if not assertion_str:
752
raise DecryptionFailed()
753
assertion = saml.assertion_from_string(assertion_str)
755
decrypt_xml = self.sec.decrypt(xmlstr)
757
logger.debug("Decryption successfull")
759
self.response = samlp.response_from_string(decrypt_xml)
760
logger.debug("Parsed decrypted assertion successfull")
762
enc = self.response.encrypted_assertion[0].extension_elements[0]
763
assertion = extension_element_to_element(
764
enc, saml.ELEMENT_FROM_STRING, namespace=saml.NAMESPACE)
766
logger.debug("Decrypted Assertion: %s" % assertion)
767
return self._assertion(assertion)
769
def parse_assertion(self):
770
if self.context == "AuthnQuery":
771
# can contain one or more assertions
773
else: # This is a saml2int limitation
775
assert len(self.response.assertion) == 1 or \
776
len(self.response.encrypted_assertion) == 1
777
except AssertionError:
778
raise Exception("No assertion part")
780
if self.response.assertion:
781
logger.debug("***Unencrypted response***")
782
for assertion in self.response.assertion:
783
if not self._assertion(assertion):
787
logger.debug("***Encrypted response***")
788
for assertion in self.response.encrypted_assertion:
789
if not self._encrypted_assertion(assertion):
794
""" Verify that the assertion is syntactically correct and
795
the signature is correct if present."""
799
except AssertionError:
802
if not isinstance(self.response, samlp.Response):
805
if self.parse_assertion():
808
logger.error("Could not parse the assertion")
811
def session_id(self):
812
""" Returns the SessionID of the response """
813
return self.response.in_response_to
816
""" Return the ID of the response """
817
return self.response.id
819
def authn_info(self):
821
for astat in self.assertion.authn_statement:
822
context = astat.authn_context
825
aclass = context.authn_context_class_ref.text
826
except AttributeError:
829
authn_auth = [a.text for a in
830
context.authenticating_authority]
831
except AttributeError:
833
res.append((aclass, authn_auth))
836
def authz_decision_info(self):
837
res = {"permit": [], "deny": [], "indeterminate": []}
838
for adstat in self.assertion.authz_decision_statement:
839
# one of 'Permit', 'Deny', 'Indeterminate'
840
res[adstat.decision.text.lower()] = adstat
843
def session_info(self):
844
""" Returns a predefined set of information gleened from the
846
:returns: Dictionary with information
848
if self.session_not_on_or_after > 0:
849
nooa = self.session_not_on_or_after
851
nooa = self.not_on_or_after
853
if self.context == "AuthzQuery":
854
return {"name_id": self.name_id, "came_from": self.came_from,
855
"issuer": self.issuer(), "not_on_or_after": nooa,
856
"authz_decision_info": self.authz_decision_info()}
858
return {"ava": self.ava, "name_id": self.name_id,
859
"came_from": self.came_from, "issuer": self.issuer(),
860
"not_on_or_after": nooa, "authn_info": self.authn_info()}
863
return "%s" % self.xmlstr
865
def verify_attesting_entity(self, address):
867
Assumes one assertion. At least one address specification has to be
870
:param address: IP address of attesting entity
875
for subject_conf in self.assertion.subject.subject_confirmation:
876
if subject_conf.subject_confirmation_data is None:
877
correct += 1 # In reality undefined
878
elif subject_conf.subject_confirmation_data.address:
879
if subject_conf.subject_confirmation_data.address == address:
890
class AuthnQueryResponse(AuthnResponse):
891
msgtype = "authn_query_response"
893
def __init__(self, sec_context, attribute_converters, entity_id,
894
return_addrs=None, timeslack=0, asynchop=False, test=False):
896
AuthnResponse.__init__(self, sec_context, attribute_converters,
897
entity_id, return_addrs, timeslack=timeslack,
898
asynchop=asynchop, test=test)
899
self.entity_id = entity_id
900
self.attribute_converters = attribute_converters
901
self.assertion = None
902
self.context = "AuthnQuery"
904
def condition_ok(self, lax=False): # Should I care about conditions ?
908
class AttributeResponse(AuthnResponse):
909
msgtype = "attribute_response"
911
def __init__(self, sec_context, attribute_converters, entity_id,
912
return_addrs=None, timeslack=0, asynchop=False, test=False):
914
AuthnResponse.__init__(self, sec_context, attribute_converters,
915
entity_id, return_addrs, timeslack=timeslack,
916
asynchop=asynchop, test=test)
917
self.entity_id = entity_id
918
self.attribute_converters = attribute_converters
919
self.assertion = None
920
self.context = "AttrQuery"
923
class AuthzResponse(AuthnResponse):
924
""" A successful response will be in the form of assertions containing
925
authorization decision statements."""
926
msgtype = "authz_decision_response"
928
def __init__(self, sec_context, attribute_converters, entity_id,
929
return_addrs=None, timeslack=0, asynchop=False):
930
AuthnResponse.__init__(self, sec_context, attribute_converters,
931
entity_id, return_addrs, timeslack=timeslack,
933
self.entity_id = entity_id
934
self.attribute_converters = attribute_converters
935
self.assertion = None
936
self.context = "AuthzQuery"
939
class ArtifactResponse(AuthnResponse):
940
msgtype = "artifact_response"
942
def __init__(self, sec_context, attribute_converters, entity_id,
943
return_addrs=None, timeslack=0, asynchop=False, test=False):
945
AuthnResponse.__init__(self, sec_context, attribute_converters,
946
entity_id, return_addrs, timeslack=timeslack,
947
asynchop=asynchop, test=test)
948
self.entity_id = entity_id
949
self.attribute_converters = attribute_converters
950
self.assertion = None
951
self.context = "ArtifactResolve"
954
def response_factory(xmlstr, conf, return_addrs=None, outstanding_queries=None,
955
timeslack=0, decode=True, request_id=0, origxml=None,
956
asynchop=True, allow_unsolicited=False, want_assertions_signed=False):
957
sec_context = security_context(conf)
960
timeslack = int(conf.accepted_time_diff)
964
attribute_converters = conf.attribute_converters
965
entity_id = conf.entityid
966
extension_schema = conf.extension_schema
968
response = StatusResponse(sec_context, return_addrs, timeslack, request_id,
971
response.loads(xmlstr, decode, origxml)
972
if response.response.assertion or response.response.encrypted_assertion:
973
authnresp = AuthnResponse(sec_context, attribute_converters,
974
entity_id, return_addrs,
975
outstanding_queries, timeslack, asynchop,
977
extension_schema=extension_schema,
978
want_assertions_signed=want_assertions_signed)
979
authnresp.update(response)
982
response.signature_check = sec_context.correctly_signed_logout_response
983
response.loads(xmlstr, decode, origxml)
984
logoutresp = LogoutResponse(sec_context, return_addrs, timeslack,
986
logoutresp.update(response)
991
# ===========================================================================
992
# A class of it's own
995
class AssertionIDResponse(object):
996
msgtype = "assertion_id_response"
998
def __init__(self, sec_context, attribute_converters, timeslack=0,
1001
self.sec = sec_context
1002
self.timeslack = timeslack
1005
self.response = None
1006
self.not_signed = False
1007
self.attribute_converters = attribute_converters
1008
self.assertion = None
1009
self.context = "AssertionIdResponse"
1010
self.signature_check = self.sec.correctly_signed_assertion_id_response
1012
def loads(self, xmldata, decode=True, origxml=None):
1014
self.xmlstr = xmldata[:]
1015
logger.debug("xmlstr: %s" % (self.xmlstr,))
1018
self.response = self.signature_check(xmldata, origdoc=origxml)
1019
self.assertion = self.response
1022
except SignatureError:
1024
except Exception, excp:
1025
logger.exception("EXCEPTION: %s", excp)
1028
#print "<", self.response
1030
return self._postamble()
1034
valid_instance(self.response)
1035
except NotValid, exc:
1036
logger.error("Not valid response: %s" % exc.args[0])
1040
def _postamble(self):
1041
if not self.response:
1042
logger.error("Response was not correctly signed")
1044
logger.info(self.xmlstr)
1045
raise IncorrectlySigned()
1047
logger.debug("response: %s" % (self.response,))