diff options
author | Ivan Kanakarakis <ivan.kanak@gmail.com> | 2020-11-24 15:25:28 +0200 |
---|---|---|
committer | Ivan Kanakarakis <ivan.kanak@gmail.com> | 2020-11-24 17:46:41 +0200 |
commit | a159cc537835b4588544d9ee129fec10bf757124 (patch) | |
tree | 81ee860ff92a6752f3b1f7a67d5021817985926a | |
parent | c0410837a5ee8c5c1fe656c501aa640c57000b59 (diff) | |
download | pysaml2-a159cc537835b4588544d9ee129fec10bf757124.tar.gz |
WIP, TODO cleanup, see description
* client_base::Base is the base for an SP and manages SP_ARGS
* server::Server is the base for an IdP and maanges AA_IDP_ARGS
* entity::Entity is the base of SP/IdPs and manages the COMMON_ARGS
The signing_algorithm and digest_algorithm are COMMON_ARGS
and should be set and managed by entity::Entity.
On init they are set as properties of the Entity object.
If no configuration has been given, the internal-default is set (through DefaultSignature()).
The set sign_alg and digest_alg must be checked against an allow/block-list
---
- Signing is done both by SPs (on requests) and IdPs (on responses).
- Signing is done both for the Redirect-binding (apply_binding()) and the POST-binding (_message() > sign()).
---
* All client_base::Base(SP) (create_*) methods end in Entity::_message()
* Almost all server::Server(IdP) (create_*) methods end in Entity::_response()
thus:
- Entity::_message() must decide the value of "sign" and call Entity::sign()
- Entity::_response() must decide the value of "sign" and call Entity::sign()
- Entity::_status_response() must decide the value of "sign" and call Entity::sign()
- Entity::sign() must decide the value of sign_alg and digest_alg and call sigver::pre_signature_part()
---
All calls to Entity::_message() and Entity::_response() (or to their callers)
must pass on sign, sign_alg and digest_alg
All calls to sigver::pre_signature_part() should happen through the same call-chain
and should pass on specific sign_alg and digest_alg params
All relevant params should be set to None unless they have been set by the caller.
---
client::do_logout should be refactored to use the same call-chain
---
These type of checks (and self.lock blocks) should be removed (there are more for sign_assertion)
```
if (sign and self.sec.cert_handler.generate_cert()) or client_crt is not None:
```
```
if self.sec.cert_handler.generate_cert()
```
---
Signed-off-by: Ivan Kanakarakis <ivan.kanak@gmail.com>
-rw-r--r-- | src/saml2/client_base.py | 384 | ||||
-rw-r--r-- | src/saml2/entity.py | 410 | ||||
-rw-r--r-- | src/saml2/server.py | 292 | ||||
-rw-r--r-- | src/saml2/sigver.py | 2 |
4 files changed, 723 insertions, 365 deletions
diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index dd3ddfdf..90a4fbd1 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -283,7 +283,7 @@ class Base(Entity): else: return None - # XXX sp create + # XXX DONE sp create > _message def create_authn_request( self, destination, @@ -296,7 +296,7 @@ class Base(Entity): consent=None, extensions=None, sign=None, - sign_prepare=False, + sign_prepare=None, sign_alg=None, digest_alg=None, allow_create=None, @@ -448,64 +448,39 @@ class Base(Entity): client_crt = kwargs.get("client_crt") nsprefix = kwargs.get("nsprefix") - sign = sign if sign is not None else self.should_sign - sign_alg = sign_alg or self.signing_algorithm - digest_alg = digest_alg or self.digest_algorithm - - if sign_alg not in [long_name for short_name, long_name in SIG_ALLOWED_ALG]: - raise Exception( - "Signature algo not in allowed list: {algo}".format(algo=sign_alg) - ) - if digest_alg not in [long_name for short_name, long_name in DIGEST_ALLOWED_ALG]: - raise Exception( - "Digest algo not in allowed list: {algo}".format(algo=digest_alg) - ) - - if (sign and self.sec.cert_handler.generate_cert()) or client_crt is not None: - with self.lock: - self.sec.cert_handler.update_cert(True, client_crt) - if client_crt is not None: - sign_prepare = True - msg = self._message( - AuthnRequest, - destination, - message_id, - consent, - extensions, - sign, - sign_prepare, - sign_alg=sign_alg, - digest_alg=digest_alg, - protocol_binding=binding, - scoping=scoping, - nsprefix=nsprefix, - **args, - ) - else: - msg = self._message( - AuthnRequest, - destination, - message_id, - consent, - extensions, - sign, - sign_prepare, - sign_alg=sign_alg, - digest_alg=digest_alg, - protocol_binding=binding, - scoping=scoping, - nsprefix=nsprefix, - **args, - ) + msg = self._message( + AuthnRequest, + destination, + message_id, + consent, + extensions, + sign, + sign_prepare, + sign_alg=sign_alg, + digest_alg=digest_alg, + protocol_binding=binding, + scoping=scoping, + nsprefix=nsprefix, + **args, + ) return msg - # XXX sp create - def create_attribute_query(self, destination, name_id=None, - attribute=None, message_id=0, consent=None, - extensions=None, sign=False, sign_prepare=False, sign_alg=None, - digest_alg=None, - **kwargs): + # XXX DONE sp create > _message + def create_attribute_query( + self, + destination, + name_id=None, + attribute=None, + message_id=0, + consent=None, + extensions=None, + sign=None, + sign_prepare=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ Constructs an AttributeQuery :param destination: To whom the query should be sent @@ -559,19 +534,40 @@ class Base(Entity): except KeyError: nsprefix = None - return self._message(AttributeQuery, destination, message_id, consent, - extensions, sign, sign_prepare, subject=subject, - attribute=attribute, nsprefix=nsprefix, - sign_alg=sign_alg, digest_alg=digest_alg) + return self._message( + AttributeQuery, + destination, + message_id, + consent, + extensions, + sign, + sign_prepare, + subject=subject, + attribute=attribute, + nsprefix=nsprefix, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) # MUST use SOAP for # AssertionIDRequest, SubjectQuery, # AuthnQuery, AttributeQuery, or AuthzDecisionQuery - # XXX sp create - def create_authz_decision_query(self, destination, action, - evidence=None, resource=None, subject=None, - message_id=0, consent=None, extensions=None, - sign=None, sign_alg=None, digest_alg=None, **kwargs): + # XXX DONE sp create > _message + def create_authz_decision_query( + self, + destination, + action, + evidence=None, + resource=None, + subject=None, + message_id=0, + consent=None, + extensions=None, + sign=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ Creates an authz decision query. :param destination: The IdP endpoint @@ -586,20 +582,38 @@ class Base(Entity): :return: AuthzDecisionQuery instance """ - return self._message(AuthzDecisionQuery, destination, message_id, - consent, extensions, sign, action=action, - evidence=evidence, resource=resource, - subject=subject, sign_alg=sign_alg, - digest_alg=digest_alg, **kwargs) - - # XXX sp create - def create_authz_decision_query_using_assertion(self, destination, - assertion, action=None, - resource=None, - subject=None, message_id=0, - consent=None, - extensions=None, - sign=False, nsprefix=None): + return self._message( + AuthzDecisionQuery, + destination, + message_id, + consent, + extensions, + sign, + action=action, + evidence=evidence, + resource=resource, + subject=subject, + sign_alg=sign_alg, + digest_alg=digest_alg, + **kwargs, + ) + + # XXX DONE sp create > self.create_authz_decision_query (FIXME pass sign/sign_alg/etc) > _message + def create_authz_decision_query_using_assertion( + self, + destination, + assertion, + action=None, + resource=None, + subject=None, + message_id=0, + consent=None, + extensions=None, + sign=None, + sign_ag=None, + digest_alg=None, + nsprefix=None, + ): """ Makes an authz decision query based on a previously received Assertion. @@ -624,12 +638,21 @@ class Base(Entity): _action = None return self.create_authz_decision_query( - destination, _action, saml.Evidence(assertion=assertion), - resource, subject, message_id=message_id, consent=consent, - extensions=extensions, sign=sign, nsprefix=nsprefix) + destination, + _action, + saml.Evidence(assertion=assertion), + resource, + subject, + message_id=message_id, + consent=consent, + extensions=extensions, + sign=sign, + sign_alg=sign_alg, + digest_alg=digest_alg, + nsprefix=nsprefix, + ) @staticmethod - # XXX sp create def create_assertion_id_request(assertion_id_refs, **kwargs): """ @@ -642,11 +665,21 @@ class Base(Entity): else: return 0, assertion_id_refs[0] - # XXX sp create - def create_authn_query(self, subject, destination=None, authn_context=None, - session_index="", message_id=0, consent=None, - extensions=None, sign=False, nsprefix=None, sign_alg=None, - digest_alg=None): + # XXX DONE sp create > _message + def create_authn_query( + self, + subject, + destination=None, + authn_context=None, + session_index="", + message_id=0, + consent=None, + extensions=None, + sign=None, + nsprefix=None, + sign_alg=None, + digest_alg=None, + ): """ :param subject: The subject its all about as a <Subject> instance @@ -659,20 +692,37 @@ class Base(Entity): :param sign: Whether the request should be signed or not. :return: """ - return self._message(AuthnQuery, destination, message_id, consent, - extensions, sign, subject=subject, - session_index=session_index, - requested_authn_context=authn_context, - nsprefix=nsprefix, sign_alg=sign_alg, - digest_alg=digest_alg) - - # XXX sp create - def create_name_id_mapping_request(self, name_id_policy, - name_id=None, base_id=None, - encrypted_id=None, destination=None, - message_id=0, consent=None, - extensions=None, sign=False, - nsprefix=None, sign_alg=None, digest_alg=None): + return self._message( + AuthnQuery, + destination, + message_id, + consent, + extensions, + sign, + subject=subject, + session_index=session_index, + requested_authn_context=authn_context, + nsprefix=nsprefix, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) + + # XXX DONE sp create > _message + def create_name_id_mapping_request( + self, + name_id_policy, + name_id=None, + base_id=None, + encrypted_id=None, + destination=None, + message_id=0, + consent=None, + extensions=None, + sign=None, + nsprefix=None, + sign_alg=None, + digest_alg=None, + ): """ :param name_id_policy: @@ -692,29 +742,39 @@ class Base(Entity): "At least one of name_id, base_id or encrypted_id must be present." ) - if name_id: - return self._message(NameIDMappingRequest, destination, message_id, - consent, extensions, sign, - name_id_policy=name_id_policy, name_id=name_id, - nsprefix=nsprefix, sign_alg=sign_alg, - digest_alg=digest_alg) - elif base_id: - return self._message(NameIDMappingRequest, destination, message_id, - consent, extensions, sign, - name_id_policy=name_id_policy, base_id=base_id, - nsprefix=nsprefix, sign_alg=sign_alg, - digest_alg=digest_alg) - else: - return self._message(NameIDMappingRequest, destination, message_id, - consent, extensions, sign, - name_id_policy=name_id_policy, - encrypted_id=encrypted_id, nsprefix=nsprefix, - sign_alg=sign_alg, digest_alg=digest_alg) + id_attr = { + "name_id": name_id, + "base_id": ( + base_id + if not name_id + else None + ), + "encrypted_id": ( + encrypted_id + if not name_id and not base_id + else None + ), + } + + return self._message( + NameIDMappingRequest, + destination, + message_id, + consent, + extensions, + sign, + name_id_policy=name_id_policy, + **id_attr, + nsprefix=nsprefix, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) # ======== response handling =========== - def parse_authn_request_response(self, xmlstr, binding, outstanding=None, - outstanding_certs=None, conv_info=None): + def parse_authn_request_response( + self, xmlstr, binding, outstanding=None, outstanding_certs=None, conv_info=None + ): """ Deal with an AuthnResponse :param xmlstr: The reply as a xml string @@ -743,15 +803,14 @@ class Base(Entity): "return_addrs": self.service_urls(binding=binding), "entity_id": self.config.entityid, "attribute_converters": self.config.attribute_converters, - "allow_unknown_attributes": - self.config.allow_unknown_attributes, - 'conv_info': conv_info + "allow_unknown_attributes": self.config.allow_unknown_attributes, + 'conv_info': conv_info, } try: - resp = self._parse_response(xmlstr, AuthnResponse, - "assertion_consumer_service", - binding, **kwargs) + resp = self._parse_response( + xmlstr, AuthnResponse, "assertion_consumer_service", binding, **kwargs + ) except StatusError as err: logger.error("SAML status error: %s", err) raise @@ -762,12 +821,14 @@ class Base(Entity): raise if not isinstance(resp, AuthnResponse): - logger.error("Response type not supported: %s", - saml2.class_name(resp)) + logger.error("Response type not supported: %s", saml2.class_name(resp)) return None - if (resp.assertion and len(resp.response.encrypted_assertion) == 0 and - resp.assertion.subject.name_id): + if ( + resp.assertion + and len(resp.response.encrypted_assertion) == 0 + and resp.assertion.subject.name_id + ): self.users.add_information_about_person(resp.session_info()) logger.info("--- ADDED person info ----") @@ -777,15 +838,15 @@ class Base(Entity): # SubjectQuery, AuthnQuery, RequestedAuthnContext, AttributeQuery, # AuthzDecisionQuery all get Response as response - def parse_authz_decision_query_response(self, response, - binding=BINDING_SOAP): + def parse_authz_decision_query_response(self, response, binding=BINDING_SOAP): """ Verify that the response is OK """ - kwargs = {"entity_id": self.config.entityid, - "attribute_converters": self.config.attribute_converters} + kwargs = { + "entity_id": self.config.entityid, + "attribute_converters": self.config.attribute_converters, + } - return self._parse_response(response, AuthzResponse, "", binding, - **kwargs) + return self._parse_response(response, AuthzResponse, "", binding, **kwargs) def parse_authn_query_response(self, response, binding=BINDING_SOAP): """ Verify that the response is OK @@ -828,9 +889,16 @@ class Base(Entity): # ------------------- ECP ------------------------------------------------ - # XXX sp create - def create_ecp_authn_request(self, entityid=None, relay_state="", - sign=False, **kwargs): + # XXX DONE sp create > create_authn_request (FIXME DONE sign/sign_alg/etc) > _message + def create_ecp_authn_request( + self, + entityid=None, + relay_state="", + sign=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ Makes an authentication request. :param entityid: The entity ID of the IdP to send the request to @@ -847,16 +915,22 @@ class Base(Entity): # must_understand and act according to the standard # - paos_request = paos.Request(must_understand="1", actor=ACTOR, - response_consumer_url=my_url, - service=ECP_SERVICE) + paos_request = paos.Request( + must_understand="1", + actor=ACTOR, + response_consumer_url=my_url, + service=ECP_SERVICE, + ) # ---------------------------------------- # <ecp:RelayState> # ---------------------------------------- - relay_state = ecp.RelayState(actor=ACTOR, must_understand="1", - text=relay_state) + relay_state = ecp.RelayState( + actor=ACTOR, + must_understand="1", + text=relay_state, + ) # ---------------------------------------- # <samlp:AuthnRequest> @@ -883,18 +957,23 @@ class Base(Entity): "single_sign_on_service", [_binding], entity_id=entityid ) req_id, authn_req = self.create_authn_request( - location, service_url_binding=BINDING_PAOS, **kwargs + location, + service_url_binding=BINDING_PAOS, + sign=sign, + sign_alg=sign_alg, + digest_alg=digest_alg, + **kwargs, ) # ---------------------------------------- # The SOAP envelope # ---------------------------------------- - soap_envelope = make_soap_enveloped_saml_thingy(authn_req, - [paos_request, - relay_state]) + soap_envelope = make_soap_enveloped_saml_thingy( + authn_req, [paos_request, relay_state] + ) - return req_id, "%s" % soap_envelope + return req_id, str(soap_envelope) def parse_ecp_authn_response(self, txt, outstanding=None): rdict = soap.class_instances_from_soap_enveloped_saml_thingies(txt, @@ -933,7 +1012,6 @@ class Base(Entity): # ---------------------------------------------------------------------- @staticmethod - # XXX sp create def create_discovery_service_request(url, entity_id, **kwargs): """ Created the HTTP redirect URL needed to send the user to the diff --git a/src/saml2/entity.py b/src/saml2/entity.py index 39bc3dec..a619882b 100644 --- a/src/saml2/entity.py +++ b/src/saml2/entity.py @@ -75,6 +75,8 @@ from saml2.pack import http_redirect_message from saml2.pack import http_form_post_message from saml2.xmldsig import DefaultSignature +from saml2.xmldsig import SIG_ALLOWED_ALG +from saml2.xmldsig import DIGEST_ALLOWED_ALG logger = logging.getLogger(__name__) @@ -213,6 +215,10 @@ class Entity(HTTPBase): return Issuer(text=self.config.entityid, format=NAMEID_FORMAT_ENTITY) + # XXX DONE will actually use sign_alg and digest_alg for the Redirect-Binding + # XXX DONE deepest level - needs to decide the sign_alg (no digest_alg here) + # XXX verify digest_alg is not needed + # XXX deprecate sigalg for sign_alg def apply_binding( self, binding, @@ -237,8 +243,13 @@ class Entity(HTTPBase): :return: A dictionary """ + # XXX sig-allowed should be configurable sign = sign if sign is not None else self.should_sign - sigalg = sigalg or self.signing_algorithm + sign_alg = sigalg or self.signing_algorithm + if sign_alg not in [long_name for short_name, long_name in SIG_ALLOWED_ALG]: + raise Exception( + "Signature algo not in allowed list: {algo}".format(algo=sign_alg) + ) # unless if BINDING_HTTP_ARTIFACT if response: @@ -260,14 +271,14 @@ class Entity(HTTPBase): relay_state=relay_state, typ=typ, sign=sign, - sigalg=sigalg, + sigalg=sign_alg, backend=self.sec.sec_backend, ) info["url"] = str(destination) info["method"] = "GET" elif binding == BINDING_SOAP or binding == BINDING_PAOS: info = self.use_soap( - msg_str, destination, sign=sign, sigalg=sigalg, **kwargs + msg_str, destination, sign=sign, sigalg=sign_alg, **kwargs ) elif binding == BINDING_URI: info = self.use_http_uri(msg_str, typ, destination) @@ -335,8 +346,13 @@ class Entity(HTTPBase): if not message_id: message_id = sid() - return {"id": message_id, "version": VERSION, - "issue_instant": instant(), "issuer": self._issuer()} + margs = { + "id": message_id, + "version": VERSION, + "issue_instant": instant(), + "issuer": self._issuer(), + } + return margs def response_args(self, message, bindings=None, descr_type=""): """ @@ -450,15 +466,30 @@ class Entity(HTTPBase): # -------------------------------------------------------------------------- + # XXX DONE will actually use sign_alg and digest_alg for the POST-Binding + # XXX DONE deepest level - needs to decide the sign_alg and digest_alg value + # XXX calls pre_signature_part def sign( self, msg, mid=None, to_sign=None, - sign_prepare=False, + sign_prepare=None, sign_alg=None, digest_alg=None, ): + # XXX sig/digest-allowed should be configurable + sign_alg = sign_alg or self.signing_algorithm + digest_alg = digest_alg or self.digest_algorithm + if sign_alg not in [long_name for short_name, long_name in SIG_ALLOWED_ALG]: + raise Exception( + "Signature algo not in allowed list: {algo}".format(algo=sign_alg) + ) + if digest_alg not in [long_name for short_name, long_name in DIGEST_ALLOWED_ALG]: + raise Exception( + "Digest algo not in allowed list: {algo}".format(algo=digest_alg) + ) + if msg.signature is None: msg.signature = pre_signature_part( msg.id, self.sec.my_cert, 1, sign_alg=sign_alg, digest_alg=digest_alg @@ -478,7 +509,11 @@ class Entity(HTTPBase): logger.info("REQUEST: %s", msg) return signed_instance_factory(msg, self.sec, to_sign) - # XXX calls self.sign + # XXX DONE will actually use sign the POST-Binding + # XXX DONE deepest level - needs to decide the sign value + # XXX DONE calls self.sign must figure out sign + # XXX ensure both SPs and IdPs go through this + # XXX ensure this works for the POST-Binding def _message( self, request_cls, @@ -486,8 +521,8 @@ class Entity(HTTPBase): message_id=0, consent=None, extensions=None, - sign=False, - sign_prepare=False, + sign=None, + sign_prepare=None, nsprefix=None, sign_alg=None, digest_alg=None, @@ -533,6 +568,8 @@ class Entity(HTTPBase): req = self.msg_cb(req) reqid = req.id + + sign = sign if sign is not None else self.should_sign if sign: signed_req = self.sign( req, @@ -593,8 +630,7 @@ class Entity(HTTPBase): return True return False - def _encrypt_assertion(self, encrypt_cert, sp_entity_id, response, - node_xpath=None): + def _encrypt_assertion(self, encrypt_cert, sp_entity_id, response, node_xpath=None): """ Encryption of assertions. :param encrypt_cert: Certificate to be used for encryption. @@ -632,15 +668,27 @@ class Entity(HTTPBase): raise exception return response - # XXX calls self.sign - def _response(self, in_response_to, consumer_url=None, status=None, - issuer=None, sign=False, to_sign=None, sp_entity_id=None, - encrypt_assertion=False, - encrypt_assertion_self_contained=False, - encrypted_advice_attributes=False, - encrypt_cert_advice=None, encrypt_cert_assertion=None, - sign_assertion=None, pefim=False, sign_alg=None, - digest_alg=None, **kwargs): + # XXX DONE calls self.sign must figure out sign + def _response( + self, + in_response_to, + consumer_url=None, + status=None, + issuer=None, + sign=None, + to_sign=None, + sp_entity_id=None, + encrypt_assertion=False, + encrypt_assertion_self_contained=False, + encrypted_advice_attributes=False, + encrypt_cert_advice=None, + encrypt_cert_assertion=None, + sign_assertion=None, + pefim=False, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ Create a Response. Encryption: encrypt_assertion must be true for encryption to be @@ -692,7 +740,12 @@ class Entity(HTTPBase): self._add_info(response, **kwargs) - if not sign and to_sign and not encrypt_assertion: + sign = sign if sign is not None else self.should_sign + if ( + not sign + and to_sign + and not encrypt_assertion + ): return signed_instance_factory(response, self.sec, to_sign) has_encrypt_cert = self.has_encrypt_cert_in_metadata(sp_entity_id) @@ -701,23 +754,31 @@ class Entity(HTTPBase): if not has_encrypt_cert and encrypt_cert_assertion is None: encrypt_assertion = False - if encrypt_assertion or ( - encrypted_advice_attributes and - response.assertion.advice is - not None and - len(response.assertion.advice.assertion) == 1): + if ( + encrypt_assertion + or ( + encrypted_advice_attributes + and response.assertion.advice is not None + and len(response.assertion.advice.assertion) == 1 + ) + ): if sign: - response.signature = pre_signature_part(response.id, - self.sec.my_cert, 1, - sign_alg=sign_alg, - digest_alg=digest_alg) + response.signature = pre_signature_part( + response.id, + self.sec.my_cert, + 1, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) sign_class = [(class_name(response), response.id)] else: sign_class = [] - if encrypted_advice_attributes and response.assertion.advice is \ - not None \ - and len(response.assertion.advice.assertion) > 0: + if ( + encrypted_advice_attributes + and response.assertion.advice is not None + and len(response.assertion.advice.assertion) > 0 + ): _assertions = response.assertion if not isinstance(_assertions, list): _assertions = [_assertions] @@ -810,10 +871,17 @@ class Entity(HTTPBase): else: return response - # XXX calls self.sign - def _status_response(self, response_class, issuer, status, sign=False, - sign_alg=None, digest_alg=None, - **kwargs): + # XXX DONE calls self.sign must figure out sign + def _status_response( + self, + response_class, + issuer, + status, + sign=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ Create a StatusResponse. :param response_class: Which subclass of StatusResponse that should be @@ -828,18 +896,21 @@ class Entity(HTTPBase): mid = sid() for key in ["binding"]: - try: - del kwargs[key] - except KeyError: - pass + kwargs.pop(key, None) if not status: status = success_status_factory() - response = response_class(issuer=issuer, id=mid, version=VERSION, - issue_instant=instant(), - status=status, **kwargs) + response = response_class( + issuer=issuer, + id=mid, + version=VERSION, + issue_instant=instant(), + status=status, + **kwargs, + ) + sign = sign if sign is not None else self.should_sign if sign: return self.sign(response, mid, sign_alg=sign_alg, digest_alg=digest_alg) else: @@ -919,10 +990,18 @@ class Entity(HTTPBase): # ------------------------------------------------------------------------ - # XXX ent create - def create_error_response(self, in_response_to, destination, info, - sign=False, issuer=None, sign_alg=None, - digest_alg=None, **kwargs): + # XXX DONE ent create > _response + def create_error_response( + self, + in_response_to, + destination, + info, + sign=None, + issuer=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ Create a error response. :param in_response_to: The identifier of the message this is a response @@ -937,18 +1016,35 @@ class Entity(HTTPBase): """ status = error_status_factory(info) - return self._response(in_response_to, destination, status, issuer, - sign, sign_alg=sign_alg, digest_alg=digest_alg) + return self._response( + in_response_to, + destination, + status, + issuer, + sign, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) # ------------------------------------------------------------------------ - # XXX ent create - def create_logout_request(self, destination, issuer_entity_id, - subject_id=None, name_id=None, - reason=None, expire=None, message_id=0, - consent=None, extensions=None, sign=False, - session_indexes=None, sign_alg=None, - digest_alg=None): + # XXX DONE ent create > _message + def create_logout_request( + self, + destination, + issuer_entity_id, + subject_id=None, + name_id=None, + reason=None, + expire=None, + message_id=0, + consent=None, + extensions=None, + sign=None, + session_indexes=None, + sign_alg=None, + digest_alg=None, + ): """ Constructs a LogoutRequest :param destination: Destination of the request @@ -970,9 +1066,9 @@ class Entity(HTTPBase): if subject_id: if self.entity_type == "idp": - name_id = NameID(text=self.users.get_entityid(subject_id, - issuer_entity_id, - False)) + name_id = NameID( + text=self.users.get_entityid(subject_id, issuer_entity_id, False) + ) else: name_id = NameID(text=subject_id) @@ -989,16 +1085,33 @@ class Entity(HTTPBase): sis.append(SessionIndex(text=si)) args["session_index"] = sis - return self._message(LogoutRequest, destination, message_id, - consent, extensions, sign, name_id=name_id, - reason=reason, not_on_or_after=expire, - issuer=self._issuer(), sign_alg=sign_alg, - digest_alg=digest_alg, **args) + return self._message( + LogoutRequest, + destination, + message_id, + consent, + extensions, + sign, + name_id=name_id, + reason=reason, + not_on_or_after=expire, + issuer=self._issuer(), + sign_alg=sign_alg, + digest_alg=digest_alg, + **args, + ) - # XXX ent create - def create_logout_response(self, request, bindings=None, status=None, - sign=False, issuer=None, sign_alg=None, - digest_alg=None): + # XXX DONE ent create > _status_response + def create_logout_response( + self, + request, + bindings=None, + status=None, + sign=None, + issuer=None, + sign_alg=None, + digest_alg=None, + ): """ Create a LogoutResponse. :param request: The request this is a response to @@ -1015,18 +1128,32 @@ class Entity(HTTPBase): if not issuer: issuer = self._issuer() - response = self._status_response(samlp.LogoutResponse, issuer, status, - sign, sign_alg=sign_alg, - digest_alg=digest_alg, **rinfo) + response = self._status_response( + samlp.LogoutResponse, + issuer, + status, + sign, + sign_alg=sign_alg, + digest_alg=digest_alg, + **rinfo, + ) logger.info("Response: %s", response) return response - # XXX ent create - def create_artifact_resolve(self, artifact, destination, sessid, - consent=None, extensions=None, sign=False, - sign_alg=None, digest_alg=None): + # XXX DONE ent create > _message + def create_artifact_resolve( + self, + artifact, + destination, + sessid, + consent=None, + extensions=None, + sign=None, + sign_alg=None, + digest_alg=None, + ): """ Create a ArtifactResolve request @@ -1041,23 +1168,45 @@ class Entity(HTTPBase): artifact = Artifact(text=artifact) - return self._message(ArtifactResolve, destination, sessid, - consent, extensions, sign, artifact=artifact, - sign_alg=sign_alg, digest_alg=digest_alg) + return self._message( + ArtifactResolve, + destination, + sessid, + consent, + extensions, + sign, + artifact=artifact, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) - # XXX ent create - def create_artifact_response(self, request, artifact, bindings=None, - status=None, sign=False, issuer=None, - sign_alg=None, digest_alg=None): + # XXX DONE ent create > _status_response + def create_artifact_response( + self, + request, + artifact, + bindings=None, + status=None, + sign=None, + issuer=None, + sign_alg=None, + digest_alg=None, + ): """ Create an ArtifactResponse :return: """ rinfo = self.response_args(request, bindings) - response = self._status_response(ArtifactResponse, issuer, status, - sign=sign, sign_alg=sign_alg, - digest_alg=digest_alg, **rinfo) + response = self._status_response( + ArtifactResponse, + issuer, + status, + sign=sign, + sign_alg=sign_alg, + digest_alg=digest_alg, + **rinfo, + ) msg = element_to_extension_element(self.artifact[artifact]) response.extension_elements = [msg] @@ -1066,13 +1215,22 @@ class Entity(HTTPBase): return response - # XXX ent create - def create_manage_name_id_request(self, destination, message_id=0, - consent=None, extensions=None, sign=False, - name_id=None, new_id=None, - encrypted_id=None, new_encrypted_id=None, - terminate=None, sign_alg=None, - digest_alg=None): + # XXX DONE ent create > _message + def create_manage_name_id_request( + self, + destination, + message_id=0, + consent=None, + extensions=None, + sign=None, + name_id=None, + new_id=None, + encrypted_id=None, + new_encrypted_id=None, + terminate=None, + sign_alg=None, + digest_alg=None, + ): """ :param destination: @@ -1108,9 +1266,16 @@ class Entity(HTTPBase): "One of NewID, NewEncryptedNameID or Terminate has to be " "provided") - return self._message(ManageNameIDRequest, destination, consent=consent, - extensions=extensions, sign=sign, - sign_alg=sign_alg, digest_alg=digest_alg, **kwargs) + return self._message( + ManageNameIDRequest, + destination, + consent=consent, + extensions=extensions, + sign=sign, + sign_alg=sign_alg, + digest_alg=digest_alg, + **kwargs, + ) def parse_manage_name_id_request(self, xmlstr, binding=BINDING_SOAP): """ Deal with a LogoutRequest @@ -1125,11 +1290,18 @@ class Entity(HTTPBase): return self._parse_request(xmlstr, saml_request.ManageNameIDRequest, "manage_name_id_service", binding) - # XXX ent create - def create_manage_name_id_response(self, request, bindings=None, - status=None, sign=False, issuer=None, - sign_alg=None, digest_alg=None, - **kwargs): + # XXX DONE ent create > _status_response + def create_manage_name_id_response( + self, + request, + bindings=None, + status=None, + sign=None, + issuer=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): rinfo = self.response_args(request, bindings) @@ -1147,16 +1319,26 @@ class Entity(HTTPBase): return response - def parse_manage_name_id_request_response(self, string, - binding=BINDING_SOAP): - return self._parse_response(string, saml_response.ManageNameIDResponse, - "manage_name_id_service", binding, - asynchop=False) + def parse_manage_name_id_request_response(self, string, binding=BINDING_SOAP): + return self._parse_response( + string, + saml_response.ManageNameIDResponse, + "manage_name_id_service", + binding, + asynchop=False, + ) # ------------------------------------------------------------------------ - def _parse_response(self, xmlstr, response_cls, service, binding, - outstanding_certs=None, **kwargs): + def _parse_response( + self, + xmlstr, + response_cls, + service, + binding, + outstanding_certs=None, + **kwargs, + ): """ Deal with a Response :param xmlstr: The response as a xml string @@ -1357,7 +1539,15 @@ class Entity(HTTPBase): return destination - def artifact2message(self, artifact, descriptor, sign=False): + # XXX DONE uses sign but not a create_* + def artifact2message( + self, + artifact, + descriptor, + sign=None, + sign_alg=None, + digest_alg=None, + ): """ :param artifact: The Base64 encoded SAML artifact as sent over the net @@ -1377,6 +1567,8 @@ class Entity(HTTPBase): destination, _sid, sign=sign, + sign_alg=sign_alg, + digest_alg=digest_alg, ) return self.send_using_soap(msg, destination) diff --git a/src/saml2/server.py b/src/saml2/server.py index 1d4b7543..04604b09 100644 --- a/src/saml2/server.py +++ b/src/saml2/server.py @@ -392,6 +392,7 @@ class Server(Entity): **kwargs) return assertion + # XXX > _response def _authn_response( self, in_response_to, @@ -403,8 +404,8 @@ class Server(Entity): authn=None, issuer=None, policy=None, - sign_assertion=False, - sign_response=False, + sign_assertion=None, + sign_response=None, best_effort=False, encrypt_assertion=False, encrypt_cert_advice=None, @@ -493,10 +494,9 @@ class Server(Entity): to_sign = [] if not encrypt_assertion: if sign_assertion: - assertion.signature = pre_signature_part(assertion.id, - self.sec.my_cert, 2, - sign_alg=sign_alg, - digest_alg=digest_alg) + assertion.signature = pre_signature_part( + assertion.id, self.sec.my_cert, 2, sign_alg=sign_alg, digest_alg=digest_alg + ) to_sign.append((class_name(assertion), assertion.id)) args["assertion"] = assertion @@ -505,25 +505,47 @@ class Server(Entity): self.session_db.store_assertion(assertion, to_sign) return self._response( - in_response_to, consumer_url, status, issuer, sign_response, - to_sign, sp_entity_id=sp_entity_id, + in_response_to, + consumer_url, + status, + issuer, + sign_response, + to_sign, + sp_entity_id=sp_entity_id, encrypt_assertion=encrypt_assertion, encrypt_cert_advice=encrypt_cert_advice, encrypt_cert_assertion=encrypt_cert_assertion, encrypt_assertion_self_contained=encrypt_assertion_self_contained, encrypted_advice_attributes=encrypted_advice_attributes, sign_assertion=sign_assertion, - pefim=pefim, sign_alg=sign_alg, digest_alg=digest_alg, **args) + pefim=pefim, + sign_alg=sign_alg, + digest_alg=digest_alg, + **args, + ) # ------------------------------------------------------------------------ - # XXX idp create - def create_attribute_response(self, identity, in_response_to, destination, - sp_entity_id, userid="", name_id=None, - status=None, issuer=None, - sign_assertion=False, sign_response=False, - attributes=None, sign_alg=None, - digest_alg=None, farg=None, **kwargs): + # XXX calls pre_signature_part without ensuring sign_alg/digest_alg + # XXX DONE idp create > _response + def create_attribute_response( + self, + identity, + in_response_to, + destination, + sp_entity_id, + userid="", + name_id=None, + status=None, + issuer=None, + sign_assertion=None, + sign_response=None, + attributes=None, + sign_alg=None, + digest_alg=None, + farg=None, + **kwargs, + ): """ Create an attribute assertion response. :param identity: A dictionary with attributes and values that are @@ -573,10 +595,10 @@ class Server(Entity): farg=farg['assertion']) if sign_assertion: - assertion.signature = pre_signature_part(assertion.id, - self.sec.my_cert, 1, - sign_alg=sign_alg, - digest_alg=digest_alg) + # XXX calls pre_signature_part without ensuring sign_alg/digest_alg + assertion.signature = pre_signature_part( + assertion.id, self.sec.my_cert, 1, sign_alg=sign_alg, digest_alg=digest_alg + ) # Just the assertion or the response and the assertion ? to_sign = [(class_name(assertion), assertion.id)] kwargs['sign_assertion'] = True @@ -690,7 +712,7 @@ class Server(Entity): return args - # XXX idp create + # XXX DONE idp create > _authn_response > _response def create_authn_response( self, identity, @@ -769,52 +791,67 @@ class Server(Entity): try: _authn = authn - if (sign_assertion or sign_response) and \ - self.sec.cert_handler.generate_cert(): - with self.lock: - self.sec.cert_handler.update_cert(True) - return self._authn_response( - in_response_to, destination, sp_entity_id, identity, - authn=_authn, issuer=issuer, pefim=pefim, - sign_alg=sign_alg, digest_alg=digest_alg, - session_not_on_or_after=session_not_on_or_after, **args) return self._authn_response( - in_response_to, destination, sp_entity_id, identity, - authn=_authn, issuer=issuer, pefim=pefim, sign_alg=sign_alg, + in_response_to, + destination, + sp_entity_id, + identity, + authn=_authn, + issuer=issuer, + pefim=pefim, + sign_alg=sign_alg, digest_alg=digest_alg, - session_not_on_or_after=session_not_on_or_after, **args) - + session_not_on_or_after=session_not_on_or_after, + **args, + ) except MissingValue as exc: - return self.create_error_response(in_response_to, destination, - sp_entity_id, exc, name_id) - - # XXX idp create - def create_authn_request_response(self, identity, in_response_to, - destination, sp_entity_id, - name_id_policy=None, userid=None, - name_id=None, authn=None, authn_decl=None, - issuer=None, sign_response=False, - sign_assertion=False, - session_not_on_or_after=None, **kwargs): - - return self.create_authn_response(identity, in_response_to, destination, - sp_entity_id, name_id_policy, userid, - name_id, authn, issuer, - sign_response, sign_assertion, - authn_decl=authn_decl, - session_not_on_or_after=session_not_on_or_after) - - # XXX idp create - def create_assertion_id_request_response(self, assertion_id, sign=False, - sign_alg=None, - digest_alg=None, **kwargs): - """ + return self.create_error_response( + in_response_to, destination, sp_entity_id, exc, name_id + ) - :param assertion_id: - :param sign: - :return: - """ + # XXX DONE idp create > create_authn_response > _authn_response > _response + def create_authn_request_response( + self, + identity, + in_response_to, + destination, + sp_entity_id, + name_id_policy=None, + userid=None, + name_id=None, + authn=None, + authn_decl=None, + issuer=None, + sign_response=None, + sign_assertion=None, + session_not_on_or_after=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): + return self.create_authn_response( + identity, + in_response_to, + destination, + sp_entity_id, + name_id_policy, + userid, + name_id, + authn, + issuer, + sign_response, + sign_assertion, + authn_decl=authn_decl, + session_not_on_or_after=session_not_on_or_after, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) + # XXX calls pre_signature_part without ensuring sign_alg/digest_alg + # XXX DONE idp create > [...] + def create_assertion_id_request_response( + self, assertion_id, sign=None, sign_alg=None, digest_alg=None, **kwargs + ): try: (assertion, to_sign) = self.session_db.get_assertion(assertion_id) except KeyError: @@ -822,22 +859,33 @@ class Server(Entity): if to_sign: if assertion.signature is None: - assertion.signature = pre_signature_part(assertion.id, - self.sec.my_cert, 1, - sign_alg=sign_alg, - digest_alg=digest_alg) - + # XXX calls pre_signature_part without ensuring sign_alg/digest_alg + assertion.signature = pre_signature_part( + assertion.id, + self.sec.my_cert, + 1, + sign_alg=sign_alg, + digest_alg=digest_alg, + ) return signed_instance_factory(assertion, self.sec, to_sign) else: return assertion + # XXX calls self.sign without ensuring sign # XXX calls self.sign => should it call _message (which calls self.sign)? - # XXX idp create - def create_name_id_mapping_response(self, name_id=None, encrypted_id=None, - in_response_to=None, - issuer=None, sign_response=False, - status=None, sign_alg=None, - digest_alg=None, **kwargs): + # XXX idp create > NameIDMappingResponse & sign? + def create_name_id_mapping_response( + self, + name_id=None, + encrypted_id=None, + in_response_to=None, + issuer=None, + sign_response=None, + status=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ protocol for mapping a principal's name identifier into a different name identifier for the same principal. @@ -855,8 +903,9 @@ class Server(Entity): ms_args = self.message_args() - _resp = NameIDMappingResponse(name_id, encrypted_id, - in_response_to=in_response_to, **ms_args) + _resp = NameIDMappingResponse( + name_id, encrypted_id, in_response_to=in_response_to, **ms_args + ) if sign_response: return self.sign(_resp, sign_alg=sign_alg, digest_alg=digest_alg) @@ -864,12 +913,20 @@ class Server(Entity): logger.info("Message: %s", _resp) return _resp - # XXX idp create - def create_authn_query_response(self, subject, session_index=None, - requested_context=None, in_response_to=None, - issuer=None, sign_response=False, - status=None, sign_alg=None, digest_alg=None, - **kwargs): + # XXX DONE idp create > _response + def create_authn_query_response( + self, + subject, + session_index=None, + requested_context=None, + in_response_to=None, + issuer=None, + sign_response=None, + status=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): """ A successful <Response> will contain one or more assertions containing authentication statements. @@ -878,33 +935,54 @@ class Server(Entity): """ margs = self.message_args() - asserts = [] - for statement in self.session_db.get_authn_statements( - subject.name_id, session_index, requested_context): - asserts.append(saml.Assertion(authn_statement=statement, - subject=subject, **margs)) + asserts = [ + saml.Assertion(authn_statement=statement, subject=subject, **margs) + for statement in self.session_db.get_authn_statements( + subject.name_id, session_index, requested_context + ) + ] if asserts: args = {"assertion": asserts} else: args = {} - return self._response(in_response_to, "", status, issuer, - sign_response, to_sign=[], sign_alg=sign_alg, - digest_alg=digest_alg, **args) + return self._response( + in_response_to, + "", + status, + issuer, + sign_response, + to_sign=[], + sign_alg=sign_alg, + digest_alg=digest_alg, + **args, + ) # --------- def parse_ecp_authn_request(self): pass - # XXX idp create - def create_ecp_authn_request_response(self, acs_url, identity, - in_response_to, destination, - sp_entity_id, name_id_policy=None, - userid=None, name_id=None, authn=None, - issuer=None, sign_response=False, - sign_assertion=False, **kwargs): + # XXX DONE idp create > create_authn_response > _authn_response > _response + def create_ecp_authn_request_response( + self, + acs_url, + identity, + in_response_to, + destination, + sp_entity_id, + name_id_policy=None, + userid=None, + name_id=None, + authn=None, + issuer=None, + sign_response=None, + sign_assertion=None, + sign_alg=None, + digest_alg=None, + **kwargs, + ): # ---------------------------------------- # <ecp:Response @@ -918,17 +996,27 @@ class Server(Entity): # <samlp:Response # ---------------------------------------- - response = self.create_authn_response(identity, in_response_to, - destination, sp_entity_id, - name_id_policy, userid, name_id, - authn, issuer, - sign_response, sign_assertion) + response = self.create_authn_response( + identity, + in_response_to, + destination, + sp_entity_id, + name_id_policy, + userid, + name_id, + authn, + issuer, + sign_response, + sign_assertion, + sign_alg=sign_alg, + digest_alg=digest_alg + ) body = soapenv.Body() body.extension_elements = [element_to_extension_element(response)] soap_envelope = soapenv.Envelope(header=header, body=body) - return "%s" % soap_envelope + return str(soap_envelope) def close(self): self.ident.close() diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py index 87441807..b8e924c2 100644 --- a/src/saml2/sigver.py +++ b/src/saml2/sigver.py @@ -1750,7 +1750,7 @@ class SecurityContext(object): return statement -# XXX calls DefaultSignature +# XXX FIXME calls DefaultSignature - remove to unveil chain of calls without proper args def pre_signature_part(ident, public_key=None, identifier=None, digest_alg=None, sign_alg=None): """ If an assertion is to be signed the signature part has to be preset |