Hello community, here is the log from the commit of package python-pysaml2 for openSUSE:Factory checked in at 2020-07-14 07:56:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pysaml2 (Old) and /work/SRC/openSUSE:Factory/.python-pysaml2.new.3060 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pysaml2" Tue Jul 14 07:56:25 2020 rev:17 rq:820096 version:5.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pysaml2/python-pysaml2.changes 2020-06-15 20:28:54.454041131 +0200 +++ /work/SRC/openSUSE:Factory/.python-pysaml2.new.3060/python-pysaml2.changes 2020-07-14 07:58:46.297689005 +0200 @@ -1,0 +2,9 @@ +Fri Jul 10 12:29:12 UTC 2020 - Dirk Mueller <[email protected]> + +- update to 5.3.0: + - Fix check for nameid_format set to the string "None" in the configuration + - Fix presence of empty eIDAS RequestedAttributes element on AuthnRequest + - Refactor create_authn_request method to be easier to reason about + - Fix NameIDPolicy checks for allowed Format and allowCreate values + +------------------------------------------------------------------- Old: ---- v5.1.0.tar.gz New: ---- v5.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pysaml2.spec ++++++ --- /var/tmp/diff_new_pack.7YHOWo/_old 2020-07-14 07:58:48.105694860 +0200 +++ /var/tmp/diff_new_pack.7YHOWo/_new 2020-07-14 07:58:48.109694872 +0200 @@ -20,7 +20,7 @@ %global modname pysaml2 %global skip_python2 1 Name: python-pysaml2 -Version: 5.1.0 +Version: 5.3.0 Release: 0 Summary: Python implementation of SAML Version 2 to be used in a WSGI environment License: Apache-2.0 ++++++ v5.1.0.tar.gz -> v5.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pysaml2-5.1.0/CHANGELOG.md new/pysaml2-5.3.0/CHANGELOG.md --- old/pysaml2-5.1.0/CHANGELOG.md 2020-06-09 13:03:44.000000000 +0200 +++ new/pysaml2-5.3.0/CHANGELOG.md 2020-06-25 19:31:48.000000000 +0200 @@ -1,5 +1,17 @@ # Changelog +## 5.3.0 (2020-06-25) + +- Fix check for nameid_format set to the string "None" in the configuration + + +## 5.2.0 (2020-06-23) + +- Fix presence of empty eIDAS RequestedAttributes element on AuthnRequest +- Refactor create_authn_request method to be easier to reason about +- Fix NameIDPolicy checks for allowed Format and allowCreate values + + ## 5.1.0 (2020-06-09) - support eIDAS RequestedAttributes per AuthnRequest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pysaml2-5.1.0/VERSION new/pysaml2-5.3.0/VERSION --- old/pysaml2-5.1.0/VERSION 2020-06-09 13:03:44.000000000 +0200 +++ new/pysaml2-5.3.0/VERSION 2020-06-25 19:31:48.000000000 +0200 @@ -1 +1 @@ -5.1.0 +5.3.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pysaml2-5.1.0/src/saml2/client_base.py new/pysaml2-5.3.0/src/saml2/client_base.py --- old/pysaml2-5.1.0/src/saml2/client_base.py 2020-06-09 13:03:44.000000000 +0200 +++ new/pysaml2-5.3.0/src/saml2/client_base.py 2020-06-25 19:31:48.000000000 +0200 @@ -14,6 +14,7 @@ from saml2.mdstore import destinations from saml2.profile import paos, ecp +from saml2.saml import NAMEID_FORMAT_PERSISTENT from saml2.saml import NAMEID_FORMAT_TRANSIENT from saml2.samlp import AuthnQuery, RequestedAuthnContext from saml2.samlp import NameIDMappingRequest @@ -90,6 +91,52 @@ pass +def create_requested_attribute_node(requested_attrs, attribute_converters): + items = [] + for attr in requested_attrs: + friendly_name = attr.get('friendly_name') + name = attr.get('name') + name_format = attr.get('name_format') + is_required = str(attr.get('required', False)).lower() + + if not name and not friendly_name: + raise ValueError("Missing required attribute: 'name' or 'friendly_name'") + + if not name: + for converter in attribute_converters: + try: + name = converter._to[friendly_name.lower()] + except KeyError: + continue + else: + if not name_format: + name_format = converter.name_format + break + + if not friendly_name: + for converter in attribute_converters: + try: + friendly_name = converter._fro[name.lower()] + except KeyError: + continue + else: + if not name_format: + name_format = converter.name_format + break + + items.append( + RequestedAttribute( + is_required=is_required, + name_format=name_format, + friendly_name=friendly_name, + name=name, + ) + ) + + node = RequestedAttributes(extension_elements=items) + return node + + class Base(Entity): """ The basic pySAML2 service provider class """ @@ -261,120 +308,90 @@ :return: either a tuple of request ID and <samlp:AuthnRequest> instance or a tuple of request ID and str when sign is set to True """ - client_crt = None - if "client_crt" in kwargs: - client_crt = kwargs["client_crt"] - args = {} - if self.config.getattr('hide_assertion_consumer_service', 'sp'): + # AssertionConsumerServiceURL + # AssertionConsumerServiceIndex + hide_assertion_consumer_service = self.config.getattr('hide_assertion_consumer_service', 'sp') + assertion_consumer_service_url = ( + kwargs.pop("assertion_consumer_service_urls", [None])[0] + or kwargs.pop("assertion_consumer_service_url", None) + ) + assertion_consumer_service_index = kwargs.pop("assertion_consumer_service_index", None) + service_url = (self.service_urls(service_url_binding or binding) or [None])[0] + if hide_assertion_consumer_service: args["assertion_consumer_service_url"] = None binding = None - else: - try: - args["assertion_consumer_service_url"] = kwargs[ - "assertion_consumer_service_urls"][0] - del kwargs["assertion_consumer_service_urls"] - except KeyError: - try: - args["assertion_consumer_service_url"] = kwargs[ - "assertion_consumer_service_url"] - del kwargs["assertion_consumer_service_url"] - except KeyError: - try: - args["assertion_consumer_service_index"] = str( - kwargs["assertion_consumer_service_index"]) - del kwargs["assertion_consumer_service_index"] - except KeyError: - if service_url_binding is None: - service_urls = self.service_urls(binding) - else: - service_urls = self.service_urls(service_url_binding) - args["assertion_consumer_service_url"] = service_urls[0] - - try: - args["provider_name"] = kwargs["provider_name"] - except KeyError: - if binding == BINDING_PAOS: - pass - else: - args["provider_name"] = self._my_name() + elif assertion_consumer_service_url: + args["assertion_consumer_service_url"] = assertion_consumer_service_url + elif assertion_consumer_service_index: + args["assertion_consumer_service_index"] = assertion_consumer_service_index + elif service_url: + args["assertion_consumer_service_url"] = service_url + + # ProviderName + provider_name = kwargs.get("provider_name") + if not provider_name and binding != BINDING_PAOS: + provider_name = self._my_name() + args["provider_name"] = provider_name # Allow argument values either as class instances or as dictionaries # all of these have cardinality 0..1 _msg = AuthnRequest() - for param in ["scoping", "requested_authn_context", "conditions", - "subject"]: - try: - _item = kwargs[param] - except KeyError: - pass + for param in ["scoping", "requested_authn_context", "conditions", "subject"]: + _item = kwargs.pop(param, None) + if not _item: + continue + + if isinstance(_item, _msg.child_class(param)): + args[param] = _item + elif isinstance(_item, dict): + args[param] = RequestedAuthnContext(**_item) else: - del kwargs[param] - # either class instance or argument dictionary - if isinstance(_item, _msg.child_class(param)): - args[param] = _item - elif isinstance(_item, dict): - args[param] = RequestedAuthnContext(**_item) - else: - raise ValueError("%s or wrong type expected %s" % (_item, - param)) - - try: - args["name_id_policy"] = kwargs["name_id_policy"] - del kwargs["name_id_policy"] - except KeyError: - if allow_create is None: - allow_create = self.config.getattr("name_id_format_allow_create", "sp") - if allow_create is None: - allow_create = "false" - else: - if allow_create is True: - allow_create = "true" - else: - allow_create = "false" + raise ValueError("Wrong type for param {name}".format(name=param)) - if nameid_format == "": - name_id_policy = None - else: - if nameid_format is None: - nameid_format = self.config.getattr("name_id_format", "sp") + # NameIDPolicy + nameid_format_config = self.config.getattr("name_id_format", "sp") + nameid_format_config = ( + nameid_format_config[0] + if isinstance(nameid_format_config, list) + else nameid_format_config + ) + nameid_format = ( + nameid_format + if nameid_format is not None + else NAMEID_FORMAT_TRANSIENT + if nameid_format_config is None + else None + if nameid_format_config == 'None' + else nameid_format_config + ) - # If no nameid_format has been set in the configuration - # or passed in then transient is the default. - if nameid_format is None: - # SAML 2.0 errata says AllowCreate MUST NOT be used for - # transient ids - to make a conservative change this is - # only applied for the default cause - allow_create = None - nameid_format = NAMEID_FORMAT_TRANSIENT - - # If a list has been configured or passed in choose the - # first since NameIDPolicy can only have one format specified. - elif isinstance(nameid_format, list): - nameid_format = nameid_format[0] - - # Allow a deployer to signal that no format should be specified - # in the NameIDPolicy by passing in or configuring the string 'None'. - elif nameid_format == 'None': - nameid_format = None + allow_create_config = self.config.getattr("name_id_format_allow_create", "sp") + allow_create = ( + None + # SAML 2.0 errata says AllowCreate MUST NOT be used for transient ids + if nameid_format == NAMEID_FORMAT_TRANSIENT + else allow_create + if allow_create is not None + else str(bool(allow_create_config)).lower() + ) - name_id_policy = samlp.NameIDPolicy(allow_create=allow_create, - format=nameid_format) + name_id_policy = ( + kwargs.pop("name_id_policy", None) + if "name_id_policy" in kwargs + else None + if nameid_format == "" + else samlp.NameIDPolicy(allow_create=allow_create, format=nameid_format) + ) - if name_id_policy and vorg: - try: - name_id_policy.sp_name_qualifier = vorg - name_id_policy.format = saml.NAMEID_FORMAT_PERSISTENT - except KeyError: - pass - args["name_id_policy"] = name_id_policy + if name_id_policy and vorg: + name_id_policy.sp_name_qualifier = vorg + name_id_policy.format = nameid_format or NAMEID_FORMAT_PERSISTENT - try: - nsprefix = kwargs["nsprefix"] - except KeyError: - nsprefix = None + args["name_id_policy"] = name_id_policy + # eIDAS SPType conf_sp_type = self.config.getattr('sp_type', 'sp') conf_sp_type_in_md = self.config.getattr('sp_type_in_metadata', 'sp') if conf_sp_type and conf_sp_type_in_md is False: @@ -383,63 +400,21 @@ item = sp_type.SPType(text=conf_sp_type) extensions.add_extension_element(item) + # eIDAS RequestedAttributes requested_attrs = ( requested_attributes or self.config.getattr('requested_attributes', 'sp') or [] ) - - if not extensions: - extensions = Extensions() - - items = [] - for attr in requested_attrs: - friendly_name = attr.get('friendly_name') - name = attr.get('name') - name_format = attr.get('name_format') - is_required = str(attr.get('required', False)).lower() - - if not name and not friendly_name: - raise ValueError( - "Missing required attribute: '{}' or '{}'".format( - 'name', 'friendly_name' - ) - ) - - if not name: - for converter in self.config.attribute_converters: - try: - name = converter._to[friendly_name.lower()] - except KeyError: - continue - else: - if not name_format: - name_format = converter.name_format - break - - if not friendly_name: - for converter in self.config.attribute_converters: - try: - friendly_name = converter._fro[name.lower()] - except KeyError: - continue - else: - if not name_format: - name_format = converter.name_format - break - - items.append( - RequestedAttribute( - is_required=is_required, - name_format=name_format, - friendly_name=friendly_name, - name=name, - ) + if requested_attrs: + req_attrs_node = create_requested_attribute_node( + requested_attrs, self.config.attribute_converters ) + if not extensions: + extensions = Extensions() + extensions.add_extension_element(req_attrs_node) - item = RequestedAttributes(extension_elements=items) - extensions.add_extension_element(item) - + # ForceAuthn force_authn = str( kwargs.pop("force_authn", None) or self.config.getattr("force_authn", "sp") @@ -452,28 +427,50 @@ AuthnRequest(), extensions, **kwargs ) args.update(_args) - args.pop("id", None) - if sign is None: - sign = self.authn_requests_signed + client_crt = kwargs.get("client_crt") + nsprefix = kwargs.get("nsprefix") + sign = self.authn_requests_signed if sign is None else sign 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 - return self._message(AuthnRequest, destination, message_id, - consent, extensions, sign, sign_prepare, - protocol_binding=binding, - scoping=scoping, nsprefix=nsprefix, - sign_alg=sign_alg, digest_alg=digest_alg, - **args) - return self._message(AuthnRequest, destination, message_id, consent, - extensions, sign, sign_prepare, - protocol_binding=binding, - scoping=scoping, nsprefix=nsprefix, - sign_alg=sign_alg, digest_alg=digest_alg, **args) + msg = self._message( + AuthnRequest, + destination, + message_id, + consent, + extensions, + sign, + sign_prepare, + protocol_binding=binding, + scoping=scoping, + nsprefix=nsprefix, + sign_alg=sign_alg, + digest_alg=digest_alg, + **args, + ) + else: + msg = self._message( + AuthnRequest, + destination, + message_id, + consent, + extensions, + sign, + sign_prepare, + protocol_binding=binding, + scoping=scoping, + nsprefix=nsprefix, + sign_alg=sign_alg, + digest_alg=digest_alg, + **args, + ) + + return msg def create_attribute_query(self, destination, name_id=None, attribute=None, message_id=0, consent=None,
