Commit 6f07c0ea authored by Alexandre Lanceart's avatar Alexandre Lanceart Committed by Florent Chehab

feat(site): CGU/RGPD requierements & ban user

* Added the necessary behavior to require the validation of CGU and RGPD.
* Added matching tests for this
* Added the possibility to ban users
* Also fixed the API Viewsets so that they can be registered like any model viewset

Fixes #121
Fixes #122
parent 9caa1a59
Pipeline #40889 passed with stages
in 3 minutes and 58 seconds
# Generated by Django 2.1.7 on 2019-05-31 18:07
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("backend_app", "0008_auto_20190525_1522")]
operations = [
migrations.AlterField(
model_name="coursefeedback",
name="adequation",
field=models.IntegerField(
default=0,
validators=[
django.core.validators.MinValueValidator(-5),
django.core.validators.MaxValueValidator(5),
],
),
),
migrations.AlterField(
model_name="coursefeedback",
name="language_following_ease",
field=models.IntegerField(
default=0,
validators=[
django.core.validators.MinValueValidator(-5),
django.core.validators.MaxValueValidator(5),
],
),
),
migrations.AlterField(
model_name="coursefeedback",
name="working_dose",
field=models.IntegerField(
default=0,
validators=[
django.core.validators.MinValueValidator(-5),
django.core.validators.MaxValueValidator(5),
],
),
),
migrations.AlterField(
model_name="exchangefeedback",
name="academical_level_appreciation",
field=models.IntegerField(
validators=[
django.core.validators.MinValueValidator(-5),
django.core.validators.MaxValueValidator(5),
]
),
),
]
...@@ -65,6 +65,17 @@ class WithUserTestCase(TestCase): ...@@ -65,6 +65,17 @@ class WithUserTestCase(TestCase):
username=cls.authenticated_user_2.username, password=password username=cls.authenticated_user_2.username, password=password
) )
for user in [
cls.staff_user,
cls.moderator_user,
cls.dri_user,
cls.authenticated_user,
cls.authenticated_user_2,
]:
user.has_validated_cgu = True
user.has_validated_rgpd = True
user.save()
cls.unauthenticated_client = APIClient() cls.unauthenticated_client = APIClient()
cls.setUpMoreTestData() cls.setUpMoreTestData()
......
...@@ -13,13 +13,8 @@ urlpatterns = [url(r"^api-doc/", include_docs_urls(title="REX-DRI API"))] ...@@ -13,13 +13,8 @@ urlpatterns = [url(r"^api-doc/", include_docs_urls(title="REX-DRI API"))]
# router will hold all api related endpoints # router will hold all api related endpoints
router = routers.DefaultRouter() router = routers.DefaultRouter()
for v in ALL_API_VIEWSETS: for v in ALL_API_VIEWSETS + ALL_API_VIEW_VIEWSETS:
router.register(v.end_point_route, v, str(v)) router.register(v.end_point_route, v, str(v))
# Add all the endpoints for the base api # Add all the endpoints for the base api
urlpatterns.append(url(r"^api/", include(router.urls))) urlpatterns.append(url(r"^api/", include(router.urls)))
# We need to register some extra viewset
for v in ALL_API_VIEW_VIEWSETS:
urlpatterns.append(url(r"^api/{}/".format(v.end_point_route), v.as_view()))
...@@ -2,7 +2,7 @@ import logging ...@@ -2,7 +2,7 @@ import logging
from django.conf import settings from django.conf import settings
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.viewsets import ViewSet
from backend_app.checks import check_viewsets from backend_app.checks import check_viewsets
from backend_app.models.abstract.essentialModule import EssentialModuleViewSet from backend_app.models.abstract.essentialModule import EssentialModuleViewSet
...@@ -37,10 +37,10 @@ from backend_app.models.universitySemestersDates import UniversitySemestersDates ...@@ -37,10 +37,10 @@ from backend_app.models.universitySemestersDates import UniversitySemestersDates
from backend_app.models.universityTaggedItem import UniversityTaggedItemViewSet from backend_app.models.universityTaggedItem import UniversityTaggedItemViewSet
from backend_app.models.userData import UserDataViewSet from backend_app.models.userData import UserDataViewSet
from backend_app.models.version import VersionViewSet from backend_app.models.version import VersionViewSet
from backend_app.permissions.app_permissions import ReadOnly from backend_app.permissions.app_permissions import ReadOnly, IsStaff
from backend_app.serializers import CourseFeedbackSerializer from backend_app.serializers import CourseFeedbackSerializer
from backend_app.settings.defaults import OBJ_MODERATION_PERMISSIONS from backend_app.settings.defaults import OBJ_MODERATION_PERMISSIONS
from base_app.models import UserViewset from base_app.models import UserViewset, User
class CourseFeedbackViewSet(EssentialModuleViewSet): class CourseFeedbackViewSet(EssentialModuleViewSet):
...@@ -87,7 +87,7 @@ if settings.TESTING: ...@@ -87,7 +87,7 @@ if settings.TESTING:
ALL_API_VIEWSETS += [ForTestingModerationViewSet, ForTestingVersioningViewSet] ALL_API_VIEWSETS += [ForTestingModerationViewSet, ForTestingVersioningViewSet]
class AppModerationStatusViewSet(APIView): class AppModerationStatusViewSet(ViewSet):
""" """
Viewset to know what is the app moderation status Viewset to know what is the app moderation status
""" """
...@@ -97,7 +97,7 @@ class AppModerationStatusViewSet(APIView): ...@@ -97,7 +97,7 @@ class AppModerationStatusViewSet(APIView):
permission_classes = (ReadOnly,) permission_classes = (ReadOnly,)
end_point_route = "serverModerationStatus" end_point_route = "serverModerationStatus"
def get(self, request): def list(self, request):
return Response( return Response(
{ {
"activated": settings.MODERATION_ACTIVATED, "activated": settings.MODERATION_ACTIVATED,
...@@ -106,7 +106,7 @@ class AppModerationStatusViewSet(APIView): ...@@ -106,7 +106,7 @@ class AppModerationStatusViewSet(APIView):
) )
class LogFrontendErrorsViewSet(APIView): class LogFrontendErrorsViewSet(ViewSet):
""" """
Viewset to handle the logging of errors coming from the frontend. Viewset to handle the logging of errors coming from the frontend.
""" """
...@@ -114,7 +114,7 @@ class LogFrontendErrorsViewSet(APIView): ...@@ -114,7 +114,7 @@ class LogFrontendErrorsViewSet(APIView):
permission_classes = tuple() permission_classes = tuple()
end_point_route = "frontendErrors" end_point_route = "frontendErrors"
def post(self, request): def create(self, request):
logger = logging.getLogger("frontend") logger = logging.getLogger("frontend")
data = request.data data = request.data
if "componentStack" in data.keys(): if "componentStack" in data.keys():
...@@ -124,7 +124,49 @@ class LogFrontendErrorsViewSet(APIView): ...@@ -124,7 +124,49 @@ class LogFrontendErrorsViewSet(APIView):
return Response(status=201) return Response(status=201)
ALL_API_VIEW_VIEWSETS = [AppModerationStatusViewSet, LogFrontendErrorsViewSet] class BannedUserViewSet(ViewSet):
"""
Viewset to be able to ban and un-ban users from the site
"""
end_point_route = "banned_users"
permission_classes = (IsStaff,)
def list(self, request, **kwargs):
return Response(
[
dict(user_id=user.pk, user_login=user.username)
for user in User.objects.filter(is_banned=True)
]
)
def update(self, request, pk=None):
if pk is None:
return Response(status=403)
user = User.objects.get(pk=pk)
if user.is_staff:
# Prevent ban of admin users
return Response(status=403)
user.is_banned = True
user.save()
return Response(status=200)
def delete(self, request, pk=None):
if pk is None:
return Response(status=403)
user = User.objects.get(pk=pk)
user.is_banned = False
user.save()
return Response(status=200)
ALL_API_VIEW_VIEWSETS = [
AppModerationStatusViewSet,
LogFrontendErrorsViewSet,
BannedUserViewSet,
]
ALL_VIEWSETS = ALL_API_VIEWSETS + ALL_API_VIEW_VIEWSETS ALL_VIEWSETS = ALL_API_VIEWSETS + ALL_API_VIEW_VIEWSETS
......
from django import forms
from base_app.models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ("has_validated_rgpd", "has_validated_cgu")
...@@ -42,8 +42,20 @@ class RexDriRequestMiddleware(MiddlewareMixin): ...@@ -42,8 +42,20 @@ class RexDriRequestMiddleware(MiddlewareMixin):
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\ work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'." 'django.core.context_processors.auth'."
path = request.path_info.lstrip("/")
full_path = request.get_full_path()
# If the user is not authenticated redirect him/her to the login page # If the user is not authenticated redirect him/her to the login page
if not request.user.is_authenticated: if not request.user.is_authenticated:
path = request.path_info.lstrip("/")
if not any(m.match(path) for m in EXEMPT_URLS): if not any(m.match(path) for m in EXEMPT_URLS):
return HttpResponseRedirect(settings.LOGIN_URL + "?next=/" + path) return HttpResponseRedirect(settings.LOGIN_URL + "?next=/" + path)
else:
# User is authenticated
# We check if he / she has validated the CGU and the full RGPD
if not (request.user.has_validated_cgu and request.user.has_validated_rgpd):
if not path.startswith("cgu"):
return HttpResponseRedirect("/cgu/?next={}".format(full_path))
# Handling of banned users
elif request.user.is_banned and full_path != "/banned_note/":
return HttpResponseRedirect("/banned_note/")
# Generated by Django 2.1.7 on 2019-05-31 18:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("base_app", "0001_squashed_0002_auto_20190402_2159")]
operations = [
migrations.AddField(
model_name="user",
name="has_validated_cgu",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user",
name="has_validated_rgpd",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user",
name="is_banned",
field=models.BooleanField(default=False),
),
]
...@@ -40,6 +40,9 @@ class User(AbstractUser): ...@@ -40,6 +40,9 @@ class User(AbstractUser):
pseudo = models.CharField( pseudo = models.CharField(
blank=False, null=False, max_length=12, default="Anonymous42" blank=False, null=False, max_length=12, default="Anonymous42"
) )
has_validated_cgu = models.BooleanField(default=False, null=False)
has_validated_rgpd = models.BooleanField(default=False, null=False)
is_banned = models.BooleanField(default=False, null=False)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vous êtes banni du site</title>
</head>
<body>
Vous êtes banni du site, veuillez contacter le SIMDE.
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<br>
<br>
<form method="post">
{% csrf_token %}
Valider le CGU
{{ form.has_validated_cgu }}
<br>
<br>
Valider la charte RGPD
{{ form.has_validated_rgpd }}
<br>
<br>
<input type="submit" value="Valider">
</form>
</body>
</html>
\ No newline at end of file
from django.test import TestCase
from rest_framework.test import APIClient
from base_app.models import User
class HasAccessTestCase(TestCase):
"""
Test to check that validation of the CGU and RGPD is Required
"""
@classmethod
def setUpTestData(cls):
password = "123456"
cls.has_validated_user = User.objects.create_user(
username="yolo", email="osef@master.fr", password=password
)
cls.has_validated_user.has_validated_cgu = True
cls.has_validated_user.has_validated_rgpd = True
cls.has_validated_user.save()
cls.has_not_validated_user = User.objects.create_user(
username="yolo2", email="osef@master.fr", password=password
)
cls.has_not_validated_user.has_validated_cgu = False
cls.has_not_validated_user.has_validated_rgpd = False
cls.has_not_validated_user.save()
cls.has_validated_client = APIClient()
cls.has_validated_client.login(
username=cls.has_validated_user.username, password=password
)
cls.has_not_validated_client = APIClient()
cls.has_not_validated_client.login(
username=cls.has_not_validated_user.username, password=password
)
def test_accepted_has_access(self):
response = self.has_validated_client.get("/api/")
self.assertEqual(response.status_code, 200)
def test_not_accepted_refused(self):
response = self.has_not_validated_client.get("/api/")
self.assertEqual(response.status_code, 302) # We have a redirect
self.assertEqual(response.url, "/cgu/?next=/api/")
...@@ -26,6 +26,8 @@ urlpatterns += [ ...@@ -26,6 +26,8 @@ urlpatterns += [
name="cas_ng_proxy_callback", name="cas_ng_proxy_callback",
), ),
url(r"^app/.*", views.index), url(r"^app/.*", views.index),
url(r"^cgu/.*", views.cgu),
url(r"^banned_note/", views.banned),
url(r"^$", RedirectView.as_view(url="./app/"), name="go to real home"), url(r"^$", RedirectView.as_view(url="./app/"), name="go to real home"),
url(r"", include("backend_app.urls")), url(r"", include("backend_app.urls")),
url(r"^media/(?P<path>.*)", media_files_view, name="media"), url(r"^media/(?P<path>.*)", media_files_view, name="media"),
......
from django.http import HttpResponse import logging
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.shortcuts import render from django.shortcuts import render
from webpack_loader.utils import get_files from webpack_loader.utils import get_files
from backend_app.utils import clean_route from backend_app.utils import clean_route
from backend_app.viewsets import ALL_VIEWSETS from backend_app.viewsets import ALL_VIEWSETS
from base_app.forms import UserForm
from base_app.models import User
logger = logging.getLogger("django")
def index(request): def index(request):
...@@ -28,6 +34,32 @@ def index(request): ...@@ -28,6 +34,32 @@ def index(request):
) )
def cgu(request):
if request.method == "POST":
user = User.objects.get(pk=request.user.pk)
form = UserForm(request.POST, instance=user)
if form.is_valid():
user = form.save(commit=True)
user.save()
else:
user = User.objects.get(pk=request.user.pk)
form = UserForm(instance=user)
if "next" in request.GET and user.has_validated_cgu and user.has_validated_rgpd:
# if the user has just validated everything, we redirect him to the location he requested
return HttpResponseRedirect(request.GET["next"])
else:
return render(request, "cgu.html", dict(user=user, form=form))
def banned(request):
user = request.user
if user.is_banned:
return render(request, "banned.html", dict(user=user))
else:
return HttpResponseNotFound()
def media_files_view(request, path): def media_files_view(request, path):
""" """
Media files are served by nginx only if the user is connected. Media files are served by nginx only if the user is connected.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment