Implements permission masks

This commit is contained in:
Yohann D'ANELLO 2020-03-19 16:12:52 +01:00
parent d083894e9b
commit 95315cdbe2
15 changed files with 133 additions and 78 deletions

View File

@ -1,55 +0,0 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from threading import local
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
_thread_locals = local()
def _set_current_user_and_ip(user=None, ip=None):
setattr(_thread_locals, USER_ATTR_NAME, user)
setattr(_thread_locals, IP_ATTR_NAME, ip)
def get_current_user():
return getattr(_thread_locals, USER_ATTR_NAME, None)
def get_current_ip():
return getattr(_thread_locals, IP_ATTR_NAME, None)
def get_current_authenticated_user():
current_user = get_current_user()
if isinstance(current_user, AnonymousUser):
return None
return current_user
class LogsMiddleware(object):
"""
This middleware get the current user with his or her IP address on each request.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = request.user
if 'HTTP_X_FORWARDED_FOR' in request.META:
ip = request.META.get('HTTP_X_FORWARDED_FOR')
else:
ip = request.META.get('REMOTE_ADDR')
_set_current_user_and_ip(user, ip)
response = self.get_response(request)
_set_current_user_and_ip(None, None)
return response

View File

@ -9,7 +9,7 @@ import getpass
from note.models import NoteUser, Alias
from .middlewares import get_current_authenticated_user, get_current_ip
from note_kfet.middlewares import get_current_authenticated_user, get_current_ip
from .models import Changelog

View File

@ -3,10 +3,10 @@
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db.models import Q, F
from note.models import Note, NoteUser, NoteClub, NoteSpecial
from note_kfet.middlewares import get_current_session
from .models import Membership, RolePermissions, Club
from django.contrib.auth.backends import ModelBackend
@ -37,7 +37,8 @@ class PermissionBackend(ModelBackend):
F=F,
Q=Q
)
yield permission
if permission.mask.rank <= get_current_session().get("permission_mask", 0):
yield permission
@staticmethod
def filter_queryset(user, model, t, field=None):
@ -50,7 +51,7 @@ class PermissionBackend(ModelBackend):
:return: A query that corresponds to the filter to give to a queryset
"""
if user.is_superuser:
if user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
# Superusers have all rights
return Q()
@ -68,7 +69,7 @@ class PermissionBackend(ModelBackend):
return query
def has_perm(self, user_obj, perm, obj=None):
if user_obj.is_superuser:
if user_obj.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
return True
if obj is None:

View File

