This commit is contained in:
Benjamin Dauvergne 2017-07-07 14:26:43 +02:00
parent af98a203e7
commit bb61f1059e
17 changed files with 1260 additions and 25 deletions

View File

@ -27,7 +27,7 @@ class AppConfig(django.apps.AppConfig):
from django_rbac.models import Operation
from django_rbac.utils import get_operation
FC_MANAGE_OP = Operation(name=u'Gérer les fédérations France Connect',
FC_MANAGE_OP = Operation(name=u'Gérer France Connect',
slug='fc_manage')
get_operation(FC_MANAGE_OP)
@ -46,7 +46,7 @@ class AppConfig(django.apps.AppConfig):
from .utils import update_roles
update_roles()
def get_a2_manager_actions(self, model=None, **kwargs):
def a2_hook_manager_other_actions(self, model=None, **kwargs):
'''Retourne des actions utilisateurs pour la gestion France Connect'''
from django.contrib.auth import get_user_model
from .actions import FranceConnect
@ -55,14 +55,269 @@ class AppConfig(django.apps.AppConfig):
return [FranceConnect]
return []
def get_a2_manager_other_data(self, model=None, **kwargs):
def a2_hook_manager_user_data(self, view, user):
'''Retourne des objets pour afficher la fédération France Connect'''
from django.contrib.auth import get_user_model
from .user_datas import FranceConnectUserData
from .user_datas import FranceConnectUserData, ValidationUserData
user_datas = []
if view.__class__.__name__ == 'UserDetailView':
user_datas.append(
FranceConnectUserData(user, view.request),
)
if user.attributes.validated:
user_datas.append(
ValidationUserData(user)
)
return user_datas
def a2_hook_manager_modify_form(self, view, form):
from django.forms.widgets import DateTimeInput, HiddenInput
from authentic2.passwords import generate_password
from . import models
if view.__class__.__name__ == 'UserAddView':
# Les usagers CUT n'ont pas d'identifiant
ou = getattr(form, 'ou', None)
if ou:
password = generate_password()
form.fields['password1'].initial = password
form.fields['password1'].widget = HiddenInput()
form.fields['password2'].initial = password
form.fields['password2'].widget = HiddenInput()
if ou.slug == 'usagers':
del form.fields['username']
for field_name in ['is_superuser', 'validated',
'validation_context', 'validation_date']:
if field_name in form.fields:
del form.fields[field_name]
form.fields['generate_password'].initial = False
form.fields['generate_password'].widget = HiddenInput()
form.fields['reset_password_at_next_login'].initial = False
form.fields['reset_password_at_next_login'].widget = HiddenInput()
form.fields['send_mail'].initial = False
form.fields['send_mail'].widget = HiddenInput()
form.fields['send_password_reset'].initial = True
form.fields['send_password_reset'].widget = HiddenInput()
else:
for field_name in form.fields:
if field_name not in ['username',
'first_name',
'last_name',
'email',
'generate_password']:
del form.fields[field_name]
form.fields['email'].required = True
form.fields['generate_password'].initial = True
form.fields['generate_password'].widget.attrs = {'readonly': ''}
form.fields['send_password_reset'].initial = True
form.fields['send_password_reset'].widget = HiddenInput()
old_save = form.save
def new_save(*args, **kwargs):
response = old_save(*args, **kwargs)
models.Journal.objects.create(
actor=form.request.user,
subject=form.instance,
message='création du compte')
return response
form.save = new_save
if view.__class__.__name__ == 'UserEditView':
# Les usagers CUT n'ont pas d'identifiant
if form.instance.ou:
if form.instance.ou.slug == 'usagers':
form.fields['email'].required = True
del form.fields['username']
for field_name in ['password1', 'password2', 'is_superuser', 'validated',
'validation_context', 'validation_date']:
if field_name in form.fields:
del form.fields[field_name]
else:
for field_name in form.fields:
if field_name not in ['username',
'first_name',
'last_name',
'email',
'is_superuser',
'generate_password']:
del form.fields[field_name]
form.fields['email'].required = True
old_save = form.save
def new_save(*args, **kwargs):
response = old_save(*args, **kwargs)
models.Journal.objects.create(
actor=form.request.user,
subject=form.instance,
message='modification des données')
return response
form.save = new_save
# Si un compte est validé, on interdit la modification des attributs coeurs
if form.instance.attributes.validated:
for field_name in form.fields:
if field_name in ['first_name',
'last_name',
'birthcountry_insee',
'birthplace_insee',
'birthcountry',
'birthplace',
'gender', 'title',
'birthdate']:
# del form.fields[field_name]
field = form.fields[field_name]
field.required = False
if field_name == 'birthdate':
field.widget = DateTimeInput(attrs={'readonly': ''})
attrs = field.widget.attrs or {}
attrs['disabled'] = ''
attrs['title'] = u'Champ validé'
field.widget.attrs = attrs
def new_clean(self, field_name):
def clean():
self.instance.refresh_from_db()
if hasattr(self.instance, field_name):
return getattr(self.instance, field_name)
else:
return getattr(self.instance.attributes, field_name)
return clean
setattr(form, 'clean_' + field_name, new_clean(form, field_name))
if field_name in ['generate_password']:
del form.fields[field_name]
if view.__class__.__name__ == 'UserDetailView':
if form.instance.ou:
if form.instance.ou.slug == 'usagers':
for field_name in ['username', 'is_superuser', 'validated',
'validation_context', 'validation_date']:
if field_name in form.fields:
del form.fields[field_name]
else:
for field_name in form.fields:
if field_name not in ['username', 'first_name', 'last_name', 'email']:
del form.fields[field_name]
if view.__class__.__name__ in ['OrganizationalUnitEditView', 'OrganizationalUnitAddView']:
del form.fields['default']
del form.fields['email_is_unique']
del form.fields['username_is_unique']
def a2_hook_manager_modify_table(self, view, table):
import django_tables2 as tables
if view.__class__.__name__ == 'UsersView':
ou = view.search_form.cleaned_data['ou']
sequence = list(table.sequence)
if ou and ou.slug == 'usagers':
for column_name in ['username', 'link']:
if column_name in table.base_columns:
del table.base_columns[column_name]
if column_name in sequence:
sequence.remove(column_name)
sequence.remove('email')
sequence.insert(2, 'email')
table.base_columns['preferred_username'] = tables.Column(
accessor='attributes.preferred_username', verbose_name=u'Nom d\'usage')
sequence.insert(2, 'preferred_username')
table.base_columns['validated'] = tables.BooleanColumn(
accessor='attributes.validated', verbose_name=u'Validé')
sequence += ['validated']
else:
del table.base_columns['link']
sequence.remove('link')
table.columns = tables.columns.BoundColumns(table)
table.sequence = sequence
return table
def a2_hook_api_modify_serializer(self, view, original_serializer):
from rest_framework import serializers
if hasattr(original_serializer, 'child'):
serializer = original_serializer.child
else:
serializer = original_serializer
if view.__class__.__name__ == 'UsersAPI':
del serializer.fields['id']
del serializer.fields['uuid']
del serializer.fields['is_superuser']
del serializer.fields['is_staff']
del serializer.fields['password']
serializer.fields['ou'].write_only = True
serializer.fields['ou'].read_only = True
del serializer.fields['username']
del serializer.fields['last_login']
def get_gender(obj):
title = obj.attributes.title
return {'Monsieur': 'male', 'Madame': 'female'}.get(title)
serializer.get_gender = get_gender
serializer.fields['gender'] = serializers.SerializerMethodField()
serializer.fields['sub'] = serializers.UUIDField(read_only=True, source='uuid',
label='IDCut')
serializer.fields['given_name'] = serializers.CharField(read_only=True,
source='first_name')
serializer.fields['family_name'] = serializers.CharField(read_only=True,
source='last_name')
serializer.fields['email_verified'].read_only = True
def a2_hook_modify_context_data(self, view, context):
from .custom_settings import CORE_ATTRIBUTES, CROWN_ATTRIBUTES
if view.__class__.__name__ == 'ProfileView':
context['cut_core_filled'] = all(getattr(view.request.user.attributes, a, None) for a in
CORE_ATTRIBUTES)
context['cut_crown_filled'] = any(getattr(view.request.user.attributes, a, None) for a
in CROWN_ATTRIBUTES)
def a2_hook_manager_modify_other_actions(self, view, other_actions):
from authentic2.manager.views import Action
class CUTValidate(Action):
name = 'cut-validate'
title = 'Valider le compte'
permission = 'custom_user.cut_validate_user'
url_name = 'cut-manager-user-edit-core'
popup = False
def display(self, user, request):
if user.ou and user.ou.slug != 'usagers':
return False
if user.attributes.validated and user.attributes.validation_context == 'fc':
return False
if user.attributes.validated:
self.title = u'Modifier les données coeur'
self.user = user
return super(CUTValidate, self).display(user, request)
class CUTJournalActions(Action):
name = 'cut-journal-actions'
title = 'Journal des actions'
permission = 'custom_user.view_user'
url_name = 'cut-manager-user-actions-journal'
popup = False
def display(self, user, request):
if user.ou and user.ou.slug == 'usagers':
return False
return super(CUTJournalActions, self).display(user, request)
class CUTJournalModifications(Action):
name = 'cut-journal-modifications'
title = 'Journal des modifications'
permission = 'custom_user.view_user'
url_name = 'cut-manager-user-modifications-journal'
popup = False
if view.__class__.__name__ == 'UserDetailView':
other_actions.append(CUTValidate())
other_actions.append(CUTJournalActions())
other_actions.append(CUTJournalModifications())
if issubclass(model, get_user_model()):
return [FranceConnectUserData]
return []
default_app_config = 'authentic2_cut.AppConfig'

View File

@ -1,12 +1,184 @@
# -*- coding: utf-8 -*-
from authentic2.settings import INSTALLED_APPS, CACHES
CORE_ATTRIBUTES = [
'title',
'first_name',
'last_name',
'birthdate',
'birthplace',
'birthcountry'
]
CROWN_ATTRIBUTES = [
'preferred_username',
'preferred_givenname',
'address_number',
'address_street',
'address_complement',
'address_zipcode',
'address_city',
'address_country',
'home_mobile_phone',
'home_phone',
'professional_mobile_phone',
'professional_phone',
'birthdepartment'
]
# Manager
A2_MANAGER_SHOW_INTERNAL_ROLES = True
A2_MANAGER_ROLE_MEMBERS_FROM_OU = True
A2_MANAGER_USER_SEARCH_MINIMUM_CHARS = 2
A2_MANAGER_ROLES_SHOW_PERMISSIONS = True
A2_MANAGER_ROLE_MEMBERS_FROM_OU = True
class RemoveFranceConnect(object):
name = 'remove-franceconnect'
title = 'Supprimer la liaison FranceConnect'
confirm = 'Êtes-vous sûr?'
def do(self, user, request, *args, **kwargs):
from authentic2_auth_fc.models import FcAccount
FcAccount.objects.filter(user=user).delete()
def display(self, user, request):
from authentic2_auth_fc.models import FcAccount
return FcAccount.objects.filter(user=user).exists()
A2_MANAGER_USER_ACTIONS = [RemoveFranceConnect()]
class FranceConnectUserData(object):
def __init__(self, user, request):
self.user = user
self.request = request
def __unicode__(self):
from authentic2_auth_fc.models import FcAccount
try:
FcAccount.objects.get(user=self.user)
return u'Utilisateur relié à un compte FranceConnect'
except FcAccount.DoesNotExist:
return u''
A2_MANAGER_USER_DATA = [FranceConnectUserData]
def modify_user_info(user, user_info, scope_set):
import logging
user_info.clear()
if 'email' in scope_set:
logging.info('givin email')
user_info['email'] = user.email
if 'profile' in scope_set:
logging.info('givin profile')
user_info['first_name'] = user.first_name
user_info['last_name'] = user.last_name
user_info['given_name'] = user.first_name
user_info['family_name'] = user.last_name
user_info['title'] = user.attributes.title
user_info['gender'] = {'Monsieur': 'male', 'Madame': 'female'}.get(user.attributes.title)
user_info['birthdate'] = user.attributes.birthdate and user.attributes.birthdate.isoformat()
user_info['birthplace'] = user.attributes.birthplace
user_info['birthcountry'] = user.attributes.birthcountry
user_info['birthplace_insee'] = user.attributes.birthplace_insee
user_info['birthcountry_insee'] = user.attributes.birthcountry_insee
user_info['validated'] = user.attributes.validated
user_info['validation_date'] = user.attributes.validation_date and user.attributes.validation_date.isoformat()
user_info['validation_context'] = user.attributes.validation_context
if 'crown' in scope_set:
logging.info('giving crown')
user_info['preferred_username'] = user.attributes.preferred_username
user_info['preferred_givenname'] = user.attributes.preferred_givenname
user_info['address_number'] = user.attributes.address_number
user_info['address_street'] = user.attributes.address_street
user_info['address_complement'] = user.attributes.address_complement
user_info['address_zipcode'] = user.attributes.address_zipcode
user_info['address_city'] = user.attributes.address_city
user_info['address_country'] = user.attributes.address_country
user_info['home_mobile_phone'] = user.attributes.home_mobile_phone
user_info['home_phone'] = user.attributes.home_phone
user_info['professional_mobile_phone'] = user.attributes.professional_mobile_phone
user_info['professional_phone'] = user.attributes.professional_phone
user_info['birthdepartment'] = user.attributes.birthdepartment
A2_IDP_OIDC_MODIFY_USER_INFO = modify_user_info
def modify_profile_form(form):
if form.instance and form.instance.attributes.validated:
for field in ('first_name', 'last_name', 'birthdate', 'title',
'birthplace', 'birthcountry'):
form.fields.pop(field, None)
return form
A2_MODIFY_PROFILE_FORM = modify_profile_form
A2_PROFILE_FIELDS = [
u'email',
u'title',
u'last_name',
u'first_name',
u'birthdate',
u'birthplace',
u'birthcountry',
u'preferred_username',
u'preferred_givenname',
u'address_number',
u'address_street',
u'address_complement',
u'address_zipcode',
u'address_city',
u'address_country',
u'home_mobile_phone',
u'home_phone',
u'professional_mobile_phone',
u'professional_phone',
u'birthdepartment',
u'comment',
u'validated',
u'validation_date',
u'validation_context',
]
A2_REGISTRATION_FIELDS = [
u'title',
u'last_name',
u'first_name',
u'birthdate',
u'birthplace',
u'birthcountry',
u'preferred_username',
u'preferred_givenname',
u'address_number',
u'address_street',
u'address_complement',
u'address_zipcode',
u'address_city',
u'address_country',
u'home_mobile_phone',
u'home_phone',
u'professional_mobile_phone',
u'professional_phone',
u'birthdepartment'
]
A2_REQUIRED_FIELDS = ['email', 'first_name', 'last_name']
A2_PRE_REGISTRATION_FIELDS = ['first_name', 'last_name']
A2_RBAC_MANAGED_CONTENT_TYPES = ()
A2_CUT_PARTNERS = [
{
'domains': ['.lyon.fr'],
'name': u'Ville de Lyon',
},
{
'domains': ['.entrouvert.org'],
'name': u'Ville de Lyon',
}
]
@ -22,5 +194,40 @@ if ('Memcached' in CACHES['default']['BACKEND'] or 'Memcached' in
print 'canot load django-cachalot', e
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 48h pour les mails de reset de mot de passe
PASSWORD_RESET_TIMEOUT_DAYS = 2
A2_EMAIL_CHANGE_TOKEN_LIFETIME = 3600 * 24 * 2
ACCOUNT_ACTIVATION_DAYS = 2
# CACHALOT_UNCACHABLE_TABLES = ('custom_user_user', 'django_migrations')
TEMPLATE_VARS = {
'theme': 'grandlyon-cut',
'css_variant': 'grandlyon-cut',
'theme_base': 'theme.html',
'theme_base_filename': 'theme.html',
}
# OIDC
A2_IDP_OIDC_SCOPES = ['openid', 'email', 'profile', 'crown']
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',
}
},
'validated': {'value': True},
'validation_date': {'compute': 'today'},
'validation_context': {'value': 'France Connect'},
}

View File

@ -0,0 +1,444 @@
[
{
"fields" : {
"disabled" : false,
"name" : "first_name",
"user_visible" : true,
"kind" : "string",
"label" : "Prénoms de naissance",
"asked_on_registration" : true,
"description" : "",
"required" : true,
"searchable" : false,
"multiple" : false,
"user_editable" : true
},
"pk" : 1,
"model" : "authentic2.attribute"
},
{
"pk" : 2,
"fields" : {
"user_editable" : true,
"description" : "",
"searchable" : false,
"required" : true,
"multiple" : false,
"user_visible" : true,
"label" : "Nom de naissance",
"kind" : "string",
"asked_on_registration" : true,
"disabled" : false,
"name" : "last_name"
},
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"fields" : {
"required" : false,
"searchable" : false,
"description" : "",
"multiple" : false,
"user_editable" : true,
"name" : "title",
"disabled" : false,
"kind" : "title",
"label" : "Civilité",
"user_visible" : true,
"asked_on_registration" : true
},
"pk" : 3
},
{
"model" : "authentic2.attribute",
"fields" : {
"user_editable" : true,
"multiple" : false,
"required" : false,
"searchable" : false,
"description" : "",
"asked_on_registration" : true,
"label" : "Complément d'adresse",
"kind" : "string",
"user_visible" : true,
"name" : "address_complement",
"disabled" : false
},
"pk" : 4
},
{
"model" : "authentic2.attribute",
"pk" : 5,
"fields" : {
"multiple" : false,
"searchable" : false,
"required" : false,
"description" : "",
"user_editable" : true,
"name" : "address_zipcode",
"disabled" : false,
"asked_on_registration" : true,
"kind" : "string",
"label" : "Code postal",
"user_visible" : true
}
},
{
"model" : "authentic2.attribute",
"pk" : 6,
"fields" : {
"multiple" : false,
"description" : "",
"required" : false,
"searchable" : false,
"user_editable" : true,
"disabled" : false,
"name" : "address_city",
"asked_on_registration" : true,
"user_visible" : true,
"kind" : "string",
"label" : "Nom de la commune"
}
},
{
"pk" : 7,
"fields" : {
"multiple" : false,
"description" : "",
"searchable" : false,
"required" : false,
"user_editable" : true,
"disabled" : false,
"name" : "address_country",
"asked_on_registration" : true,
"user_visible" : true,
"kind" : "string",
"label" : "Pays"
},
"model" : "authentic2.attribute"
},
{
"fields" : {
"description" : "",
"required" : false,
"searchable" : false,
"multiple" : false,
"user_editable" : true,
"disabled" : false,
"name" : "birthdate",
"user_visible" : true,
"label" : "Date de naissance",
"kind" : "date",
"asked_on_registration" : true
},
"pk" : 8,
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"pk" : 9,
"fields" : {
"name" : "home_phone",
"disabled" : false,
"asked_on_registration" : true,
"label" : "Téléphone fixe personnel",
"kind" : "string",
"user_visible" : true,
"multiple" : false,
"searchable" : true,
"required" : false,
"description" : "",
"user_editable" : true
}
},
{
"model" : "authentic2.attribute",
"fields" : {
"name" : "home_mobile_phone",
"disabled" : false,
"asked_on_registration" : true,
"kind" : "string",
"label" : "Téléphone mobile personnel",
"user_visible" : true,
"multiple" : false,
"required" : false,
"searchable" : true,
"description" : "",
"user_editable" : true
},
"pk" : 10
},
{
"fields" : {
"user_editable" : false,
"multiple" : false,
"description" : "",
"required" : false,
"searchable" : false,
"asked_on_registration" : false,
"user_visible" : false,
"kind" : "boolean",
"label" : "Validé",
"disabled" : false,
"name" : "validated"
},
"pk" : 11,
"model" : "authentic2.attribute"
},
{
"pk" : 12,
"fields" : {
"disabled" : false,
"name" : "birthplace",
"user_visible" : true,
"kind" : "string",
"label" : "Lieu de naissance",
"asked_on_registration" : true,
"description" : "",
"searchable" : false,
"required" : false,
"multiple" : false,
"user_editable" : true
},
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"fields" : {
"user_editable" : true,
"multiple" : false,
"description" : "",
"searchable" : false,
"required" : false,
"asked_on_registration" : true,
"user_visible" : true,
"kind" : "string",
"label" : "Pays de naissance",
"disabled" : false,
"name" : "birthcountry"
},
"pk" : 13
},
{
"model" : "authentic2.attribute",
"pk" : 14,
"fields" : {
"user_visible" : true,
"kind" : "string",
"label" : "Prénoms d'usage",
"asked_on_registration" : true,
"disabled" : false,
"name" : "preferred_givenname",
"user_editable" : true,
"description" : "",
"required" : false,
"searchable" : true,
"multiple" : false
}
},
{
"fields" : {
"multiple" : false,
"description" : "",
"required" : false,
"searchable" : true,
"user_editable" : true,
"disabled" : false,
"name" : "preferred_username",
"asked_on_registration" : true,
"user_visible" : true,
"kind" : "string",
"label" : "Nom d'usage"
},
"pk" : 15,
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"pk" : 16,
"fields" : {
"name" : "birthdepartment",
"disabled" : false,
"asked_on_registration" : true,
"label" : "Département de naissance",
"kind" : "string",
"user_visible" : true,
"multiple" : false,
"required" : false,
"searchable" : false,
"description" : "",
"user_editable" : true
}
},
{
"model" : "authentic2.attribute",
"pk" : 17,
"fields" : {
"multiple" : false,
"searchable" : false,
"required" : false,
"description" : "",
"user_editable" : true,
"name" : "address_street",
"disabled" : false,
"asked_on_registration" : true,
"kind" : "string",
"label" : "Nom de la voie",
"user_visible" : true
}
},
{
"pk" : 18,
"fields" : {
"name" : "address_number",
"disabled" : false,
"kind" : "string",
"label" : "Numéro sur la voie",
"user_visible" : true,
"asked_on_registration" : true,
"required" : false,
"searchable" : false,
"description" : "",
"multiple" : false,
"user_editable" : true
},
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"pk" : 19,
"fields" : {
"disabled" : false,
"name" : "professional_mobile_phone",
"user_visible" : true,
"kind" : "string",
"label" : "Téléphone mobile professionnel",
"asked_on_registration" : true,
"description" : "",
"searchable" : true,
"required" : false,
"multiple" : false,
"user_editable" : true
}
},
{
"model" : "authentic2.attribute",
"fields" : {
"user_editable" : true,
"searchable" : true,
"required" : false,
"description" : "",
"multiple" : false,
"label" : "Téléphone fixe professionnel",
"kind" : "string",
"user_visible" : true,
"asked_on_registration" : true,
"name" : "professional_phone",
"disabled" : false
},
"pk" : 20
},
{
"model" : "authentic2.attribute",
"fields" : {
"user_editable" : false,
"description" : "",
"searchable" : false,
"required" : false,
"multiple" : false,
"user_visible" : false,
"kind" : "string",
"label" : "Commentaire",
"asked_on_registration" : false,
"disabled" : false,
"name" : "comment"
},
"pk" : 21
},
{
"fields" : {
"kind" : "string",
"label" : "Contexte de validation",
"user_visible" : false,
"asked_on_registration" : false,
"name" : "validation_context",
"disabled" : false,
"user_editable" : false,
"required" : false,
"searchable" : false,
"description" : "FranceConnect, Online request, Office qui seront traduits en FranceConnect, Demande en ligne, En guichet.",
"multiple" : false
},
"pk" : 24,
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"pk" : 25,
"fields" : {
"multiple" : false,
"searchable" : false,
"required" : false,
"description" : "",
"user_editable" : false,
"name" : "validation_date",
"disabled" : false,
"asked_on_registration" : false,
"kind" : "date",
"label" : "Date de validation",
"user_visible" : false
}
},
{
"pk" : 26,
"fields" : {
"asked_on_registration" : false,
"kind" : "string",
"label" : "Lieu de naissance (code INSEE)",
"user_visible" : false,
"name" : "birthplace_insee",
"disabled" : false,
"user_editable" : false,
"multiple" : false,
"searchable" : false,
"required" : false,
"description" : ""
},
"model" : "authentic2.attribute"
},
{
"model" : "authentic2.attribute",
"fields" : {
"user_visible" : false,
"label" : "Pays de naissance (code INSEE)",
"kind" : "string",
"asked_on_registration" : false,
"disabled" : false,
"name" : "birthcountry_insee",
"user_editable" : false,
"description" : "",
"searchable" : false,
"required" : false,
"multiple" : false
},
"pk" : 27
},
{
"pk" : 28,
"fields" : {
"asked_on_registration" : false,
"user_visible" : false,
"kind" : "string",
"label" : "Genre",
"disabled" : false,
"name" : "gender",
"user_editable" : false,
"multiple" : false,
"description" : "",
"required" : false,
"searchable" : false
},
"model" : "authentic2.attribute"
}
]

View File

@ -1,5 +1,6 @@
from django.conf import settings
import urlparse
import logging
class CUTMiddleware(object):
@ -32,7 +33,13 @@ class CUTMiddleware(object):
netloc = urlparse.urlparse(url).netloc
if netloc:
domain = netloc.split(':')[-1]
if domain:
if not domain:
domain = request.session.get('cut_domain')
if not domain:
request.partner = None
else:
if domain not in self.MATCHES:
for partner_def in getattr(settings, 'A2_CUT_PARTNERS', []):
patterns = partner_def.get('domains', [])
@ -52,11 +59,11 @@ class CUTMiddleware(object):
# sometimes
self.MATCHES[domain] = None
request.session['cut_domain'] = None
else:
domain = request.session.get('cut_domain')
if domain and domain in self.MATCHES:
request.partner = self.MATCHES[domain]
else:
request.partner = None
return None
if domain and domain in self.MATCHES:
request.partner = self.MATCHES[domain]
if request.session.get('cut_domain') != domain:
request.session['cut_domain'] = domain
else:
request.partner = None
return None

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Journal',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='Horodatage', db_index=True)),
('message', models.TextField(verbose_name=b'Message')),
('actor', models.ForeignKey(related_name='actor_journal', verbose_name='Auteur', to=settings.AUTH_USER_MODEL)),
('subject', models.ForeignKey(related_name='subject_journal', verbose_name=b'Sujet', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ('-timestamp', '-id'),
'verbose_name': 'historique',
'verbose_name_plural': 'historiques',
},
),
]

View File

@ -0,0 +1,24 @@
from django.db import models
from django.conf import settings
class Journal(models.Model):
timestamp = models.DateTimeField(
verbose_name=u'Horodatage',
db_index=True,
auto_now_add=True)
actor = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=u'Auteur',
related_name='actor_journal')
subject = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name='Sujet',
related_name='subject_journal')
message = models.TextField(
verbose_name='Message')
class Meta:
verbose_name = 'historique'
verbose_name_plural = 'historiques'
ordering = ('-timestamp', '-id')

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext_lazy as _
import django_tables2 as tables
from .models import Journal
class UserActionsTable(tables.Table):
class Meta:
attrs = {'class': 'main'}
model = Journal
exclude = ['id', 'actor']
empty_text = _('None')
class UserModificationsTable(tables.Table):
class Meta:
attrs = {'class': 'main'}
model = Journal
exclude = ['id', 'subject']
empty_text = _('None')

View File

@ -0,0 +1 @@
{% extends "authentic2/accounts_edit.html %}

View File

@ -0,0 +1 @@
{% extends "authentic2/accounts_edit.html %}

View File

@ -0,0 +1,18 @@
{% extends "authentic2/manager/base.html" %}
{% load i18n staticfiles django_tables2 %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'a2-manager-users' %}{% if object.ou %}?search-ou={{ object.ou.pk }}{% endif %}">{% trans 'Users' %}{% if object.ou %}&nbsp;: {{ object.ou }}{% endif %}</a>
<a href="{% url 'a2-manager-user-detail' pk=object.pk %}">{{ object.get_full_name }}</a>
<a href="#">Journal des actions</a>
{% endblock %}
{% block page_title %}
{% trans 'User' %} - {{ object }}x
{% endblock %}
{% block content %}
{% render_table table "authentic2/manager/table.html" %}
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "authentic2/manager/user_edit.html" %}
{% block appbar %}
<h2>{{ title }}</h2>
{% endblock %}
{% block content %}
<ul class="messages">
<li class="info">
<p>Vous devez demander à l'usager de présenter une pièce d'identité pour valider son compte ou
modifier des données coeur déjà validées.</p>
<p>Si l'usager est mineur vous devez demander une attestation d'un tuteur légal.</p>
</li>
</ul>
{{ block.super }}
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "authentic2/manager/base.html" %}
{% load i18n staticfiles django_tables2 %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'a2-manager-users' %}{% if object.ou %}?search-ou={{ object.ou.pk }}{% endif %}">{% trans 'Users' %}{% if object.ou %}&nbsp;: {{ object.ou }}{% endif %}</a>
<a href="{% url 'a2-manager-user-detail' pk=object.pk %}">{{ object.get_full_name }}</a>
<a href="#">Journal des modifications</a>
{% endblock %}
{% block page_title %}
{% trans 'User' %} - {{ object }}x
{% endblock %}
{% block content %}
{% render_table table "authentic2/manager/table.html" %}
{% endblock %}

View File

@ -1,5 +1,5 @@
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2016 Entr'ouvert
# Copyright (C) 2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
@ -14,9 +14,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import patterns, url, include
from django.conf.urls import patterns, url
# from . import views
from . import views
urlpatterns = patterns('',
urlpatterns = patterns(
'',
url('^accounts/edit-core/$', views.edit_core, name='cut-edit-core'),
url('^accounts/edit-crown/$', views.edit_crown, name='cut-edit-crown'),
url('^manage/users/(?P<pk>\d+)/edit-core/$', views.manager_user_edit_core,
name='cut-manager-user-edit-core'),
url('^manage/users/(?P<pk>\d+)/actions-journal/$', views.user_actions_journal,
name='cut-manager-user-actions-journal'),
url('^manage/users/(?P<pk>\d+)/modifications-journal/$', views.user_modifications_journal,
name='cut-manager-user-modifications-journal'),
)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2016 Entr'ouvert
#
@ -27,3 +28,21 @@ class FranceConnectUserData(object):
return u'Utilisateur relié à un compte FranceConnect'
except FcAccount.DoesNotExist:
return u''
class ValidationUserData(object):
def __init__(self, user):
self.user = user
def __unicode__(self):
context_map = {
'FC': 'par FranceConnect',
'office': 'au guichet',
'online': 'en ligne',
}
validation_context = self.user.attributes.validation_context
validation_date = self.user.attributes.validation_date
return u'Compte validé {0} le {1}'.format(
context_map.get(validation_context, validation_context),
validation_date.strftime('%d/%m/%Y'),
)

View File

@ -3,14 +3,17 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django_rbac.utils import get_ou_model, get_role_model, get_operation, get_permission_model
from django_rbac.models import CHANGE_OP, SEARCH_OP, ADD_OP, VIEW_OP, DELETE_OP, ADMIN_OP
from authentic2.a2_rbac.models import CHANGE_PASSWORD_OP, RESET_PASSWORD_OP, ACTIVATE_OP
from django_rbac.models import CHANGE_OP, SEARCH_OP, ADD_OP, VIEW_OP, DELETE_OP, ADMIN_OP, Operation
from authentic2.a2_rbac.models import RESET_PASSWORD_OP, ACTIVATE_OP
OU = get_ou_model()
Role = get_role_model()
Permission = get_permission_model()
User = get_user_model()
CUT_VALIDATE_OP = Operation(name='Valider', slug='cut_validate')
ROLE_TEMPLATES = [
{
'name': u'Administrateur CUT - création',
@ -64,7 +67,7 @@ ROLE_TEMPLATES = [
{
'name': u'Administrateur CUT - validation',
'slug': '_a2-cut-validate',
'operations': [],
'operations': [CUT_VALIDATE_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
@ -120,7 +123,10 @@ ROLE_TEMPLATES = [
'role_parents': [
'_a2-cut-admin-users',
'_a2-cut-admin-roles'
]
],
'operations': [ADMIN_OP],
'target': 'ou_ct',
'scope': 'no_scope',
},
]
@ -153,10 +159,13 @@ def update_roles():
ct_ct = ContentType.objects.get_for_model(ContentType)
user_ct = ContentType.objects.get_for_model(User)
role_ct = ContentType.objects.get_for_model(Role)
ou_ct = ContentType.objects.get_for_model(OU)
roles = {}
def handle_ou(ou, ou_usagers, ou_territoire, user_ct, role_ct, **kwargs):
def handle_ou(ou, ou_usagers, ou_territoire, user_ct, role_ct, ou_ct, **kwargs):
ou_ct = ContentType.objects.get_for_model(OU)
no_scope = None
if ou.slug == 'usagers':
return
for tpl in ROLE_TEMPLATES:
@ -194,6 +203,13 @@ def update_roles():
if 'role_parents' in tpl:
for role_parent in tpl['role_parents']:
role.add_parent(roles[(ou, role_parent)])
if tpl['name'] == 'Administrateur':
op = get_operation(ADMIN_OP)
permission, created = Permission.objects.get_or_create(
operation=op,
target_ct=ct_ct,
target_id=ou_ct.pk)
role.permissions.add(permission)
slugs = [tpl['slug'] for tpl in ROLE_TEMPLATES]
Role.objects.filter(slug__startswith='_a2-cut').exclude(slug__in=slugs).delete()
handle_ou(ou_territoire, **vars())

131
src/authentic2_cut/views.py Normal file
View File

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.timezone import now
from django.contrib import messages
from django.contrib.auth import get_user_model
from authentic2.views import EditProfile
from authentic2.manager.views import SimpleSubTableView
from authentic2.manager.user_views import UserEditView
from .custom_settings import CORE_ATTRIBUTES
from . import tables, models
class EditCoreView(EditProfile):
template_names = ['authentic2/cut-edit-core.html']
fields = [
'first_name',
'last_name',
'title',
'birthdate',
'birthplace',
'birthcountry',
]
@classmethod
def get_fields(cls):
fields, labels = super(EditCoreView, cls).get_fields()
return [field for field in fields if field in cls.fields], labels
edit_core = EditCoreView.as_view()
class EditCrownView(EditProfile):
template_names = ['authentic2/cut-edit-crown.html']
fields = [
'first_name',
'last_name',
'title',
'birthdate',
'birthplace',
'birthcountry',
]
@classmethod
def get_fields(cls):
fields, labels = super(EditCrownView, cls).get_fields()
return [field for field in fields if field not in cls.fields], labels
edit_crown = EditCrownView.as_view()
class UserEditCoreView(UserEditView):
template_name = 'authentic2/cut_manager_user_edit_core.html'
permissions = ['custom_user.cut_validate_user']
def get_title(self):
if self.object.attributes.validated:
return u'Modifier les attributs cœurs'
return u'Valider les attributs cœurs'
def get_fields(self):
fields = super(UserEditCoreView, self).get_fields()
return [field for field in CORE_ATTRIBUTES if field in fields]
def get_form(self, *args, **kwargs):
form = super(UserEditCoreView, self).get_form(*args, **kwargs)
for field in form.fields.values():
field.required = True
if 'birthdate' in form.fields:
form.fields['birthdate'].widget.options['endDate'] = '+0d'
return form
def form_valid(self, form):
response = super(UserEditCoreView, self).form_valid(form)
if not form.instance.attributes.validated:
models.Journal.objects.create(
author=self.request.user,
subject=self.object,
message='validation du compte')
form.instance.attributes.validated = True
form.instance.attributes.validation_context = 'office'
form.instance.attributes.validation_date = now().date()
messages.info(self.request, u'Le compte a été validé.')
else:
models.Journal.objects.create(
author=self.request.user,
subject=self.object,
message='modification des données cœur')
messages.info(self.request, u'Les données cœur ont été modifié.')
return response
manager_user_edit_core = UserEditCoreView.as_view()
class UserActionsJournal(SimpleSubTableView):
model = get_user_model()
table_class = tables.UserActionsTable
template_name = 'authentic2/cut_manager_user_actions_journal.html'
def get_table_queryset(self):
return self.object.actor_journal.all()
user_actions_journal = UserActionsJournal.as_view()
class UserModificationsJournal(SimpleSubTableView):
model = get_user_model()
table_class = tables.UserModificationsTable
template_name = 'authentic2/cut_manager_user_modifications_journal.html'
def get_table_queryset(self):
return self.object.subject_journal.all()
user_modifications_journal = UserModificationsJournal.as_view()