Commit b89d8b63 authored by Florent Chehab's avatar Florent Chehab

feat(rgpd/cgu): everything is ready

* Full handling of RGPD/CGU validation
* management command to clean user personnal data created
* Added RGPD and CGU as markdown in the documentation (also used in the front)

Front:
* Multiple entrypoints for the different needs
* Added raw file loader
* bumped frontend image version
* More generic front template (for the multiple entries)
* Added NotFound Component
* More generic Markdown component (in regards to handling off-the-api situations and heading size)

Closes #67
parent 2f3e6bb6
Pipeline #42031 passed with stages
in 8 minutes and 37 seconds
......@@ -34,11 +34,15 @@ check_back:
check_front:
<<: *only-default
stage: check
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.4.0
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.5.0
before_script:
- cd frontend && cp -R /usr/src/deps/node_modules .
script:
- npm run build
artifacts:
paths:
- frontend/webpack-stats.json
expire_in: 1 hour
tags:
- docker
......@@ -70,7 +74,7 @@ test_back:
test_frontend:
<<: *only-default
stage: test
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.4.0
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.5.0
before_script:
- cd frontend && cp -R /usr/src/deps/node_modules .
script:
......@@ -90,7 +94,7 @@ flake8:
eslint:
<<: *only-default
stage: lint
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.4.0
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.5.0
before_script:
- cd frontend && cp -R /usr/src/deps/node_modules .
script:
......
......@@ -72,8 +72,7 @@ class WithUserTestCase(TestCase):
cls.authenticated_user,
cls.authenticated_user_2,
]:
user.has_validated_cgu = True
user.has_validated_rgpd = True
user.has_validated_cgu_rgpd = True
user.save()
cls.unauthenticated_client = APIClient()
......
......@@ -167,6 +167,37 @@ class BannedUserViewSet(ViewSet):
return Response(status=200)
class DeleteUserViewSet(ViewSet):
"""
Viewset to handle account deletion
"""
end_point_route = "emptyUserAccount"
permission_classes = tuple()
def create(self, request):
"""
Line up the user from the request for deletion
"""
user = request.user
user.delete_next_time = True
user.save()
return Response(status=201)
def update(self, request):
# Here only to have the correct routes automatically generated, not to be used.
return Response(status=403)
def delete(self, request, pk="osef"): # don't delete this unused argument!
"""
Un-Line up the user from the request for deletion
"""
user = request.user
user.delete_next_time = False
user.save()
return Response(status=200)
class RecommendationListChangeFollowerViewSet(ViewSet):
"""
Viewset to be able to add or delete followers on
......@@ -184,7 +215,7 @@ class RecommendationListChangeFollowerViewSet(ViewSet):
recommendation = RecommendationList.objects.get(pk=pk)
if recommendation.is_public:
recommendation.followers.add(self.request.user)
recommendation.followers.add(request.user)
recommendation.save()
return Response(status=200)
else:
......@@ -195,7 +226,7 @@ class RecommendationListChangeFollowerViewSet(ViewSet):
return Response(status=403)
# can delete folower even if list not public
recommendation = RecommendationList.objects.get(pk=pk)
recommendation.followers.remove(self.request.user)
recommendation.followers.remove(request.user)
recommendation.save()
return Response(status=200)
......@@ -205,6 +236,7 @@ ALL_API_VIEW_VIEWSETS = [
LogFrontendErrorsViewSet,
BannedUserViewSet,
RecommendationListChangeFollowerViewSet,
DeleteUserViewSet,
]
ALL_VIEWSETS = ALL_API_VIEWSETS + ALL_API_VIEW_VIEWSETS
......
......@@ -5,4 +5,4 @@ from base_app.models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ("has_validated_rgpd", "has_validated_cgu")
fields = ("has_validated_cgu_rgpd",)
import logging
from django.contrib.sessions.management.commands.clearsessions import (
Command as ClearSessionCommand,
)
from django.core.management.base import BaseCommand
from django_cas_ng.models import ProxyGrantingTicket, SessionTicket
from backend_app.models.recommendationList import RecommendationList
from backend_app.models.userData import UserData
from backend_app.utils import get_default_theme_settings
from base_app.models import User
logger = logging.getLogger("django")
class Command(BaseCommand):
help = "Command to handle user accounts emptying"
def handle(self, *args, **options):
logger.info("here")
for user in User.objects.filter(delete_next_time=True):
logger.info("Emptying account of user {}".format(user.pk))
# for user Data we don't delete it but restore it to default
# We do this to be consistent with the assumption that each user has some userData
user_data = UserData.objects.get(pk=user.pk)
user_data.theme = get_default_theme_settings()
user_data.save()
# deleting all owned private lists
RecommendationList.objects.filter(owner=user, is_public=False).delete()
# Emptying user model
user.allow_sharing_personal_info = False
user.secondary_email = ""
user.pseudo = "Del."
user.has_validated_cgu_rgpd = False
user.is_banned = False
user.username = "__Deleted__{}".format(user.pk)
user.email = ""
user.is_active = False
user.delete_next_time = False
user.is_deleted = True
user.save()
# Finally we clear the sessions
ClearSessionCommand().handle()
ProxyGrantingTicket.clean_deleted_sessions()
SessionTicket.clean_deleted_sessions()
# Still error 500 on delete then reconnect... Unknown
......@@ -53,9 +53,9 @@ class RexDriRequestMiddleware(MiddlewareMixin):
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))
if not request.user.has_validated_cgu_rgpd:
if not path.startswith("cgu-rgpd"):
return HttpResponseRedirect("/cgu-rgpd/?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-06-16 15:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("base_app", "0002_auto_20190531_2007")]
operations = [
migrations.RenameField(
model_name="user",
old_name="has_validated_cgu",
new_name="has_validated_cgu_rgpd",
),
migrations.RemoveField(model_name="user", name="has_validated_rgpd"),
migrations.AddField(
model_name="user",
name="delete_next_time",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user",
name="is_deleted",
field=models.BooleanField(default=False),
),
]
......@@ -28,6 +28,10 @@ def validate(user, allow_sharing_personal_info):
class User(AbstractUser):
"""
If you modify this model, don't forget to modify the command that handles its emptying for RGPD
"""
@cached_property
def cached_groups(self) -> List[str]:
out = ["authenticated_user"]
......@@ -40,10 +44,15 @@ class User(AbstractUser):
pseudo = models.CharField(
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)
has_validated_cgu_rgpd = models.BooleanField(default=False, null=False)
is_banned = models.BooleanField(default=False, null=False)
# Handling of account deletion
delete_next_time = models.BooleanField(
default=False, null=False
) # if true, the account will be deleted at midnight
is_deleted = models.BooleanField(default=False, null=False)
def save(self, *args, **kwargs):
"""
Custom save function to ensure consistency.
......@@ -75,6 +84,7 @@ class UserSerializer(BaseModelSerializer):
"allow_sharing_personal_info",
"secondary_email",
"pseudo",
"delete_next_time",
"is_staff",
)
read_only_fields = ("username", "first_name", "last_name", "email")
......
......@@ -179,7 +179,8 @@ AUTH_PASSWORD_VALIDATORS = [
]
LOGIN_URL = "/user/login"
LOGIN_EXEMPT_URLS = [LOGIN_URL]
RGPD_URL = "/rgpd-raw"
LOGIN_EXEMPT_URLS = [LOGIN_URL, RGPD_URL]
if TESTING:
# In a testing environment we may want to by pass the loginRequiredMiddleware
......
{% load static %}
<!DOCTYPE html>
<html style="font-size:14">
<head>
<meta
charset="utf-8"
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/>
<!-- Custom colors for phone status bar -->
<meta name="theme-color" content="#9c27b0">
<meta name="apple-mobile-web-app-status-bar-style" content="#9c27b0">
<!-- Favicon hell -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="{% static '/base_app/favicon/apple-touch-icon-57x57.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="{% static '/base_app/favicon/apple-touch-icon-114x114.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="{% static '/base_app/favicon/apple-touch-icon-72x72.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{% static '/base_app/favicon/apple-touch-icon-144x144.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="{% static '/base_app/favicon/apple-touch-icon-60x60.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="{% static '/base_app/favicon/apple-touch-icon-120x120.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="{% static '/base_app/favicon/apple-touch-icon-76x76.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{% static '/base_app/favicon/apple-touch-icon-152x152.png' %}" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-196x196.png' %}" sizes="196x196" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-96x96.png' %}" sizes="96x96" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-32x32.png' %}" sizes="32x32" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-16x16.png' %}" sizes="16x16" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-128.png' %}" sizes="128x128" />
<!-- End of favicon hell -->
<title>REX-DRI</title>
</head>
<body>
<div id="app" class="columns">
<!-- React -->
</div>
</body>
{% block content %}{% endblock %}
<noscript>Cette application nécessite Javascript... Merci de l'activer ou d'utiliser un navigateur approprié.</noscript>
</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
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% block content %}
<script>
var __hasValidatedCguRgpd = {% if user.has_validated_cgu_rgpd %} true {% else %} false {% endif %};
</script>
{% render_bundle 'rgpdCgu' %}
{% render_bundle 'vendor' %}
{% if user.has_validated_cgu_rgpd %}
{% else %}
<div style="display: none;">
<!-- Hidden form to be used by react to submit the values -->
<form method="post">
{% csrf_token %}
{{ form.has_validated_cgu_rgpd }}
<br>
<input type="submit" value="Valider" id="formSubmitButton">
</form>
</div>
{% endif %}
{% endblock %}
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% load static %}
<!DOCTYPE html>
<html style="font-size:14">
<head>
<meta
charset="utf-8"
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/>
<!-- Custom colors for phone status bar -->
<meta name="theme-color" content="#9c27b0">
<meta name="apple-mobile-web-app-status-bar-style" content="#9c27b0">
<!-- Favicon hell -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="{% static '/base_app/favicon/apple-touch-icon-57x57.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="{% static '/base_app/favicon/apple-touch-icon-114x114.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="{% static '/base_app/favicon/apple-touch-icon-72x72.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{% static '/base_app/favicon/apple-touch-icon-144x144.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="{% static '/base_app/favicon/apple-touch-icon-60x60.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="{% static '/base_app/favicon/apple-touch-icon-120x120.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="{% static '/base_app/favicon/apple-touch-icon-76x76.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{% static '/base_app/favicon/apple-touch-icon-152x152.png' %}" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-196x196.png' %}" sizes="196x196" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-96x96.png' %}" sizes="96x96" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-32x32.png' %}" sizes="32x32" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-16x16.png' %}" sizes="16x16" />
<link rel="icon" type="image/png" href="{% static '/base_app/favicon/favicon-128.png' %}" sizes="128x128" />
<!-- End of favicon hell -->
<title>REX-DRI</title>
</head>
<body>
<div id="app" class="columns">
<!-- React -->
</div>
</body>
{% block content %}
<script>
var __AppUserId = {{ user.pk }};
var __AllBackendEndPointsRoutes = {{ endpoints|safe }};
</script>
{% render_bundle 'main' %}
{% render_bundle 'vendor' %}
<link rel="stylesheet" href="{{ front_bundle_loc }}mapbox-gl-dist/mapbox-gl.css"/>
<noscript>Cette application nécessite Javascript... Merci de l'activer ou d'utiliser un navigateur approprié.</noscript>
</html>
{% endblock %}
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% block content %}
{% render_bundle 'rgpdRaw' %}
{% render_bundle 'vendor' %}
{% endblock %}
......@@ -16,15 +16,13 @@ class HasAccessTestCase(TestCase):
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.has_validated_cgu_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.has_validated_cgu_rgpd = False
cls.has_not_validated_user.save()
cls.has_validated_client = APIClient()
......@@ -44,4 +42,4 @@ class HasAccessTestCase(TestCase):
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/")
self.assertEqual(response.url, "/cgu-rgpd/?next=/api/")
from django.test import TestCase
from rest_framework.test import APIClient
class HasAccessTestCase(TestCase):
"""
Test to check that the RGPD doc is publicly accessible
"""
def test_unauthentificated_can_access(self):
client = APIClient()
response = client.get("/rgpd-raw/")
self.assertEqual(response.status_code, 200)
......@@ -26,7 +26,8 @@ urlpatterns += [
name="cas_ng_proxy_callback",
),
url(r"^app/.*", views.index),
url(r"^cgu/.*", views.cgu),
url(r"^cgu-rgpd/.*", views.cgu_rgpd),
url(r"^rgpd-raw/.*", views.rgpd_raw),
url(r"^banned_note/", views.banned),
url(r"^$", RedirectView.as_view(url="./app/"), name="go to real home"),
url(r"", include("backend_app.urls")),
......
......@@ -12,6 +12,10 @@ from base_app.models import User
logger = logging.getLogger("django")
def get_bundle_loc(name):
return "/".join(get_files(name)[0]["url"].split("/")[:-1]) + "/"
def index(request):
"""
View to to display the index app that contains the JS / CSS
......@@ -23,7 +27,7 @@ def index(request):
user = request.user
# small hack to get the correct location of the frontend bundled files
front_bundle_loc = "/".join(get_files("main")[0]["url"].split("/")[:-1]) + "/"
front_bundle_loc = get_bundle_loc("main")
# We also retrieve the list of all routes endpoints
endpoints = list(map(lambda v: clean_route(v.end_point_route), ALL_VIEWSETS))
......@@ -34,7 +38,17 @@ def index(request):
)
def cgu(request):
def rgpd_raw(request):
"""
Render the view that displays only the RGPD conditions
"""
return render(request, "rgpd_raw.html")
def cgu_rgpd(request):
"""
Render the view that handles user accepting CGU and RGPD conditions
"""
if request.method == "POST":
user = User.objects.get(pk=request.user.pk)
form = UserForm(request.POST, instance=user)
......@@ -45,11 +59,11 @@ def cgu(request):
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 "next" in request.GET and user.has_validated_cgu_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))
return render(request, "cgu_rgpd.html", dict(user=user, form=form))
def banned(request):
......
......@@ -63,7 +63,7 @@ services:
# Service to handle frontend live developpments and building
frontend:
# Get the image from the registry
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.4.0
image: registry.gitlab.utc.fr/rex-dri/rex-dri/frontend:v0.5.0
# To use a locally built one, comment above, uncomment bellow.
# build: ./frontend
# On startup, we retrieve the dependencies from the image and start the developpement server
......
Conditions générales d'utilisation de la plateforme REX-DRI
===========================================================
Les présentes « Conditions générales d'utilisation » (CGU) ont pour objet l'encadrement juridique des modalités de mise à disposition des services de la plateforme **REX-DRI** à ses utilisateurs.
*Le terme **« utilisateur »** désigne toute personne accédant à la plateforme et qui a accepté les présentes CGU.*
La plateforme REX-DRI a pour finalité le partage de retours d'expérience et d'informations générales sur les échanges universitaires. Ce afin d'aider les étudiants à choisir leurs destinations et vivre au mieux leurs expériences d'échanges (de la candidature au retour en France).
## Valeurs
Les valeurs de la plateforme REX-DRI sont : **bienveillance**, **partage**, **contribution** et **collaboration**. Aidez-nous à les diffuser !
## Responsabilité
Toute contribution sur la plateforme est associée à son contributeur. La responsabilité d'éditeur incombe au contributeur.
L'utilisateur s'engage à ne pas mettre en ligne de contenu volontairement inexact, trompeur, ou pouvant porter atteinte à autrui. Aucun contenu irrespectueux ne sera toléré. *La législation française s'applique à toute contribution.*
En raison du format de la plateforme, le degré de fiabilité des informations mises à disposition est laissé à l'appréciation des utilisateurs ; chacun étant invité à l'améliorer.
## Licences associées aux données
### Définitions des licences
La licence « **REX-DRI-BY** » se définit comme étant une licence CC-BY ([https://creativecommons.org/licenses/by/2.0/fr/](https://creativecommons.org/licenses/by/2.0/fr/)) restreinte dont :
- Le droit à « l'adaptation » est restreint à toute personne physique pouvant accéder à la plateforme REX-DRI ([https://rex.dri.utc.fr](https://rex.dri.utc.fr))
- Le droit au « partage » est restreint au fait que le contenu partagé (adapté ou non) n'est accessible qu'aux utilisateurs de la plateforme REX-DRI ou derrière le système d'authentification central de l'UTC ([https://cas.utc.fr/cas/login](https://cas.utc.fr/cas/login))
La licence « **REX-DRI-PRIVATE** » se définit comme suit :
Sauf mention contraire de votre part, vous détenez tous les droits sur le contenu concerné par cette licence.
Toutefois, vous autorisez le traitement de ce contenu (tant qu'il est accessible) par les administrateurs du site à des fins statistiques.
### Cadre d'utilisation
Sauf mention contraire toute donnée transférée sur la plateforme REX-DRI est sous licence **REX-DRI-BY**.
Pour information, sont notamment concernées par la licence REX-DRI-PRIVATE les listes privées des utilisateurs, et la configuration utilisateur.
## Données personnelles
Se référer à la mention d'information ([https://rex.dri.utc.fr/app/about/rgpd/](https://rex.dri.utc.fr/app/about/rgpd/)).
## Condition de mise à disposition
REX-DRI est une ressource partagée. Afin de garantir le meilleur service possible à tous, aucun ne doit en abuser ou tenter de nuire à toute ou partie de la plateforme.
Tout manquement aux présentes CGU pourra donner lieu à des sanctions proportionnées.
## Mentions légales
Le **SIMDE** (Service informatique de la Maison Des Étudiants -- commission du Bureau Des Étudiants de l'Université de Technologie de Compiègne) met à disposition la plateforme REX-DRI ([https://rex.dri.utc.fr](https://rex.dri.utc.fr)) pour une durée indéterminée.
contact : [simde@assos.utc.fr](mailto:simde@assos.utc.fr)
Le site est hébergé par l'**UTC** (Université de Technologie de Compiègne).
Mention d'information RGPD plateforme REX-DRI
=============================================
Le SIMDE (Service Informatique de la Maison Des Étudiants – commission du Bureau Des Étudiants de l'Université de Technologie de Compiègne) met à disposition la plateforme REX-DRI ([https://rex.dri.utc.fr](https://rex.dri.utc.fr)). Elle a pour finalité le partage de retours d'expérience et d'informations générales sur les échanges universitaires. Ce afin d'aider les étudiants à choisir leurs destinations et vivre au mieux leurs expériences d'échanges (de la candidature au retour en France).
La base légale du traitement est le **consentement** (article 6 du règlement européen 2016/679, dit RGPD - Règlement général sur la protection des données personnelles).
---------------------------------
**Les données traitées sur tout utilisateur de la plateforme sont :**
1. Vos nom **\***, prénom **\***, login UTC **\***, identifiant unique **\*** et adresse mail UTC **\*** fournis par le système d'authentification centralisé du SIMDE ([https://assos.utc.fr/login](https://assos.utc.fr/login)),
2. Si vous les renseignez dans l'application, une adresse mail secondaire et votre pseudo,
3. Si vous en donnez l'autorisation sur l'ENT, les cours que vous avez suivis à l'étranger, associés à votre login UTC,
4. La date de votre dernière connexion,
5. Les données sous licence REX-DRI private qui contiennent des informations à caractère personnel.
6. Les traces de toutes les requêtes effectuées sur la plateforme.
**Les destinataires de ces données sont :**
- Les administrateurs de la plateforme: toutes ces données,
- Les utilisateurs de la plateforme peuvent accéder aux catégories de données 1, 2 et 3 (données d'identification et cours suivis à l'étranger) uniquement après votre consentement explicite.
**Durée de conservation des données**
- 1, 2, 3, 4 et 5 : au plus 5 ans après la dernière connexion,
- 6 : au plus 31 jours.
**Gestion des traces informatiques (6)**
La gestion des traces informatiques est obligatoire et a notamment pour finalité l'analyse de l'utilisation de la plateforme et la détection de défaillances. Passer le délai de 31 jours précédemment mentionné, les traces informatiques sont susceptibles d'être conservées dans un format garantissant l'anonymat des données.
---------------------------------
Conformément au règlement européen 2016/679 dit RGPD, vous pouvez retirer votre consentement à tout moment et demander l'effacement des données vous concernant. Pour cela, rendez vous simplement sur la page prévue à cet effet ([https://rex.dri.utc.fr/app/user/me](https://rex.dri.utc.fr/app/user/me)) et cliquez sur le bouton “Supprimer mon compte”. Vous pouvez à tout moment vous rendre sur l'ENT et supprimer l'autorisation de partage des cours que vous avez suivis lors de votre échange.
Vous disposez également d'un droit d'accès, de rectification et d'opposition aux informations qui vous concernent ainsi qu'un droit à la limitation du traitement et à la portabilité de ces données, droits que vous pouvez exercer en vous adressant au SIMDE ([simde@assos.utc.fr](mailto:simde@assos.utc.fr)).
Pour votre droit à la portabilité de vos données, nous vous invitons à vous rendre dans un premier temps sur l'API ([https://rex.dri.utc.fr/api/](https://rex.dri.utc.fr/api/)) de la plateforme, et à télécharger vos données.
Si vous estimez, après nous avoir contactés, que vos droits sur vos données ne sont pas respectés, vous pouvez adresser une réclamation à la CNIL.
**\***: les données marquées d'un astérisque sont fournies par le système d'authentification centralisé du SIMDE et ne sont pas modifiables via la plateforme REX-DRI.
......@@ -12204,6 +12204,16 @@
}
}
},
"raw-loader": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.0.0.tgz",
"integrity": "sha512-FsELYliOpX5HdPdxa7PzTmEc5OTchmLUs/r4f8oLDGCYE+xC2FjVbDXzdyLcBrdlDnvkx1x5wzphixcWpxJG5w==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0",
"schema-utils": "^1.0.0"
}
},
"react": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
......
......@@ -73,6 +73,7 @@
"node-sass": "^4.12.0",