improve mapping of FC attributes to A2 attributes (#10062)

This commit is contained in:
Benjamin Dauvergne 2017-03-07 11:29:57 +01:00
parent 74aadc0508
commit f0a7266451
5 changed files with 167 additions and 22 deletions

45
README
View File

@ -73,3 +73,48 @@ fc_data_dic = {
[FD_name, data],
],
}
Attribute mapping
=================
You can map France Connect attributes to Authentic2 attributes through the
setting A2_FC_USER_INFO_MAPPINGS. A2_FC_USER_INFO_MAPPINGS is a dictionnary
whose keys are authentic2's attribute names and value can be France Connect
attribute names or dictionnary with the following keys:
- `value` : a static value which will be assigned to the authentic2 attribute,
can be any Python value,
- `ref` : the name of a France Connect attribute,
- `translation` : a transformation name among:
- @insee-communes@ : translate the value using mapping from INSEE code of
communes to their name,
- @insee-countries@ : translate the value using mapping from INSEE code of
countries to their name,
- @simple@ : lookup the value using the dictionnary in @translation_simple@.
- `compute`: compute a value using a known function, only known function for now
is @today@ which returns @datetime.date.today()@.
- `verified`: set the verified flag on the value.
Exemple:
A2_FC_USER_INFO_MAPPINGS = {
'first_name': 'given_name',
'last_name': 'family_name',
'birthdate': { 'ref': 'birthdate', 'translation': 'isodate' },
'birthplace': { 'ref': 'birthplace', 'translation': 'insee-communes' },
'birthcountry': { 'ref': 'birthcountry', 'translation': 'insee-countries' },
'birthplace_insee': 'birthplace',
'birthcountry_insee': 'birthcountry',
'title': {
'ref': 'gender',
'translation': 'simple',
'translation_simple': {
'male': 'Monsieur',
'female': 'Madame',
}
},
'gender': 'gender',
'validated': { 'value': True },
'validation_date': { 'compute': 'today' },
'validation_context': { 'value': 'France Connect' },
}

View File

@ -58,9 +58,19 @@ class AppSettings(object):
@property
def attributes_mapping(self):
return self._setting('ATTRIBUTES_MAPPING',
{'family_name': 'last_name',
'given_name': 'first_name',
'email': 'email'})
{
'family_name': 'last_name',
'given_name': 'first_name',
'email': 'email'
})
@property
def user_info_mappings(self):
return self._setting('USER_INFO_MAPPINGS', {
'last_name': 'family_name',
'first_name': 'given_name',
'email': 'email',
})
@property
def next_field_name(self):

View File

@ -1,42 +1,48 @@
import json
import logging
from . import models, app_settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from . import models, app_settings, utils
logger = logging.getLogger(__name__)
class FcBackend(ModelBackend):
def authenticate(self, sub=None, **kwargs):
user_info = kwargs.get('user_info')
user = None
try:
fc_account = models.FcAccount.objects.get(sub=sub, user__is_active=True)
msg = 'existing user {} using sub {}'.format(fc_account.user, sub)
logger.debug(msg)
return fc_account.user
user = fc_account.user
except models.FcAccount.DoesNotExist:
logger.debug('user with the sub {} not existing.'.format(sub))
if app_settings.create and 'user_info' in kwargs:
User = get_user_model()
user_info = kwargs['user_info']
user = User.objects.create(
first_name=user_info['given_name'],
last_name=user_info['family_name'],
)
fc_account = models.FcAccount.objects.create(
user=user,
sub=sub,
token=json.dumps(kwargs['token']))
msg = 'user creation enabled ' \
'(given_name : {} - family_name : {}) ' \
'with fc_account (sub : {} - token : {})'.format(
user_info['given_name'],
user_info['family_name'],
if user_info:
if not user and app_settings.create:
User = get_user_model()
user = User.objects.create()
fc_account = models.FcAccount.objects.create(
user=user,
sub=sub,
token=json.dumps(kwargs['token']))
msg = 'user creation enabled with fc_account (sub : {} - token : {})'.format(
sub,
json.dumps(kwargs['token'])
)
logger.debug(msg)
if not user:
return None
msg = 'updated (given_name : {} - family_name : {}) '.format(
user_info['given_name'],
user_info['family_name'],
)
user.first_name = user_info['given_name']
user.last_name = user_info['family_name']
logger.debug(msg)
utils.apply_user_info_mappings(user, user_info)
return user
def get_saml2_authn_context(self):

View File

@ -1,4 +1,8 @@
import urllib
import logging
import os
import json
import datetime
from django.core.urlresolvers import reverse
@ -37,3 +41,82 @@ def get_mapped_attributes_flat(request):
if fc_name in request.session['fc-user_info']:
values[local_name] = request.session['fc-user_info'][fc_name]
return values
def mapping_to_value(mapping, user_info):
if isinstance(mapping, basestring):
value = user_info[mapping]
elif 'ref' in mapping:
value = user_info[mapping['ref']]
elif 'value' in mapping:
value = mapping['value']
elif 'compute' in mapping:
if mapping['compute'] == 'today':
value = datetime.date.today()
else:
raise NotImplementedError
if 'translation' in mapping:
if mapping['translation'] == 'insee-communes':
value = resolve_insee_commune(value)
elif mapping['translation'] == 'insee-countries':
value = resolve_insee_country(value)
elif mapping['translation'] == 'isodate':
value = datetime.datetime.strptime(value, '%Y-%m-%d').date()
elif mapping['translation'] == 'simple':
value = mapping['translation_simple'].get(
value, mapping.get('translation_simple_default', ''))
else:
raise NotImplementedError
return value
_insee_communes = None
def resolve_insee_commune(insee_code):
global _insee_communes
if not _insee_communes:
_insee_communes = json.load(
open(
os.path.join(
os.path.dirname(__file__), 'insee-communes.json')))
return _insee_communes.get(insee_code, 'Code insee inconnu')
_insee_countries = None
def resolve_insee_country(insee_code):
global _insee_countries
if not _insee_countries:
_insee_countries = json.load(
open(
os.path.join(
os.path.dirname(__file__), 'insee-countries.json')))
return _insee_countries.get(insee_code, 'Code insee inconnu')
def apply_user_info_mappings(user, user_info):
assert user
assert user_info
logger = logging.getLogger(__name__)
mappings = app_settings.user_info_mappings
for attribute, mapping in mappings.iteritems():
try:
value = mapping_to_value(mapping, user_info)
except (ValueError, KeyError, NotImplementedError) as e:
logger.warning(u'auth_fc: cannot apply mapping %s <- %r: %s', attribute, mapping, e)
continue
if hasattr(user.attributes, attribute):
if getattr(mapping, 'verified', False):
setattr(user.verified_attributes, attribute, value)
else:
setattr(user.attributes, attribute, value)
elif hasattr(user, 'attribute'):
setattr(user, attribute, value)
else:
logger.warning(u'auth_fc: unknown attribut in user_info mapping: %s', attribute)

View File

@ -284,6 +284,7 @@ class LoginOrLinkView(PopupViewMixin, FcOAuthSessionViewMixin, View):
def update_user_info(self):
self.fc_account.user_info = json.dumps(self.user_info)
self.fc_account.save()
utils.apply_user_info_mappings(self.fc_account.user, self.user_info)
self.logger.debug('updating user_info %s', self.fc_account.user_info)
def get(self, request, *args, **kwargs):