auth_saml: allow provisionning all attributes from SAML attributs (fixes #10599)

A new mellon setting named A2_ATTRIBUTE_MAPPING must be used, whose syntax is:

    MELLON_A2_ATTRIBUTE_MAPPING = [
        {
            'attribute': 'email',
            'saml_attribute': 'mail',  # value from the Name attribute of the saml:Attribute node
            'mandatory': True,  # optional boolean, if True and attribute is missing, SSO will be refused
        },
    ]
This commit is contained in:
Benjamin Dauvergne 2016-05-02 10:07:43 +02:00
parent e7248a8011
commit e178961566
2 changed files with 90 additions and 2 deletions

View File

@ -1,9 +1,57 @@
import logging
from mellon.adapters import DefaultAdapter
from django.contrib.auth import get_user_model
from mellon.utils import get_setting
class AuthenticAdapter(DefaultAdapter):
def create_user(self, user_class):
return user_class.objects.create()
def finish_create_user(self, idp, saml_attributes, user):
pass
'''Copy incoming SAML attributes to user attributes, A2_ATTRIBUTE_MAPPING must be a list of
dictinnaries like:
{
'attribute': 'email',
'saml_attribute': 'email',
# optional:
'mandatory': False,
}
If an attribute is not mandatory any error is just logged, if the attribute is
mandatory, login will fail.
'''
log = logging.getLogger(__name__)
attribute_mapping = get_setting(idp, 'A2_ATTRIBUTE_MAPPING', [])
for mapping in attribute_mapping:
attribute = mapping['attribute']
saml_attribute = mapping['saml_attribute']
mandatory = mapping.get('mandatory', False)
if not saml_attributes.get(saml_attribute):
if mandatory:
log.error('mandatory saml attribute %r is missing', saml_attribute,
extra={'attributes': repr(saml_attributes)})
raise ValueError('missing attribute')
else:
continue
try:
value = saml_attributes[saml_attribute]
self.set_user_attribute(user, attribute, value)
except Exception, e:
log.error(u'failed to set attribute %r from saml attribute %r with value %r: %s',
attribute, saml_attribute, value, e,
extra={'attributes': repr(saml_attributes)})
if mandatory:
raise
def set_user_attribute(self, user, attribute, value):
if isinstance(value, list):
if len(value) > 1:
raise ValueError('too much values')
value = value[0]
if attribute in ('first_name', 'last_name', 'email', 'username'):
setattr(user, attribute, value)
else:
setattr(user.attributes, attribute, value)

40
tests/test_auth_saml.py Normal file
View File

@ -0,0 +1,40 @@
import pytest
from django.contrib.auth import get_user_model
from authentic2.models import Attribute
pytestmark = pytest.mark.django_db
def test_provision_attributes():
from authentic2_auth_saml.adapters import AuthenticAdapter
adapter = AuthenticAdapter()
User = get_user_model()
Attribute.objects.create(kind='title', name='title', label='title')
user = User.objects.create()
idp = {
'A2_ATTRIBUTE_MAPPING': [
{
'attribute': 'email',
'saml_attribute': 'mail',
'mandatory': True,
},
{
'attribute': 'title',
'saml_attribute': 'title',
},
]
}
saml_attributes = {
u'mail': u'john.doe@example.com',
u'title': u'Mr.',
}
adapter.finish_create_user(idp, saml_attributes, user)
assert user.email == 'john.doe@example.com'
assert user.attributes.title == 'Mr.'
del saml_attributes['mail']
with pytest.raises(ValueError):
adapter.finish_create_user(idp, saml_attributes, user)