@ -6,12 +6,21 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from dal import autocomplete
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.models import User
from permission.models import PermissionMask
from .models import Profile, Club, Membership
class CustomAuthenticationForm(AuthenticationForm):
permission_mask = forms.ModelChoiceField(
label="Masque de permissions",
queryset=PermissionMask.objects.order_by("rank"),
empty_label=None,
)
class SignUpForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -9,6 +9,7 @@ from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.http import HttpResponseRedirect
@ -26,11 +27,20 @@ from note.tables import HistoryTable, AliasTable
from .backends import PermissionBackend
from .filters import UserFilter, UserFilterFormHelper
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper, \
CustomAuthenticationForm
from .models import Club, Membership
from .tables import ClubTable, UserTable
class CustomLoginView(LoginView):
form_class = CustomAuthenticationForm
def form_valid(self, form):
self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank
return super().form_valid(form)
class UserCreateView(CreateView):
"""
Une vue pour inscrire un utilisateur et lui créer un profile

View File

@ -4,7 +4,7 @@
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
from logs.middlewares import get_current_authenticated_user
from note_kfet.middlewares import get_current_authenticated_user
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
TemplateTransaction, SpecialTransaction

View File

@ -3,7 +3,7 @@
"model": "note.note",
"pk": 1,
"fields": {
"polymorphic_ctype": 40,
"polymorphic_ctype": 41,
"balance": 0,
"is_active": true,
"display_image": "",
@ -14,7 +14,7 @@
"model": "note.note",
"pk": 2,
"fields": {
"polymorphic_ctype": 40,
"polymorphic_ctype": 41,
"balance": 0,
"is_active": true,
"display_image": "",
@ -25,7 +25,7 @@
"model": "note.note",
"pk": 3,
"fields": {
"polymorphic_ctype": 40,
"polymorphic_ctype": 41,
"balance": 0,
"is_active": true,
"display_image": "",
@ -36,7 +36,7 @@
"model": "note.note",
"pk": 4,
"fields": {
"polymorphic_ctype": 40,
"polymorphic_ctype": 41,
"balance": 0,
"is_active": true,
"display_image": "",
@ -47,7 +47,7 @@
"model": "note.note",
"pk": 5,
"fields": {
"polymorphic_ctype": 39,
"polymorphic_ctype": 40,
"balance": 0,
"is_active": true,
"display_image": "",
@ -58,7 +58,7 @@
"model": "note.note",
"pk": 6,
"fields": {
"polymorphic_ctype": 39,
"polymorphic_ctype": 40,
"balance": 0,
"is_active": true,
"display_image": "",

View File

@ -3,7 +3,15 @@
from django.contrib import admin
from .models import Permission
from .models import Permission, PermissionMask
@admin.register(PermissionMask)
class PermissionMaskAdmin(admin.ModelAdmin):
"""
Admin customisation for Permission
"""
list_display = ('rank', 'description')
@admin.register(Permission)

View File

@ -50,6 +50,20 @@ class InstancedPermission:
return self.__repr__()
class PermissionMask(models.Model):
rank = models.PositiveSmallIntegerField(
verbose_name=_('rank'),
)
description = models.CharField(
max_length=255,
verbose_name=_('description'),
)
def __str__(self):
return self.description
class Permission(models.Model):
PERMISSION_TYPES = [
@ -85,6 +99,11 @@ class Permission(models.Model):
type = models.CharField(max_length=15, choices=PERMISSION_TYPES)
mask = models.ForeignKey(
PermissionMask,
on_delete=models.PROTECT,
)
field = models.CharField(max_length=255, blank=True)
description = models.CharField(max_length=255, blank=True)

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from django.core.exceptions import PermissionDenied
from logs.middlewares import get_current_authenticated_user
from note_kfet.middlewares import get_current_authenticated_user
EXCLUDED = [

View File

@ -4,7 +4,7 @@
from django.contrib.contenttypes.models import ContentType
from django.template.defaultfilters import stringfilter
from logs.middlewares import get_current_authenticated_user
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
from django import template
from member.backends import PermissionBackend
@ -19,7 +19,7 @@ def not_empty_model_list(model_name):
user = get_current_authenticated_user()
if user is None:
return False
elif user.is_superuser:
elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
return True
spl = model_name.split(".")
ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
@ -32,7 +32,7 @@ def not_empty_model_change_list(model_name):
user = get_current_authenticated_user()
if user is None:
return False
elif user.is_superuser:
elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
return True
spl = model_name.split(".")
ct = ContentType.objects.get(app_label=spl[0], model=spl[1])

View File

@ -7,7 +7,7 @@ if [ -z ${NOTE_URL+x} ]; then
else
sed -i -e "s/example.com/$DOMAIN/g" /code/apps/member/fixtures/initial.json
sed -i -e "s/localhost/$NOTE_URL/g" /code/note_kfet/fixtures/initial.json
sed -i -e "s/\.\*/https?:\/\/$NOTE_URL\/.*/g" /code/note_kfet/fixtures/cas.json
sed -i -e "s/\"\.\*\"/\"https?:\/\/$NOTE_URL\/.*\"/g" /code/note_kfet/fixtures/cas.json
sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /code/note_kfet/fixtures/cas.json
fi

View File

@ -1,6 +1,66 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
from threading import local
from django.contrib.sessions.backends.db import SessionStore
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session')
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
_thread_locals = local()
def _set_current_user_and_ip(user=None, session=None, ip=None):
setattr(_thread_locals, USER_ATTR_NAME, user)
setattr(_thread_locals, SESSION_ATTR_NAME, session)
setattr(_thread_locals, IP_ATTR_NAME, ip)
def get_current_user() -> User:
return getattr(_thread_locals, USER_ATTR_NAME, None)
def get_current_session() -> SessionStore:
return getattr(_thread_locals, SESSION_ATTR_NAME, None)
def get_current_ip() -> str:
return getattr(_thread_locals, IP_ATTR_NAME, None)
def get_current_authenticated_user():
current_user = get_current_user()
if isinstance(current_user, AnonymousUser):
return None
return current_user
class SessionMiddleware(object):
"""
This middleware get the current user with his or her IP address on each request.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = request.user
if 'HTTP_X_FORWARDED_FOR' in request.META:
ip = request.META.get('HTTP_X_FORWARDED_FOR')
else:
ip = request.META.get('REMOTE_ADDR')
_set_current_user_and_ip(user, request.session, ip)
response = self.get_response(request)
_set_current_user_and_ip(None, None, None)
return response
class TurbolinksMiddleware(object):
"""

View File

@ -74,7 +74,7 @@ if "cas" in INSTALLED_APPS:
if "logs" in INSTALLED_APPS:
MIDDLEWARE += ('logs.middlewares.LogsMiddleware',)
MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',)
if "debug_toolbar" in INSTALLED_APPS:
MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware")

View File

@ -7,6 +7,8 @@ from django.contrib import admin
from django.urls import path, include
from django.views.generic import RedirectView
from member.views import CustomLoginView
urlpatterns = [
# Dev so redirect to something random
path('', RedirectView.as_view(pattern_name='note:transfer'), name='index'),
@ -16,10 +18,11 @@ urlpatterns = [
# Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')),
path('accounts/', include('member.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls),
path('accounts/', include('member.urls')),
path('accounts/login/', CustomLoginView.as_view()),
path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')),
]