auth_saml: update error message on user creation failure (#62930)

This commit is contained in:
Serghei Mihai 2022-03-23 12:49:04 +01:00
parent b6ab4b32aa
commit 968f4fae4f
2 changed files with 36 additions and 20 deletions

View File

@ -21,6 +21,7 @@ from django.contrib import messages
from django.core.exceptions import MultipleObjectsReturned
from django.db.transaction import atomic
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop as N_
from mellon.adapters import DefaultAdapter, UserCreationError
from mellon.utils import get_setting
@ -37,15 +38,17 @@ logger = logging.getLogger('authentic2.auth_saml')
class MappingError(Exception):
details = None
def __init__(self, message, details=None):
self.details = details or {}
super().__init__(message)
def __init__(self, msg, *args, **kwargs):
assert msg % kwargs
self.msg = msg
self.kwargs = kwargs
super().__init__(*args)
def message(self):
return _(self.msg) % self.kwargs
def __str__(self):
s = str(self.args[0])
if self.details:
s += ' ' + repr(self.details)
return s
return N_(self.msg) % self.kwargs
class SamlConditionContextProxy:
@ -76,7 +79,7 @@ class AuthenticAdapter(DefaultAdapter):
except MappingError as e:
logger.warning('auth_saml: user creation failed on a mandatory mapping action, %s', e)
if self.request:
messages.error(self.request, _('user creation failed on a mandatory mapping action: %s') % e)
messages.error(self.request, _('User creation failed: %s.') % e.message())
raise UserCreationError
if not user.ou:
user.ou = get_default_ou()
@ -121,7 +124,7 @@ class AuthenticAdapter(DefaultAdapter):
user_modified = False
for mapping in attribute_mapping:
if not isinstance(mapping, dict):
raise MappingError('invalid mapping action', details={'mapping': mapping})
raise MappingError('invalid mapping action "%(mapping)s"', mapping=mapping)
action = mapping.get('action', 'set-attribute')
mandatory = mapping.get('mandatory', False) is True
method = None
@ -137,7 +140,6 @@ class AuthenticAdapter(DefaultAdapter):
if method(user, idp, saml_attributes, mapping):
user_modified = True
except MappingError as e:
e.details['mapping'] = mapping
if mandatory:
# it's mandatory, provisionning should fail completely
raise e
@ -171,9 +173,13 @@ class AuthenticAdapter(DefaultAdapter):
def set_user_attribute(self, user, attribute, value):
if isinstance(value, list):
if len(value) == 0:
raise MappingError('no value')
raise MappingError('no value for attribute "%(attribute)s"', attribute=attribute)
if len(value) > 1:
raise MappingError('too many values', details={'value': value})
raise MappingError(
'too many values for attribute "%(attribute)s": %(value)s',
attribute=attribute,
value=value,
)
value = value[0]
if attribute in ('first_name', 'last_name', 'email', 'username'):
if getattr(user, attribute) != value:
@ -201,14 +207,14 @@ class AuthenticAdapter(DefaultAdapter):
try:
return OU.objects.get(slug=slug)
except OU.DoesNotExist:
raise MappingError('unknown ou', details={'slug': slug})
raise MappingError('unknown ou: "%(slug)s"', slug=slug)
elif name:
if not isinstance(name, str):
raise MappingError('invalid ou.slug in ou description')
try:
return OU.objects.get(name=name)
except OU.DoesNotExist:
raise MappingError('unknown ou', details={'name': name})
raise MappingError('unknown ou: "%(name)s"', name=name)
else:
raise MappingError('invalid ou description')
@ -226,11 +232,11 @@ class AuthenticAdapter(DefaultAdapter):
if slug:
if not isinstance(slug, str):
raise MappingError('invalid role slug', details={'slug': slug})
raise MappingError('invalid role slug: "%(slug)s"', slug=slug)
kwargs['slug'] = slug
elif name:
if not isinstance(name, str):
raise MappingError('invalid role name', details={'name': name})
raise MappingError('invalid role name: "%(name)s"', name=name)
kwargs['name'] = name
else:
raise MappingError('invalid role description')
@ -238,9 +244,9 @@ class AuthenticAdapter(DefaultAdapter):
try:
return Role.objects.get(**kwargs)
except Role.DoesNotExist:
raise MappingError('unknown role', details=kwargs)
raise MappingError('unknown role: %(kwargs)s', kwargs=kwargs)
except MultipleObjectsReturned:
raise MappingError('ambiuous role description', details=kwargs)
raise MappingError('ambiugous role description: %(kwargs)s', kwargs=kwargs)
def evaluate_condition(self, user, saml_attributes, mapping):
condition = mapping.get('condition')
@ -255,7 +261,7 @@ class AuthenticAdapter(DefaultAdapter):
logger.debug('auth_saml: condition %r is %s', condition, value, extra={'user': user})
return value
except Exception as e:
raise MappingError('condition evaluation failed', details={'error': str(e)})
raise MappingError('condition evaluation failed: %(message)s', message=e)
def action_add_role(self, user, idp, saml_attributes, mapping):
role = self.get_role(mapping)

View File

@ -21,6 +21,7 @@ from unittest import mock
import lasso
import pytest
from django.contrib.auth import get_user_model
from mellon.adapters import UserCreationError
from mellon.models import Issuer, UserSAMLIdentifier
from authentic2.custom_user.models import DeletedUser
@ -138,13 +139,22 @@ def test_apply_attribute_mapping_missing_attribute_logged(
def test_apply_attribute_mapping_missing_attribute_exception(
adapter, idp, saml_attributes, title_attribute, user
adapter, idp, saml_attributes, title_attribute, user, rf
):
saml_attributes['http://fucking/attribute/givenName'] = []
idp['A2_ATTRIBUTE_MAPPING'][-1]['mandatory'] = True
with pytest.raises(MappingError, match='no value'):
adapter.apply_attribute_mapping(user, idp, saml_attributes, idp['A2_ATTRIBUTE_MAPPING'])
request = rf.get('/')
request._messages = mock.Mock()
adapter.request = request
with pytest.raises(UserCreationError):
adapter.finish_create_user(idp, saml_attributes, user)
request._messages.add.assert_called_once_with(
40, 'User creation failed: no value for attribute "first_name".', ''
)
@pytest.mark.parametrize('action_name', ['add-role', 'toggle-role'])
class TestAddRole: