auth_saml: always add mapping as MappingError details (#47760)

This commit is contained in:
Benjamin Dauvergne 2020-10-15 17:46:22 +02:00
parent d47bc8e1ad
commit 6cd84ac407
2 changed files with 39 additions and 12 deletions

View File

@ -38,8 +38,7 @@ class MappingError(Exception):
details = None
def __init__(self, message, details=None):
if details:
self.details = details
self.details = details or {}
super(MappingError, self).__init__(message)
def __str__(self):
@ -109,6 +108,10 @@ class AuthenticAdapter(DefaultAdapter):
if not isinstance(attribute_mapping, list):
raise MappingError('invalid A2_ATTRIBUTE_MAPPING')
if self.apply_attribute_mapping(user, idp, saml_attributes, attribute_mapping):
user.save()
def apply_attribute_mapping(self, user, idp, saml_attributes, attribute_mapping):
user_modified = False
for mapping in attribute_mapping:
if not isinstance(mapping, dict):
@ -121,21 +124,20 @@ class AuthenticAdapter(DefaultAdapter):
method = getattr(self, 'action_' + action.replace('-', '_'))
except AttributeError:
pass
if not method:
raise MappingError('invalid action %r' % action)
try:
if not method:
raise MappingError('invalid action')
logger.debug('auth_saml: applying provisionning mapping %s', mapping)
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
else:
logger.debug('auth_saml: action mapping %r failed: %s', mapping, e)
if user_modified:
user.save()
logger.warning('auth_saml: mapping action failed: %s', e)
return user_modified
def action_rename(self, user, idp, saml_attributes, mapping):
from_name = mapping.get('from')
@ -157,16 +159,16 @@ class AuthenticAdapter(DefaultAdapter):
raise MappingError('missing saml_attribute key')
if saml_attribute not in saml_attributes:
raise MappingError('unknown saml_attribute', details={'saml_attribute': saml_attribute})
raise MappingError('unknown saml_attribute')
value = saml_attributes[saml_attribute]
return self.set_user_attribute(user, attribute, value)
def set_user_attribute(self, user, attribute, value):
if isinstance(value, list):
if len(value) == 0:
raise MappingError('no value for %s' % attribute, details={'attribute': attribute})
raise MappingError('no value')
if len(value) > 1:
raise MappingError('too many values for %s' % attribute, details={'attribute': attribute})
raise MappingError('too many values', details={'value': value})
value = value[0]
if attribute in ('first_name', 'last_name', 'email', 'username'):
if getattr(user, attribute) != value:

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import re
import pytest
@ -165,6 +165,31 @@ def test_provision_add_role(db, simple_role, action_name):
adapter.provision(user, idp, saml_attributes)
# condition failed, so role should be removed
assert simple_role not in user.roles.all()
user.delete()
# on missing mandatory attribute, no user is created
del saml_attributes['mail']
assert adapter.lookup_user(idp, saml_attributes) is None
# simulate no attribute value
saml_attributes['first_name'] = []
attribute_mapping = [
{
'mandatory': True,
'attribute': 'first_name',
'saml_attribute': 'first_name',
}
]
# fail fast
with pytest.raises(MappingError, match='no value.*first_name'):
adapter.apply_attribute_mapping(user, idp, saml_attributes, attribute_mapping)
# or log a warning
caplog.clear()
del attribute_mapping[0]['mandatory']
adapter.apply_attribute_mapping(user, idp, saml_attributes, attribute_mapping)
assert re.match('.*no value.*first_name', caplog.records[0].message)
def test_login_with_conditionnal_authenticators(db, app, settings, caplog):