improve mapping of FC attributes to A2 attributes (#10062)
This commit is contained in:
parent
74aadc0508
commit
f0a7266451
45
README
45
README
|
@ -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' },
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue