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:
parent
e7248a8011
commit
e178961566
|
@ -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)
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue