From aba208fe3d9991854609c339e63e313acd9d48b5 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sun, 26 Apr 2020 22:04:47 +0200 Subject: [PATCH 2/2] idp_saml2: convert dumps to unicode for uniformity (#41235) --- src/authentic2/idp/saml/saml2_endpoints.py | 28 ++++---- src/authentic2/saml/fields.py | 74 ++++++++-------------- 2 files changed, 41 insertions(+), 61 deletions(-) diff --git src/authentic2/idp/saml/saml2_endpoints.py src/authentic2/idp/saml/saml2_endpoints.py index 08f879d8..321d47f0 100644 --- src/authentic2/idp/saml/saml2_endpoints.py +++ src/authentic2/idp/saml/saml2_endpoints.py @@ -280,7 +280,7 @@ def add_attributes(request, assertion, provider): def saml2_add_attribute_values(assertion, attributes): if attributes: logger.debug('adding attributes') - logger.debug('assertion before processing %s', assertion.dump()) + logger.debug('assertion before processing %s', force_text(assertion.dump())) logger.debug(u'adding attributes %s', attributes) if not assertion.attributeStatement: assertion.attributeStatement = [lasso.Saml2AttributeStatement()] @@ -328,7 +328,7 @@ def saml2_add_attribute_values(assertion, attributes): attribute_value.any = [text_node] attribute_value_list.append(attribute_value) attribute.attributeValue = attribute_value_list - logger.debug('assertion after processing %s', assertion.dump()) + logger.debug('assertion after processing %s', force_text(assertion.dump())) def build_assertion(request, login, nid_format='transient'): @@ -383,7 +383,7 @@ def build_assertion(request, login, nid_format='transient'): # service providers can outlive the IdP session. expiry_date = request.session.get_expiry_date() assertion.authnStatement[0].sessionNotOnOrAfter = datetime_to_xs_datetime(expiry_date) - logger.debug('assertion building in progress %s', assertion.dump()) + logger.debug('assertion building in progress %s', force_text(assertion.dump())) fill_assertion(request, login.request, assertion, login.remoteProviderId, nid_format) # Save federation and new session if nid_format == 'persistent': @@ -515,7 +515,7 @@ def sso(request): if (name_id_policy and name_id_policy.format and name_id_policy.format != lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED): - logger.debug('nameID policy is %s', name_id_policy.dump()) + logger.debug('nameID policy is %s', force_text(name_id_policy.dump())) nid_format = saml2_urn_to_nidformat(name_id_policy.format, accepted=policy.accepted_name_id_format) logger.debug('nameID format %s', nid_format) default_nid_format = policy.default_name_id_format @@ -544,7 +544,7 @@ def need_login(request, login, nid_format, service): the login form was submitted """ nonce = login.request.id or get_nonce() - save_key_values(nonce, login.dump(), False, nid_format) + save_key_values(nonce, force_text(login.dump()), False, nid_format) next_url = make_url(continue_sso, params={NONCE_FIELD_NAME: nonce}) logger.debug('redirect to login page with next url %s', next_url) return login_require(request, next_url=next_url, params={NONCE_FIELD_NAME: nonce}, @@ -558,7 +558,7 @@ def get_url_with_nonce(request, function, nonce): def need_consent_for_federation(request, login, nid_format): nonce = login.request.id or get_nonce() - save_key_values(nonce, login.dump(), False, nid_format) + save_key_values(nonce, force_text(login.dump()), False, nid_format) display_name = None try: provider = \ @@ -601,7 +601,7 @@ def continue_sso(request): server = create_server(request) # Work Around for lasso < 2.3.6 login_dump = login_dump.replace('', '') - login = lasso.Login.newFromDump(server, login_dump) + login = lasso.Login.newFromDump(server, force_str(login_dump)) logger.debug('login newFromDump done') if not login: return error_page(request, _('continue_sso: error loading login'), logger=logger) @@ -633,7 +633,7 @@ def needs_persistence(nid_format): def get_extensions(profile): if not profile.request.extensions: return [] - xml_string = force_text(profile.request.extensions.dump()) + xml_string = force_str(profile.request.extensions.dump()) element = ctree.fromstring(xml_string) return list(element) @@ -825,13 +825,13 @@ def sso_after_process_request(request, login, consent_obtained=False, logger.debug('ask the user consent now') return need_consent_for_federation(request, login, nid_format) - logger.debug('login dump before processing %s', login.dump()) + logger.debug('login dump before processing %s', force_text(login.dump())) try: if needs_persistence(nid_format): logger.debug('load identity dump') load_federation(request, get_entity_id(request, reverse(metadata)), login, user) login.validateRequestMsg(not user.is_anonymous(), consent_obtained) - logger.debug('validateRequestMsg %s', login.dump()) + logger.debug('validateRequestMsg %s', force_text(login.dump())) except lasso.LoginRequestDeniedError: logger.warning('access denied due to LoginRequestDeniedError') set_saml2_response_responder_status_code(login.response, lasso.SAML2_STATUS_CODE_REQUEST_DENIED) @@ -1016,7 +1016,7 @@ def finish_slo(request): messages.warning(request, _('request has expired')) return utils.redirect(request, 'auth_homepage') server = create_server(request) - logout = lasso.Logout.newFromDump(server, logout_dump) + logout = lasso.Logout.newFromDump(server, force_str(logout_dump)) load_provider(request, logout.remoteProviderId, server=logout.server) # Clean all session all_sessions = \ @@ -1340,7 +1340,7 @@ def slo(request): provider_id=logout.remoteProviderId, django_session_key=request.session.session_key).delete() # Save some values for cleaning up - save_key_values(logout.request.id, logout.dump(), request.session.session_key) + save_key_values(logout.request.id, force_text(logout.dump()), request.session.session_key) # Use the logout view and come back to the finish slo view next_url = make_url(finish_slo, params={'id': logout.request.id}) @@ -1428,7 +1428,7 @@ def idp_slo(request, provider_id=None): return redirect_next(request, next) or ko_icon(request) return process_logout_response(request, logout, soap_response, next) else: - save_key_values(logout.request.id, logout.dump(), provider_id, next) + save_key_values(logout.request.id, force_text(logout.dump()), provider_id, next) return HttpResponseRedirect(logout.msgUrl) @@ -1461,7 +1461,7 @@ def slo_return(request): except KeyError: return error_redirect(request, N_('unknown relay state %r'), relay_state, default_url=icon_url('ko')) server = create_server(request) - logout = lasso.Logout.newFromDump(server, logout_dump) + logout = lasso.Logout.newFromDump(server, force_str(logout_dump)) provider_id = logout.remoteProviderId # forced to reset signature_verify_hint as it is not saved in the dump provider = load_provider(request, provider_id, server=server) diff --git src/authentic2/saml/fields.py src/authentic2/saml/fields.py index 77b04cee..440e2591 100644 --- src/authentic2/saml/fields.py +++ src/authentic2/saml/fields.py @@ -14,28 +14,37 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import base64 try: import cPickle as pickle except ImportError: import pickle -import six from django import forms from django.db import models -from django.db.models.lookups import Exact, In from django.core.exceptions import ValidationError +from django.utils import six from django.utils.text import capfirst -from django.utils.encoding import force_bytes, force_str +from django.utils.encoding import force_bytes, force_text from django.contrib.humanize.templatetags.humanize import apnumber from django.template.defaultfilters import pluralize def loads(value): - return pickle.loads(force_bytes(str(value))) + # value is always an unicode string + value = force_bytes(value) + try: + value = base64.b64decode(value) + except ValueError: + # there can be old values which are just pickle + pass + return pickle.loads(value) def dumps(value): - return force_str(pickle.dumps(value, protocol=0)) + return PickledObject( + force_text( + base64.b64encode(pickle.dumps(value, protocol=0)))) # This is a copy of http://djangosnippets.org/snippets/513/ # @@ -50,65 +59,36 @@ def dumps(value): # Initial author: Oliver Beattie -class PickledObject(str): +class PickledObject(six.text_type): """A subclass of string so it can be told whether a string is a pickled object or not (if the object is an instance of this class then it must [well, should] be a pickled one).""" pass -try: - # make PickledObject compatible with Postgres - import psycopg2.extensions - psycopg2.extensions.register_adapter(PickledObject, psycopg2.extensions.QuotedString) -except ImportError: - pass - - -def do_pickle(value): - if value is not None and not isinstance(value, PickledObject): - value = PickledObject(dumps(value)) - return value - class PickledObjectField(models.Field): - def to_python(self, value): - if isinstance(value, PickledObject): - # If the value is a definite pickle; and an error is raised in - # de-pickling it should be allowed to propogate. - return loads(value) - else: - return loads(value) + if value is not None: + value = loads(value) + return value def from_db_value(self, value, expression, connection, context): return self.to_python(value) - def get_db_prep_save(self, value, connection): - return do_pickle(value) + def get_prep_value(self, value): + if value is not None and not isinstance(value, PickledObject): + value = dumps(value) + return value def get_internal_type(self): return 'TextField' - def value_to_string(self, obj): - return PickledObject(dumps(obj)) - - -class PickledObjectFieldExact(Exact): - - def get_db_prep_lookup(self, value, connection): - value = do_pickle(value) - return super(PickledObjectFieldExact, self).get_db_prep_lookup(value, connection) - - -class PickledObjectFieldIn(In): - - def get_db_prep_lookup(self, value, connection): - value = [do_pickle(v) for v in value] - return super(PickledObjectField, self).get_db_prep_lookup(value, connection) - + def get_lookup(self, lookup_name): + """ + We need to limit the lookup types. + """ + raise TypeError('Lookup type %s is not supported.' % lookup_name) -PickledObjectField.register_lookup(PickledObjectFieldExact) -PickledObjectField.register_lookup(PickledObjectFieldIn) # This is a modified copy of http://djangosnippets.org/snippets/1200/ # -- 2.26.0