From f98ccb2242f88c11311c4a04b340c885524a66ed Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Tue, 2 Apr 2019 18:24:03 +0200
Subject: [PATCH] python3: define a base64 decoding exception (#31180)

---
 src/authentic2/compat.py         | 7 +++++++
 src/authentic2/crypto.py         | 4 +++-
 src/authentic2/profile_views.py  | 4 ++--
 src/authentic2_idp_oidc/views.py | 3 ++-
 tests/test_auth_oidc.py          | 3 ++-
 tests/test_idp_saml2.py          | 5 +++--
 6 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/src/authentic2/compat.py b/src/authentic2/compat.py
index 4e36de6d..67455314 100644
--- a/src/authentic2/compat.py
+++ b/src/authentic2/compat.py
@@ -6,6 +6,7 @@ from django.conf import settings
 from django.db import connection
 from django.db.utils import OperationalError
 from django.core.exceptions import ImproperlyConfigured
+from django.utils import six
 
 from django.contrib.auth.tokens import PasswordResetTokenGenerator
 
@@ -121,3 +122,9 @@ else:
                 jsonfield.fields.configure_database_connection(connection, **kwargs)
         jsonfield.fields.connection_created.disconnect(jsonfield.fields.configure_database_connection)
         jsonfield.fields.connection_created.connect(configure_database_connection)
+
+
+if six.PY2:
+    Base64Error = TypeError
+else:
+    from binascii import Error as Base64Error
diff --git a/src/authentic2/crypto.py b/src/authentic2/crypto.py
index 5cb468ca..313cd945 100644
--- a/src/authentic2/crypto.py
+++ b/src/authentic2/crypto.py
@@ -8,6 +8,8 @@ from Crypto.Hash import SHA256
 from Crypto.Hash import HMAC
 from Crypto import Random
 
+from authentic2.compat import Base64Error
+
 
 class DecryptionError(Exception):
     pass
@@ -52,7 +54,7 @@ def aes_base64_decrypt(key, payload, raise_on_error=True):
     try:
         iv = base64.b64decode(iv)
         crypted = base64.b64decode(crypted)
-    except TypeError:
+    except Base64Error:
         if raise_on_error:
             raise DecryptionError('incorrect base64 encoding')
         return None
diff --git a/src/authentic2/profile_views.py b/src/authentic2/profile_views.py
index 8739ced9..3a145c85 100644
--- a/src/authentic2/profile_views.py
+++ b/src/authentic2/profile_views.py
@@ -7,7 +7,7 @@ from django.http import Http404
 from django.utils.translation import ugettext as _
 from django.utils.http import urlsafe_base64_decode
 
-from .compat import default_token_generator
+from .compat import default_token_generator, Base64Error
 from .registration_backend.forms import SetPasswordForm
 from . import app_settings, cbv, profile_forms, utils, hooks
 
@@ -73,7 +73,7 @@ class PasswordResetConfirmView(cbv.RedirectToNextURLViewMixin, FormView):
             uid = urlsafe_base64_decode(uidb64)
             # use authenticate to eventually get an LDAPUser
             self.user = authenticate(user=UserModel._default_manager.get(pk=uid))
-        except (TypeError, ValueError, OverflowError,
+        except (Base64Error, ValueError, OverflowError,
                 UserModel.DoesNotExist):
             validlink = False
             messages.warning(request, _('User not found'))
diff --git a/src/authentic2_idp_oidc/views.py b/src/authentic2_idp_oidc/views.py
index af3242b3..406e2c1a 100644
--- a/src/authentic2_idp_oidc/views.py
+++ b/src/authentic2_idp_oidc/views.py
@@ -15,6 +15,7 @@ from django.contrib import messages
 from django.conf import settings
 from django.utils.translation import ugettext as _
 
+from authentic2.compat import Base64Error
 from authentic2.decorators import setting_enabled
 from authentic2.utils import (login_require, redirect, timestamp_from_datetime,
                               last_authentication_event, make_url)
@@ -328,7 +329,7 @@ def authenticate_client(request, client=None):
             return None
         try:
             decoded = base64.b64decode(authorization[1])
-        except TypeError:
+        except Base64Error:
             return None
         parts = decoded.split(':')
         if len(parts) != 2:
diff --git a/tests/test_auth_oidc.py b/tests/test_auth_oidc.py
index 05392540..5e7c735e 100644
--- a/tests/test_auth_oidc.py
+++ b/tests/test_auth_oidc.py
@@ -25,12 +25,13 @@ from authentic2.models import AttributeValue
 from authentic2.utils import timestamp_from_datetime, last_authentication_event
 from authentic2.a2_rbac.utils import get_default_ou
 from authentic2.crypto import base64url_encode
+from authentic2.compat import Base64Error
 
 import utils
 
 
 def test_base64url_decode():
-    with pytest.raises(TypeError):
+    with pytest.raises(Base64Error):
         base64url_decode('x')
     base64url_decode('aa')
 
diff --git a/tests/test_idp_saml2.py b/tests/test_idp_saml2.py
index 59c89af1..050521d4 100644
--- a/tests/test_idp_saml2.py
+++ b/tests/test_idp_saml2.py
@@ -13,6 +13,7 @@ from django.utils import six
 from django.utils.translation import gettext as _
 from django.utils.six.moves.urllib import parse as urlparse
 
+from authentic2.compat import Base64Error
 from authentic2.saml import models as saml_models
 from authentic2.a2_rbac.models import Role, OrganizationalUnit
 from authentic2.utils import make_url
@@ -292,7 +293,7 @@ class SamlSSOTestCase(SamlBaseTestCase):
             saml_response = doc.forms[0].fields['SAMLResponse']
             try:
                 base64.b64decode(saml_response)
-            except TypeError:
+            except Base64Error:
                 self.fail('SAMLResponse is not base64 encoded: %s'
                           % saml_response)
             with self.assertRaises(lasso.ProfileRequestDeniedError):
@@ -336,7 +337,7 @@ class SamlSSOTestCase(SamlBaseTestCase):
             saml_response = doc.forms[0].fields['SAMLResponse']
             try:
                 base64.b64decode(saml_response)
-            except TypeError:
+            except Base64Error:
                 self.fail('SAMLResponse is not base64 encoded: %s' % saml_response)
             login = self.parse_authn_response(saml_response)
             assertion = login.assertion
-- 
2.20.1

