Implémentation des backends pr@tic d'authentification
- X509 utilisateur - X509 collectivité + login/password - collectivité/login/mot de passe La page d'accueil du manager a été adapté: - pour un super-administrateur on montre tout - pour un administrateur de collectivité on montre directement des liens profonds vers la gestion des utilisateurs, des instances de service et des accès pour sa collectivité.
This commit is contained in:
parent
81813ab91a
commit
8a4205b4bc
|
@ -13,3 +13,6 @@ class Plugin(object):
|
|||
|
||||
def get_auth_frontends(self):
|
||||
return ['authentic2_pratic.auth_frontends.PraticFrontend']
|
||||
|
||||
def get_after_middleware(self):
|
||||
return ('authentic2_pratic.middleware.PraticAuthMiddleware',)
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
class AppSettings(object):
|
||||
__DEFAULTS = {
|
||||
'X509_KEYS': {
|
||||
'subject_dn': 'SSL_CLIENT_S_DN',
|
||||
'issuer_dn': 'SSL_CLIENT_I_DN',
|
||||
'serial': ('SSL_CLIENT_M_SERIAL', 'SSL_CLIENT_SERIAL'),
|
||||
'cert': 'SSL_CLIENT_CERT',
|
||||
'verify': 'SSL_CLIENT_VERIFY',
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, prefix):
|
||||
|
|
|
@ -1,21 +1,68 @@
|
|||
from django.utils.translation import gettext_noop
|
||||
from django import forms
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
|
||||
from django.shortcuts import render
|
||||
from django.conf import settings
|
||||
|
||||
from authentic2.constants import NONCE_FIELD_NAME
|
||||
from authentic2.models import AuthenticationEvent
|
||||
|
||||
from . import constants, forms, models
|
||||
|
||||
class PraticFrontend(object):
|
||||
def enabled(self):
|
||||
True
|
||||
|
||||
def name(self):
|
||||
return gettext_noop('Pr@tic')
|
||||
|
||||
def id(self):
|
||||
return 'pratic'
|
||||
|
||||
def form(self):
|
||||
return forms.Form
|
||||
def name(self):
|
||||
return gettext_noop('Pr@tic')
|
||||
|
||||
def post(self, request, form, nonce, next):
|
||||
raise NotImplemented
|
||||
|
||||
def template(self):
|
||||
return 'authentic2_pratic/login.html'
|
||||
def login(self, request, *args, **kwargs):
|
||||
next_url = request.GET.get(REDIRECT_FIELD_NAME) or settings.LOGIN_REDIRECT_URL
|
||||
nonce = request.GET.get(NONCE_FIELD_NAME,'')
|
||||
# if there is only one ssl user, autolog him
|
||||
# only autologin every 10 minutes
|
||||
user = None
|
||||
how = None
|
||||
autologin = False
|
||||
if constants.PRATIC_AUTOLOGIN_COOKIE_NAME not in request.COOKIES:
|
||||
if hasattr(request, 'ssl_info') and request.ssl_info and \
|
||||
len(request.ssl_info.users) == 1:
|
||||
user = authenticate(ssl_user=request.ssl_info.users[0])
|
||||
how = 'ssl'
|
||||
autologin = True
|
||||
# login SSL users by selection
|
||||
if not user and request.method == 'POST' and 'ssl-user' in request.POST \
|
||||
and hasattr(request, 'ssl_info') and request.ssl_info:
|
||||
try:
|
||||
ssl_user_id = int(request.POST['ssl-user'])
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
ssl_user = request.ssl_info.users.get(pk=ssl_user_id)
|
||||
except models.User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
user = authenticate(ssl_user=ssl_user)
|
||||
how = 'ssl'
|
||||
# now try to login using collectivity/login/password
|
||||
form = forms.AuthenticationForm(request=request, data=request.POST or None)
|
||||
if not user and request.method == 'POST' and form.is_valid():
|
||||
how = form.get_how()
|
||||
if how == 'password' and request.is_secure():
|
||||
how += '-on-https'
|
||||
user = form.get_user()
|
||||
if user and how:
|
||||
login(request, user)
|
||||
AuthenticationEvent.objects.create(who=user.username,
|
||||
how=how, nonce=nonce)
|
||||
response = HttpResponseRedirect(next_url)
|
||||
if autologin:
|
||||
response.set_cookie(constants.PRATIC_AUTOLOGIN_COOKIE_NAME,
|
||||
max_age=constants.PRATIC_AUTOLOGIN_COOKIE_MAX_AGE)
|
||||
return response
|
||||
return render(request, 'authentic2_pratic/login.html',
|
||||
{'form': form})
|
||||
|
|
|
@ -1,8 +1,54 @@
|
|||
import logging
|
||||
|
||||
class PraticBackend(object):
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
def authenticate(self, **kwargs):
|
||||
raise NotImplemented
|
||||
from . import models, constants
|
||||
|
||||
class BaseBackend(ModelBackend):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
super(BaseBackend, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
return models.User._default_manager.get(pk=user_id)
|
||||
except models.User.DoesNotExist:
|
||||
return None
|
||||
|
||||
class PraticLoginPasswordBackend(BaseBackend):
|
||||
def authenticate(self, collectivity, username, password, **kwargs):
|
||||
try:
|
||||
user = models.User.objects.select_related().get(collectivity=collectivity, uid=username)
|
||||
except models.User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
if user.check_password(password):
|
||||
return user
|
||||
|
||||
def get_saml2_authn_context(self, **kwargs):
|
||||
import lasso
|
||||
request = kwargs.pop('request', None)
|
||||
if request and request.is_secure():
|
||||
return lasso.SAML2_AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT
|
||||
else:
|
||||
return lasso.SAML2_AUTHN_CONTEXT_PASSWORD
|
||||
|
||||
class PraticSSLBackend(BaseBackend):
|
||||
def authenticate(self, ssl_user, **kwargs):
|
||||
return ssl_user
|
||||
|
||||
def get_saml2_authn_context(self, **kwargs):
|
||||
import lasso
|
||||
return lasso.SAML2_AUTHN_CONTEXT_X509
|
||||
|
||||
class PraticLoginPasswordSSLBackend(PraticLoginPasswordBackend):
|
||||
def authenticate(self, collectivity, username, password, ssl_info):
|
||||
user = super(PraticLoginPasswordSSLBackend, self).authenticate(collectivity, username, password)
|
||||
if user:
|
||||
col = user.collectivity
|
||||
if col.certificate_issuer_dn == ssl_info.issuer_dn and \
|
||||
col.certificate_subject_dn == ssl_info.subject_dn:
|
||||
return user
|
||||
|
||||
def get_saml2_authn_context(self, **kwargs):
|
||||
return constants.PRATIC_AUTHN_CONTEXT_SSL_COLLECTIVITY
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
PRATIC_AUTOLOGIN_COOKIE_NAME = 'pratic-autologin'
|
||||
PRATIC_AUTOLOGIN_COOKIE_MAX_AGE = 60*10 # 10 minutes
|
||||
PRATIC_AUTHN_CONTEXT_SSL_COLLECTIVITY = 'urn:cdg59.fr:names:tc:SAML:2.0:ac:classes:X509Collectivity'
|
|
@ -1,4 +1,11 @@
|
|||
import re
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
|
||||
from . import models
|
||||
|
||||
|
@ -10,7 +17,29 @@ class ServiceForm(BaseForm):
|
|||
class Meta:
|
||||
model = models.Service
|
||||
|
||||
class CollectivityForm(BaseForm):
|
||||
class CertificateMixin(forms.ModelForm):
|
||||
certificate = forms.FileField(label=_('X509 Certificate'), help_text=_('You can set '
|
||||
'the issuer and subject DNs by uploading the certificate'), required=False)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CertificateMixin, self).clean()
|
||||
certificate = cleaned_data.get('certificate')
|
||||
if certificate is not None:
|
||||
with tempfile.NamedTemporaryFile() as certificate_file:
|
||||
certificate_file.write(certificate.read())
|
||||
certificate_file.flush()
|
||||
description = subprocess.check_output(['/usr/bin/openssl', 'x509', '-text', '-in', certificate_file.name])
|
||||
match = re.findall(r'(?:Issuer|Subject): (.*)', description)
|
||||
if len(match) == 2:
|
||||
cleaned_data['certificate_issuer_dn'] = '/' + match[0].replace(', ', '/')
|
||||
cleaned_data['certificate_subject_dn'] = '/' + match[1].replace(', ', '/')
|
||||
if not cleaned_data.get('certificate_issuer_dn') or \
|
||||
not cleaned_data.get('certificate_subject_dn'):
|
||||
cleaned_data['certificate_issuer_dn'] = None
|
||||
cleaned_data['certificate_subject_dn'] = None
|
||||
return cleaned_data
|
||||
|
||||
class CollectivityForm(CertificateMixin, BaseForm):
|
||||
class Meta:
|
||||
model = models.Collectivity
|
||||
|
||||
|
@ -29,7 +58,7 @@ class AccessForm(BaseForm):
|
|||
class Meta:
|
||||
model = models.Access
|
||||
|
||||
class UserForm(BaseForm):
|
||||
class UserForm(CertificateMixin, BaseForm):
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = (
|
||||
|
@ -43,6 +72,92 @@ class UserForm(BaseForm):
|
|||
'postal_address',
|
||||
'fax',
|
||||
'mobile',
|
||||
'phone'
|
||||
|
||||
'phone',
|
||||
'certificate_issuer_dn',
|
||||
'certificate_subject_dn',
|
||||
'certificate',
|
||||
)
|
||||
|
||||
class AuthenticationForm(forms.Form):
|
||||
how = None
|
||||
|
||||
collectivity = forms.ModelChoiceField(queryset=models.Collectivity.objects,
|
||||
label=_('Collectivity'))
|
||||
username = forms.CharField(label=_('Username'))
|
||||
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput)
|
||||
|
||||
error_messages = {
|
||||
'invalid_login': _("Please enter a correct username and password. "
|
||||
"Note that both fields may be case-sensitive."),
|
||||
'inactive': _("This account is inactive."),
|
||||
}
|
||||
|
||||
def __init__(self, request=None, *args, **kwargs):
|
||||
"""
|
||||
The 'request' parameter is set for custom auth use by subclasses.
|
||||
The form data comes in via the standard 'data' kwarg.
|
||||
"""
|
||||
self.request = request
|
||||
self.user_cache = None
|
||||
super(AuthenticationForm, self).__init__(*args, **kwargs)
|
||||
if request and hasattr(request, 'ssl_info') and request.ssl_info \
|
||||
and request.ssl_info.collectivity:
|
||||
self.fields['collectivity'].initial = request.ssl_info.collectivity.pk
|
||||
|
||||
def clean(self):
|
||||
collectivity = self.cleaned_data.get('collectivity')
|
||||
username = self.cleaned_data.get('username')
|
||||
password = self.cleaned_data.get('password')
|
||||
|
||||
if username and password and collectivity:
|
||||
if self.request and hasattr(self.request, 'ssl_info') and self.request.ssl_info:
|
||||
self.user_cache = authenticate(collectivity=collectivity,
|
||||
username=username,
|
||||
password=password,
|
||||
ssl_info=self.request.ssl_info)
|
||||
if self.user_cache:
|
||||
self.how = 'ssl-collectivity'
|
||||
else:
|
||||
self.user_cache = authenticate(collectivity=collectivity,
|
||||
username=username,
|
||||
password=password)
|
||||
if self.user_cache:
|
||||
self.how = 'password'
|
||||
if self.user_cache is None:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['invalid_login'],
|
||||
code='invalid_login',
|
||||
params={'username': ugettext('username')},
|
||||
)
|
||||
else:
|
||||
self.confirm_login_allowed(self.user_cache)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def confirm_login_allowed(self, user):
|
||||
"""
|
||||
Controls whether the given User may log in. This is a policy setting,
|
||||
independent of end-user authentication. This default behavior is to
|
||||
allow login by active users, and reject login by inactive users.
|
||||
|
||||
If the given user cannot log in, this method should raise a
|
||||
``forms.ValidationError``.
|
||||
|
||||
If the given user may log in, this method should return None.
|
||||
"""
|
||||
if not user.is_active:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['inactive'],
|
||||
code='inactive',
|
||||
)
|
||||
|
||||
def get_user_id(self):
|
||||
if self.user_cache:
|
||||
return self.user_cache.id
|
||||
return None
|
||||
|
||||
def get_user(self):
|
||||
return self.user_cache
|
||||
|
||||
def get_how(self):
|
||||
return self.how
|
||||
|
|
|
@ -1,32 +1,70 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
# Authentic2 Pr@tic French Locale
|
||||
# Copyright (C) 2014 CDG59
|
||||
# This file is distributed under the same license as the authentic2-pratic package.
|
||||
# Benjamin Dauvergne <bdauvergne@entrouvert.com>, 2014.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Project-Id-Version: authentic2-pratic 1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-10-30 17:20+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"POT-Creation-Date: 2014-11-19 14:17+0100\n"
|
||||
"PO-Revision-Date: 2014-11-19 14:17+0100\n"
|
||||
"Last-Translator: Benjamin Dauvergne <bdauvergne@entrouvert.com>\n"
|
||||
"Language-Team: France <fr@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: auth_frontends.py:9 dashboard.py:8
|
||||
#: auth_frontends.py:20 dashboard.py:8
|
||||
msgid "Pr@tic"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:20 models.py:92 tables.py:36 tables.py:49
|
||||
#: forms.py:21
|
||||
msgid "X509 Certificate"
|
||||
msgstr "Certificat X509 au format PEM"
|
||||
|
||||
#: forms.py:21
|
||||
msgid "You can set the issuer and subject DNs by uploading the certificate"
|
||||
msgstr ""
|
||||
"Vous pouvez aussi définir les noms distingués du certificat en le "
|
||||
"téléchargeant."
|
||||
|
||||
#: forms.py:85
|
||||
msgid "Collectivity"
|
||||
msgstr "Collectivité"
|
||||
|
||||
#: forms.py:86
|
||||
msgid "Username"
|
||||
msgstr "Identifiant"
|
||||
|
||||
#: forms.py:87
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: forms.py:90
|
||||
msgid ""
|
||||
"Please enter a correct username and password. Note that both fields may be "
|
||||
"case-sensitive."
|
||||
msgstr ""
|
||||
"Veuillez entrer un identifiant et un mot de passe corrects. Remarquez que "
|
||||
"chacun de ces champs est sensible à la casse (différenciation des majuscules/"
|
||||
"minuscules)."
|
||||
|
||||
#: forms.py:92
|
||||
msgid "This account is inactive."
|
||||
msgstr "Ce compte est inactif"
|
||||
|
||||
#: forms.py:130
|
||||
msgid "username"
|
||||
msgstr "identifiant"
|
||||
|
||||
#: models.py:20 models.py:107 tables.py:37 tables.py:50
|
||||
msgid "identifier"
|
||||
msgstr "identifiant"
|
||||
|
||||
#: models.py:24 models.py:212 models.py:279
|
||||
#: models.py:24 models.py:243 models.py:312
|
||||
msgid "collectivity"
|
||||
msgstr "collectivité"
|
||||
|
||||
|
@ -34,7 +72,7 @@ msgstr "collectivité"
|
|||
msgid "is admin"
|
||||
msgstr "Administrateur?"
|
||||
|
||||
#: models.py:32 models.py:106 models.py:111
|
||||
#: models.py:32 models.py:121 models.py:126
|
||||
msgid "SIRH Code"
|
||||
msgstr ""
|
||||
|
||||
|
@ -50,11 +88,11 @@ msgstr "durée de la dernière connexion"
|
|||
msgid "employee type"
|
||||
msgstr "type de poste"
|
||||
|
||||
#: models.py:53 models.py:129
|
||||
#: models.py:53 models.py:144
|
||||
msgid "postal address"
|
||||
msgstr "Adresse postale"
|
||||
|
||||
#: models.py:57 models.py:189
|
||||
#: models.py:57 models.py:204
|
||||
msgid "fax"
|
||||
msgstr ""
|
||||
|
||||
|
@ -62,223 +100,245 @@ msgstr ""
|
|||
msgid "mobile"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:67 models.py:184
|
||||
#: models.py:67 models.py:199
|
||||
msgid "phone"
|
||||
msgstr "téléphone"
|
||||
|
||||
#: models.py:72
|
||||
#: models.py:71 models.py:218
|
||||
msgid "certificate issuer DN"
|
||||
msgstr "nom distingué de l'émetteur du certificat"
|
||||
|
||||
#: models.py:76 models.py:223
|
||||
msgid "certificate subject DN"
|
||||
msgstr "nom distingué du sujet du certificat"
|
||||
|
||||
#: models.py:85
|
||||
msgid "agent"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:73
|
||||
#: models.py:86
|
||||
msgid "agents"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:88 models.py:221 tables.py:10 tables.py:23
|
||||
#: models.py:103 models.py:254 tables.py:11 tables.py:24
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: models.py:96
|
||||
#: models.py:111
|
||||
msgid "is superuser"
|
||||
msgstr "Tous les utilisateurs sont des super-administrateurs?"
|
||||
|
||||
#: models.py:101
|
||||
#: models.py:116
|
||||
msgid "collectivity id"
|
||||
msgstr "identifiant de la collectivité"
|
||||
|
||||
#: models.py:116
|
||||
#: models.py:131
|
||||
msgid "INSEE Code"
|
||||
msgstr "code INSEE"
|
||||
|
||||
#: models.py:121
|
||||
#: models.py:136
|
||||
msgid "SIRET Code"
|
||||
msgstr "code SIRET"
|
||||
|
||||
#: models.py:133
|
||||
#: models.py:148
|
||||
msgid "street number"
|
||||
msgstr "numéro dans la rue"
|
||||
|
||||
#: models.py:138
|
||||
#: models.py:153
|
||||
msgid "street"
|
||||
msgstr "rue"
|
||||
|
||||
#: models.py:143
|
||||
#: models.py:158
|
||||
msgid "postal code"
|
||||
msgstr "code postal"
|
||||
|
||||
#: models.py:148
|
||||
#: models.py:163
|
||||
msgid "complementary address"
|
||||
msgstr "adresse complémentaire"
|
||||
|
||||
#: models.py:153
|
||||
#: models.py:168
|
||||
msgid "address mention"
|
||||
msgstr "adresse (mention complémentaire)"
|
||||
|
||||
#: models.py:158
|
||||
#: models.py:173
|
||||
msgid "arrondissement code"
|
||||
msgstr "code d'arrondissement"
|
||||
|
||||
#: models.py:163
|
||||
#: models.py:178
|
||||
msgid "canton code"
|
||||
msgstr "numéro de canton"
|
||||
|
||||
#: models.py:168
|
||||
#: models.py:183
|
||||
msgid "departement code"
|
||||
msgstr "numéro de département"
|
||||
|
||||
#: models.py:173 models.py:178
|
||||
#: models.py:188 models.py:193
|
||||
msgid "distribution office"
|
||||
msgstr "centre de distribution du courrier"
|
||||
|
||||
#: models.py:194
|
||||
#: models.py:209
|
||||
msgid "email"
|
||||
msgstr "courriel"
|
||||
|
||||
#: models.py:199 models.py:233 models.py:281
|
||||
#: models.py:214 models.py:266 models.py:314
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:213
|
||||
#: models.py:244
|
||||
msgid "collectivities"
|
||||
msgstr "collectivités"
|
||||
|
||||
#: models.py:229
|
||||
#: models.py:262
|
||||
msgid "is global"
|
||||
msgstr "service global?"
|
||||
|
||||
#: models.py:235 models.py:284
|
||||
#: models.py:268 models.py:317
|
||||
msgid "SAML Metadata URL"
|
||||
msgstr "URL des métadonnées SAML"
|
||||
|
||||
#: models.py:238 models.py:287
|
||||
#: models.py:271 models.py:320
|
||||
msgid "OAuth2 URL"
|
||||
msgstr "URL du point d'accès OAuth2"
|
||||
|
||||
#: models.py:241 models.py:290
|
||||
#: models.py:274 models.py:323
|
||||
msgid "OAuth2 Key"
|
||||
msgstr "Clé OAuth2"
|
||||
|
||||
#: models.py:254 models.py:276
|
||||
#: models.py:287 models.py:309
|
||||
msgid "service"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:255
|
||||
#: models.py:288
|
||||
msgid "services"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:308
|
||||
#: models.py:341
|
||||
msgid "There can be only one instance of a global service by collectivity"
|
||||
msgstr ""
|
||||
"Il ne peut y avoir qu'une seule instance d'un service global par collectivité"
|
||||
|
||||
#: models.py:315
|
||||
#: models.py:348
|
||||
msgid "Service URL field is required"
|
||||
msgstr "L'URL de service est requise pour un service non global"
|
||||
|
||||
#: models.py:318 models.py:339
|
||||
#: models.py:351 models.py:372
|
||||
msgid "service instance"
|
||||
msgstr "instance de service"
|
||||
|
||||
#: models.py:319
|
||||
#: models.py:352
|
||||
msgid "service instances"
|
||||
msgstr "instances de service"
|
||||
|
||||
#: models.py:337
|
||||
#: models.py:370
|
||||
msgid "user"
|
||||
msgstr "agent"
|
||||
|
||||
#: models.py:347
|
||||
#: models.py:380
|
||||
msgid "access"
|
||||
msgstr "accréditation"
|
||||
|
||||
#: models.py:348
|
||||
#: models.py:381
|
||||
msgid "accesses"
|
||||
msgstr "accréditations"
|
||||
|
||||
#: pratic_attribute_source.py:28
|
||||
msgid "User domain"
|
||||
msgstr ""
|
||||
msgstr "Domaine de l'utilisateur"
|
||||
|
||||
#: pratic_attribute_source.py:29
|
||||
msgid "User identifier"
|
||||
msgstr ""
|
||||
msgstr "Identifiant utilisateur"
|
||||
|
||||
#: tables.py:13 tables.py:26 tables.py:39 tables.py:52
|
||||
msgid "delete"
|
||||
msgstr "supprimer"
|
||||
|
||||
#: tables.py:61
|
||||
#: tables.py:62
|
||||
msgid "Last name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: tables.py:63
|
||||
#: tables.py:64
|
||||
msgid "First name"
|
||||
msgstr "Prénom"
|
||||
|
||||
#: views.py:28 templates/authentic2_pratic/delete.html:27
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
#: views.py:37
|
||||
msgid "You are not a super-administrator or a collectivity administrator"
|
||||
msgstr ""
|
||||
"Vous n'êtes pas un super-administrateur ou un administrateur de collectivité."
|
||||
|
||||
#: views.py:29
|
||||
#, python-format
|
||||
msgid "Do you really want to delete \"%s\" ?"
|
||||
msgstr "Êtes-vous sûr de vouloir supprimer \"%s\" ?"
|
||||
#: views.py:48
|
||||
msgid "You are not a super-administrator"
|
||||
msgstr "Vous n'êtes pas un super-administrateur."
|
||||
|
||||
#: views.py:63 templates/authentic2_pratic/services.html:10
|
||||
#: views.py:65 templates/authentic2_pratic/services.html:10
|
||||
msgid "Add service"
|
||||
msgstr "Ajouter un service"
|
||||
|
||||
#: views.py:65 views.py:91 views.py:141 views.py:168 views.py:207
|
||||
#: views.py:67 views.py:93 views.py:152 views.py:186 views.py:225
|
||||
msgid "Add"
|
||||
msgstr "Ajouter"
|
||||
|
||||
#: views.py:70
|
||||
#: views.py:72
|
||||
msgid "Edit service"
|
||||
msgstr "Éditer un service"
|
||||
|
||||
#: views.py:77
|
||||
#: views.py:79
|
||||
msgid "Delete service"
|
||||
msgstr "Supprimer un service"
|
||||
|
||||
#: views.py:89 templates/authentic2_pratic/collectivities.html:10
|
||||
#: views.py:91 templates/authentic2_pratic/collectivities.html:10
|
||||
msgid "Add collectivity"
|
||||
msgstr "Ajouter une collectivité"
|
||||
|
||||
#: views.py:96
|
||||
#: views.py:98
|
||||
msgid "Edit collectivity"
|
||||
msgstr "Éditer une collectivité"
|
||||
|
||||
#: views.py:104
|
||||
#: views.py:106
|
||||
msgid "Delete collectivity"
|
||||
msgstr "Supprimer une collectivité"
|
||||
|
||||
#: views.py:139 templates/authentic2_pratic/users.html:10
|
||||
#: views.py:121
|
||||
#, python-format
|
||||
msgid "You are not a super-administrator or an administrator of %s"
|
||||
msgstr "Vous n'êtes pas un super-administrateur ou un administrateur de %s"
|
||||
|
||||
#: views.py:150 templates/authentic2_pratic/users.html:10
|
||||
msgid "Add agent"
|
||||
msgstr "Ajouter un agent"
|
||||
|
||||
#: views.py:146
|
||||
#: views.py:157
|
||||
msgid "Edit agent"
|
||||
msgstr "Éditer un agent"
|
||||
|
||||
#: views.py:154
|
||||
#: views.py:162
|
||||
msgid "Reset password"
|
||||
msgstr "Ré-initialiser le mot de passe"
|
||||
|
||||
#: views.py:164
|
||||
msgid "Deactivate"
|
||||
msgstr "Désactiver"
|
||||
|
||||
#: views.py:166
|
||||
msgid "Activate"
|
||||
msgstr "Activer"
|
||||
|
||||
#: views.py:172
|
||||
msgid "Delete agent"
|
||||
msgstr "Supprimer un agent"
|
||||
|
||||
#: views.py:166 views.py:205
|
||||
#: views.py:184 views.py:223
|
||||
msgid "Add service instance"
|
||||
msgstr "Ajouter une instance de service"
|
||||
|
||||
#: views.py:174 views.py:213
|
||||
#: views.py:192 views.py:231
|
||||
msgid "Edit service instance"
|
||||
msgstr "Éditer une instance de service"
|
||||
|
||||
#: views.py:182 views.py:221
|
||||
#: views.py:200 views.py:239
|
||||
msgid "Delete service instance"
|
||||
msgstr "Supprimer une instance de service"
|
||||
|
||||
#: templates/authentic2_pratic/accesses.html:4
|
||||
#: templates/authentic2_pratic/accesses.html:6
|
||||
#: templates/authentic2_pratic/collectivity_edit.html:14
|
||||
#: templates/authentic2_pratic/homepage.html:29
|
||||
msgid "Accesses management"
|
||||
msgstr "Gestion des accréditations"
|
||||
|
||||
|
@ -317,12 +377,14 @@ msgstr[0] "%(count)s collectivité"
|
|||
msgstr[1] "%(count)s collectivités"
|
||||
|
||||
#: templates/authentic2_pratic/collectivity_edit.html:12
|
||||
#: templates/authentic2_pratic/homepage.html:27
|
||||
#: templates/authentic2_pratic/users.html:4
|
||||
#: templates/authentic2_pratic/users.html:6
|
||||
msgid "Agents management"
|
||||
msgstr "Gestion des agents"
|
||||
|
||||
#: templates/authentic2_pratic/collectivity_edit.html:13
|
||||
#: templates/authentic2_pratic/homepage.html:28
|
||||
#: templates/authentic2_pratic/service_instances.html:4
|
||||
#: templates/authentic2_pratic/service_instances.html:6
|
||||
msgid "Service instances management"
|
||||
|
@ -341,6 +403,7 @@ msgstr "Sauvegarder"
|
|||
#: templates/authentic2_pratic/collectivity_edit.html:49
|
||||
#: templates/authentic2_pratic/delete.html:28
|
||||
#: templates/authentic2_pratic/form.html:27
|
||||
#: templates/authentic2_pratic/login.html:50
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
|
@ -349,6 +412,10 @@ msgstr "Annuler"
|
|||
msgid "Do you really want to delete « %(object)s » ?"
|
||||
msgstr "Êtez-vous sûr de vouloir supprimer « %(object)s » ?"
|
||||
|
||||
#: templates/authentic2_pratic/delete.html:27
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: templates/authentic2_pratic/homepage.html:8
|
||||
msgid "Welcome"
|
||||
msgstr "Bievenue"
|
||||
|
@ -357,14 +424,44 @@ msgstr "Bievenue"
|
|||
msgid "Password change"
|
||||
msgstr "Changer de mot de passe"
|
||||
|
||||
#: templates/authentic2_pratic/homepage.html:19
|
||||
#: templates/authentic2_pratic/homepage.html:21
|
||||
msgid "Collectivities"
|
||||
msgstr "Collectivités"
|
||||
|
||||
#: templates/authentic2_pratic/homepage.html:20
|
||||
#: templates/authentic2_pratic/homepage.html:22
|
||||
msgid "Services"
|
||||
msgstr ""
|
||||
|
||||
#: templates/authentic2_pratic/homepage.html:25
|
||||
#, python-format
|
||||
msgid "Management of %(collectivity)s"
|
||||
msgstr "Gestion de %(collectivity)s"
|
||||
|
||||
#: templates/authentic2_pratic/login.html:20
|
||||
msgid "You are authenticated with certificate of users :"
|
||||
msgstr "Vous êtez authentifié avec le certificat des utilisateurs :"
|
||||
|
||||
#: templates/authentic2_pratic/login.html:26
|
||||
#, python-format
|
||||
msgid "<em>%(user)s</em> from <em>%(collectivity)s</em>"
|
||||
msgstr "<em>%(user)s</em> de <em>%(collectivity)s</em>"
|
||||
|
||||
#: templates/authentic2_pratic/login.html:29
|
||||
#: templates/authentic2_pratic/login.html:49
|
||||
msgid "Log in"
|
||||
msgstr "Connexion"
|
||||
|
||||
#: templates/authentic2_pratic/login.html:40
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You are\n"
|
||||
" authenticated with certificate of collectivity <em>%(collectivity)s.</"
|
||||
"em> It has\n"
|
||||
" been pre-selected for you."
|
||||
msgstr ""
|
||||
"Vous êtez authentifié avec le certificat de la collectivité <em>"
|
||||
"%(collectivity)s</em>. Cette collectivité a été pré-selectionné pour vous."
|
||||
|
||||
#: templates/authentic2_pratic/service_instances.html:10
|
||||
msgid "Add service-instance"
|
||||
msgstr "Ajouter une instance de service"
|
||||
|
@ -394,9 +491,3 @@ msgid "%(count)s agent"
|
|||
msgid_plural "%(count)s agents"
|
||||
msgstr[0] "%(count)s agent"
|
||||
msgstr[1] "%(count)s agents"
|
||||
|
||||
#~ msgid "Add user"
|
||||
#~ msgstr "Ajouter un agent"
|
||||
|
||||
#~ msgid "Edit user"
|
||||
#~ msgstr "Éditer un agent"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
from . import utils
|
||||
|
||||
class PraticAuthMiddleware(object):
|
||||
"""
|
||||
attempts to find a valid user based on the client certificate info
|
||||
"""
|
||||
def process_request(self, request):
|
||||
if request.user and not request.user.is_authenticated():
|
||||
request.ssl_info = utils.SSLInfo(request)
|
|
@ -68,12 +68,12 @@ class User(AuthUser):
|
|||
max_length=32,
|
||||
blank=True)
|
||||
certificate_issuer_dn = CharField(
|
||||
verbose_name=_('Certificate Issuer DN'),
|
||||
verbose_name=_('certificate issuer DN'),
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True)
|
||||
certificate_subject_dn = CharField(
|
||||
verbose_name=_('Certificate Subject DN'),
|
||||
verbose_name=_('certificate subject DN'),
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True)
|
||||
|
@ -91,6 +91,8 @@ class User(AuthUser):
|
|||
# prevent collisions between users from multiple collectivities
|
||||
if self.uid and not self.username and self.collectivity:
|
||||
self.username = u'%s@%s' % (self.uid, self.collectivity.slug)
|
||||
if self.collectivity:
|
||||
self.is_superuser = self.collectivity.is_superuser
|
||||
super(User, self).clean()
|
||||
|
||||
# Fields to support
|
||||
|
@ -213,12 +215,12 @@ class Collectivity(Model):
|
|||
max_length=128,
|
||||
blank=True)
|
||||
certificate_issuer_dn = CharField(
|
||||
verbose_name=_('Certificate Issuer DN'),
|
||||
verbose_name=_('certificate issuer DN'),
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True)
|
||||
certificate_subject_dn = CharField(
|
||||
verbose_name=_('Certificate Subject DN'),
|
||||
verbose_name=_('certificate subject DN'),
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True)
|
||||
|
@ -231,6 +233,12 @@ class Collectivity(Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# set is_superuser on users
|
||||
response = super(Collectivity, self).save(*args, **kwargs)
|
||||
User.objects.filter(collectivity=self).update(is_superuser=self.is_superuser)
|
||||
return response
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('collectivity')
|
||||
verbose_name_plural = _('collectivities')
|
||||
|
@ -337,8 +345,6 @@ class ServiceInstance(Model):
|
|||
self.oauth2_url = self.service.oauth2_url
|
||||
self.oauth2_key = self.oauth2_key
|
||||
if not self.service.is_global and not self.service_url:
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
raise ValidationError(_('Service URL field is required'))
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<form method="post" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
|
||||
<div class="form-inner-container">
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
{% load i18n staticfiles django_tables2 %}
|
||||
|
||||
{% block page_title %}
|
||||
<a href="{% url "a2-pratic-collectivity-edit" collectivity_pk=collectivity.pk %}">
|
||||
{{ collectivity }}
|
||||
</a>
|
||||
{% comment %}
|
||||
Only show link for super-admins, collectivity's admins just go back to the homepag
|
||||
{% endcomment %}
|
||||
{% if user.is_superuser %}
|
||||
<a href="{% url "a2-pratic-collectivity-edit" collectivity_pk=collectivity.pk %}">
|
||||
{{ collectivity }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{% if title %}
|
||||
<div id="appbar"><h2>{{ title }}</h2></div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<form method="post" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
|
||||
<div class="form-inner-container">
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
|
|
|
@ -15,10 +15,20 @@
|
|||
(<a href="{% url "auth_password_change" %}">{% trans "Password change" %}</a>)
|
||||
</div>
|
||||
|
||||
<ul class="apps">
|
||||
<li id="collectivities"><a href="{% url "a2-pratic-collectivities" %}">{% trans "Collectivities" %}</a></li>
|
||||
<li id="services"><a href="{% url "a2-pratic-services" %}">{% trans "Services" %}</a></li>
|
||||
</ul>
|
||||
<br style="clear: both;"/>
|
||||
|
||||
{% if user.is_superuser %}
|
||||
<ul class="apps">
|
||||
<li id="pratic-collectivities"><a href="{% url "a2-pratic-collectivities" %}">{% trans "Collectivities" %}</a></li>
|
||||
<li id="pratic-services"><a href="{% url "a2-pratic-services" %}">{% trans "Services" %}</a></li>
|
||||
</ul>
|
||||
{% elif user.is_admin %}
|
||||
<h3>{% blocktrans with collectivity=user.collectivity %}Management of {{ collectivity }}{% endblocktrans %}</h3>
|
||||
<ul class="apps">
|
||||
<li id="pratic-collectivity-users"><a href="{% url "a2-pratic-users" collectivity_pk=user.collectivity.pk %}">{% trans "Agents management" %}</a></li>
|
||||
<li id="pratic-collectivity-service-instances"><a href="{% url "a2-pratic-service-instances" collectivity_pk=user.collectivity.pk %}">{% trans "Service instances management" %}</a></li>
|
||||
<li id="pratic-collectivity-accesses"><a href="{% url "a2-pratic-accesses" collectivity_pk=user.collectivity.pk %}">{% trans "Accesses management" %}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<br style="clear: both;"/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
{% load i18n %}
|
||||
|
||||
<!--
|
||||
issuer dn: {{ request.ssl_info.issuer_dn }}
|
||||
subject dn: {{ request.ssl_info.subject_dn }}
|
||||
-->
|
||||
<div id="pratic-login">
|
||||
{% if request.ssl_info and request.ssl_info.users %}
|
||||
<style>
|
||||
.pratic-ssl-user-auth, .pratic-ssl-collectivity-auth {
|
||||
background: lightblue;
|
||||
color: black;
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
}
|
||||
.pratic-ssl-user-auth-button {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
<div class="pratic-ssl-user-auth">{% trans "You are authenticated with certificate of users :" %}
|
||||
<ul>
|
||||
{% for user in request.ssl_info.users %}
|
||||
<li>
|
||||
<p>
|
||||
<form method="post">
|
||||
<span>{% blocktrans with collectivity=user.collectivity %}<em>{{user}}</em> from <em>{{collectivity}}</em>{% endblocktrans %}</span>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="ssl-user" value="{{ user.pk }}"/>
|
||||
<input type="submit" name="{{ submit_name }}" value="{% trans "Log in" %}" class="pratic-ssl-user-auth-button"/>
|
||||
</form>
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.ssl_info and request.ssl_info.collectivity %}
|
||||
<p class="pratic-ssl-collectivity-auth">
|
||||
{% blocktrans with collectivity=request.ssl_info.collectivity %}You are
|
||||
authenticated with certificate of collectivity <em>{{collectivity}}.</em> It has
|
||||
been pre-selected for you.{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="pratic-login" value="{% trans "Log in" %}"/>
|
||||
<input type="submit" name="pratic-cancel" value="{% trans "Cancel" %}"/>
|
||||
</form>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,86 @@
|
|||
import base64
|
||||
|
||||
from . import models, app_settings
|
||||
|
||||
def normalize_cert(certificate_pem):
|
||||
'''Normalize content of the certificate'''
|
||||
base64_content = ''.join(certificate_pem.splitlines()[1:-1])
|
||||
content = base64.b64decode(base64_content)
|
||||
return base64.b64encode(content)
|
||||
|
||||
def explode_dn(dn):
|
||||
'''Extract sub element of a DN as displayed by mod_ssl or nginx_ssl'''
|
||||
dn = dn.strip('/')
|
||||
parts = dn.split('/')
|
||||
parts = [part.split('=') for part in parts]
|
||||
parts = [(part[0], part[1].decode('string_escape').decode('utf-8'))
|
||||
for part in parts]
|
||||
return parts
|
||||
|
||||
TRANSFORM = {
|
||||
'cert': normalize_cert,
|
||||
}
|
||||
|
||||
class SSLInfo(object):
|
||||
"""
|
||||
Encapsulates the SSL environment variables in a read-only object. It
|
||||
attempts to find the ssl vars based on the type of request passed to the
|
||||
constructor. Currently only WSGIRequest and ModPythonRequest are
|
||||
supported.
|
||||
"""
|
||||
def __init__(self, request):
|
||||
name = request.__class__.__name__
|
||||
if name == 'WSGIRequest':
|
||||
env = request.environ
|
||||
elif name == 'ModPythonRequest':
|
||||
env = request._req.subprocess_env
|
||||
else:
|
||||
raise EnvironmentError, 'The SSL authentication currently only \
|
||||
works with mod_python or wsgi requests'
|
||||
self.read_env(env);
|
||||
if self.issuer_dn and self.subject_dn:
|
||||
kwargs = dict(certificate_issuer_dn=self.issuer_dn,
|
||||
certificate_subject_dn=self.subject_dn)
|
||||
try:
|
||||
self.__dict__['collectivity'] = models.Collectivity.objects.get(**kwargs)
|
||||
except models.Collectivity.DoesNotExist:
|
||||
self.__dict__['collectivity'] = None
|
||||
self.__dict__['users'] = models.User.objects.filter(**kwargs).select_related()
|
||||
else:
|
||||
self.__dict__['collectivity'] = None
|
||||
self.__dict__['users'] = models.User.objects.none()
|
||||
|
||||
def read_env(self, env):
|
||||
for attr, keys in app_settings.X509_KEYS.iteritems():
|
||||
if isinstance(keys, basestring):
|
||||
keys = [keys]
|
||||
for key in keys:
|
||||
if key in env and env[key]:
|
||||
v = env[key]
|
||||
if attr in TRANSFORM:
|
||||
v = TRANSFORM[attr](v)
|
||||
self.__dict__[attr] = v
|
||||
break
|
||||
else:
|
||||
self.__dict__[attr] = None
|
||||
|
||||
|
||||
if self.__dict__['verify'] == 'SUCCESS':
|
||||
self.__dict__['verify'] = True
|
||||
else:
|
||||
self.__dict__['verify'] = False
|
||||
|
||||
def get(self, attr):
|
||||
return self.__getattr__(attr)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self.__dict__:
|
||||
return self.__dict__[attr]
|
||||
else:
|
||||
raise AttributeError, 'SSLInfo does not contain key %s' % attr
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
raise AttributeError, 'SSL vars are read only!'
|
||||
|
||||
def __repr__(self):
|
||||
return '<SSLInfo %s>' % self.__dict__
|
|
@ -1,13 +1,11 @@
|
|||
import logging
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.db.models.query import Q
|
||||
|
||||
from django.views.generic import (TemplateView, FormView, UpdateView,
|
||||
CreateView, DeleteView, View, ListView)
|
||||
from django.views.generic import (TemplateView, UpdateView,
|
||||
CreateView, DeleteView)
|
||||
from django_tables2 import SingleTableView
|
||||
|
||||
from authentic2.manager.views import (AjaxFormViewMixin,
|
||||
|
@ -15,13 +13,6 @@ from authentic2.manager.views import (AjaxFormViewMixin,
|
|||
|
||||
from . import models, tables, forms
|
||||
|
||||
def is_pratic_admin(user):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
return getattr(user, 'is_admin', False)
|
||||
|
||||
pratic_admin = user_passes_test(is_pratic_admin)
|
||||
|
||||
class SearchMixin(object):
|
||||
search_filter = []
|
||||
|
||||
|
@ -36,51 +27,72 @@ class SearchMixin(object):
|
|||
qs = qs.filter(filters)
|
||||
return qs
|
||||
|
||||
class AdminMixin(object):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
user = request.user
|
||||
if not user.is_authenticated:
|
||||
return redirect_to_login(request.get_full_path())
|
||||
|
||||
class HomepageView(TemplateView):
|
||||
if not user.is_superuser and (not hasattr(user, 'is_admin') or not user.is_admin):
|
||||
messages.warning(request, _('You are not a super-administrator or a collectivity administrator'))
|
||||
return redirect('auth_homepage')
|
||||
return super(AdminMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
class SuperAdminMixin(object):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
user = request.user
|
||||
if not user.is_authenticated:
|
||||
return redirect_to_login(request.get_full_path())
|
||||
|
||||
if not user.is_superuser:
|
||||
messages.warning(request, _('You are not a super-administrator'))
|
||||
return redirect('auth_homepage')
|
||||
return super(SuperAdminMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
class HomepageView(AdminMixin, TemplateView):
|
||||
template_name = 'authentic2_pratic/homepage.html'
|
||||
|
||||
# Services
|
||||
|
||||
class ServicesView(SingleTableView):
|
||||
class ServicesView(SuperAdminMixin, SingleTableView):
|
||||
template_name = 'authentic2_pratic/services.html'
|
||||
model = models.Service
|
||||
table_class = tables.ServiceTable
|
||||
|
||||
class ServiceAddView(TitleMixin, ActionMixin, AjaxFormViewMixin, CreateView):
|
||||
class ServiceAddView(SuperAdminMixin, TitleMixin, ActionMixin, AjaxFormViewMixin, CreateView):
|
||||
model = models.Service
|
||||
form_class = forms.ServiceForm
|
||||
title = _('Add service')
|
||||
template_name = 'authentic2_pratic/form.html'
|
||||
action = _('Add')
|
||||
|
||||
class ServiceView(TitleMixin, OtherActionsMixin,
|
||||
class ServiceView(SuperAdminMixin, TitleMixin, OtherActionsMixin,
|
||||
AjaxFormViewMixin, UpdateView):
|
||||
model = models.Service
|
||||
title = _('Edit service')
|
||||
template_name = 'authentic2_pratic/form.html'
|
||||
form_class = forms.ServiceForm
|
||||
|
||||
class ServiceDeleteView(TitleMixin, AjaxFormViewMixin, DeleteView):
|
||||
class ServiceDeleteView(SuperAdminMixin, TitleMixin, AjaxFormViewMixin, DeleteView):
|
||||
model = models.Service
|
||||
template_name = 'authentic2_pratic/delete.html'
|
||||
title = _('Delete service')
|
||||
success_url = 'a2-pratic-services'
|
||||
|
||||
# Collectivities
|
||||
class CollectivitiesView(SearchMixin, SingleTableView):
|
||||
class CollectivitiesView(SuperAdminMixin, SearchMixin, SingleTableView):
|
||||
search_filter = ('name', 'slug', 'sirh_label', 'postal_code')
|
||||
template_name = 'authentic2_pratic/collectivities.html'
|
||||
model = models.Collectivity
|
||||
table_class = tables.CollectivityTable
|
||||
|
||||
class CollectivityAddView(TitleMixin, ActionMixin, AjaxFormViewMixin, CreateView):
|
||||
class CollectivityAddView(SuperAdminMixin, TitleMixin, ActionMixin, AjaxFormViewMixin, CreateView):
|
||||
model = models.Collectivity
|
||||
title = _('Add collectivity')
|
||||
template_name = 'authentic2_pratic/form.html'
|
||||
action = _('Add')
|
||||
|
||||
class CollectivityView(TitleMixin, OtherActionsMixin,
|
||||
class CollectivityView(SuperAdminMixin, TitleMixin, OtherActionsMixin,
|
||||
AjaxFormViewMixin, UpdateView):
|
||||
model = models.Collectivity
|
||||
title = _('Edit collectivity')
|
||||
|
@ -88,7 +100,7 @@ class CollectivityView(TitleMixin, OtherActionsMixin,
|
|||
form_class = forms.CollectivityForm
|
||||
pk_url_kwarg = 'collectivity_pk'
|
||||
|
||||
class CollectivityDeleteView(TitleMixin, AjaxFormViewMixin, DeleteView):
|
||||
class CollectivityDeleteView(SuperAdminMixin, TitleMixin, AjaxFormViewMixin, DeleteView):
|
||||
model = models.Service
|
||||
template_name = 'authentic2_pratic/delete.html'
|
||||
title = _('Delete collectivity')
|
||||
|
@ -99,6 +111,15 @@ class CollectivityMixin(object):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
self.collectivity = get_object_or_404(models.Collectivity,
|
||||
pk=kwargs['collectivity_pk'])
|
||||
user = request.user
|
||||
if not user.is_authenticated:
|
||||
return redirect_to_login(request.get_full_path())
|
||||
|
||||
if not user.is_superuser and \
|
||||
(not hasattr(user, 'is_admin') or
|
||||
not (user.is_admin and user.collectivity == self.collectivity)):
|
||||
messages.warning(request, _('You are not a super-administrator or an administrator of %s') % self.collectivity)
|
||||
return redirect('auth_homepage')
|
||||
return super(CollectivityMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -219,35 +240,35 @@ class AccessDeleteView(AccessMixin, CollectivityMixin, TitleMixin,
|
|||
success_url = 'a2-pratic-users'
|
||||
|
||||
# general views
|
||||
homepage = pratic_admin(HomepageView.as_view())
|
||||
homepage = HomepageView.as_view()
|
||||
|
||||
# services
|
||||
services = pratic_admin(ServicesView.as_view())
|
||||
service_edit = pratic_admin(ServiceView.as_view())
|
||||
service_add = pratic_admin(ServiceAddView.as_view())
|
||||
service_delete = pratic_admin(ServiceDeleteView.as_view())
|
||||
services = ServicesView.as_view()
|
||||
service_edit = ServiceView.as_view()
|
||||
service_add = ServiceAddView.as_view()
|
||||
service_delete = ServiceDeleteView.as_view()
|
||||
|
||||
# collectivities
|
||||
collectivities = pratic_admin(CollectivitiesView.as_view())
|
||||
collectivity_edit = pratic_admin(CollectivityView.as_view())
|
||||
collectivity_add = pratic_admin(CollectivityAddView.as_view())
|
||||
collectivity_delete = pratic_admin(CollectivityDeleteView.as_view())
|
||||
collectivities = CollectivitiesView.as_view()
|
||||
collectivity_edit = CollectivityView.as_view()
|
||||
collectivity_add = CollectivityAddView.as_view()
|
||||
collectivity_delete = CollectivityDeleteView.as_view()
|
||||
|
||||
# collectivity users
|
||||
collectivity_users = pratic_admin(UsersView.as_view())
|
||||
user_add = pratic_admin(UserAddView.as_view())
|
||||
user_edit = pratic_admin(UserView.as_view())
|
||||
user_delete = pratic_admin(UserDeleteView.as_view())
|
||||
collectivity_users = UsersView.as_view()
|
||||
user_add = UserAddView.as_view()
|
||||
user_edit = UserView.as_view()
|
||||
user_delete = UserDeleteView.as_view()
|
||||
|
||||
# service instances
|
||||
collectivity_service_instances = pratic_admin(ServiceInstancesView.as_view())
|
||||
service_instance_add = pratic_admin(ServiceInstanceAddView.as_view())
|
||||
service_instance_edit = pratic_admin(ServiceInstanceView.as_view())
|
||||
service_instance_delete = pratic_admin(ServiceInstanceDeleteView.as_view())
|
||||
collectivity_service_instances = ServiceInstancesView.as_view()
|
||||
service_instance_add = ServiceInstanceAddView.as_view()
|
||||
service_instance_edit = ServiceInstanceView.as_view()
|
||||
service_instance_delete = ServiceInstanceDeleteView.as_view()
|
||||
|
||||
# accesses
|
||||
collectivity_accesses = pratic_admin(AccessesView.as_view())
|
||||
access_add = pratic_admin(AccessAddView.as_view())
|
||||
access_edit = pratic_admin(AccessView.as_view())
|
||||
access_delete = pratic_admin(AccessDeleteView.as_view())
|
||||
collectivity_accesses = AccessesView.as_view()
|
||||
access_add = AccessAddView.as_view()
|
||||
access_edit = AccessView.as_view()
|
||||
access_delete = AccessDeleteView.as_view()
|
||||
|
||||
|
|
Reference in New Issue