diff --git a/.env_example b/.env_example index eef94aac..7e1dbd3b 100644 --- a/.env_example +++ b/.env_example @@ -10,7 +10,6 @@ DJANGO_SECRET_KEY=CHANGE_ME DJANGO_SETTINGS_MODULE=note_kfet.settings CONTACT_EMAIL=tresorerie.bde@localhost NOTE_URL=localhost -DOMAIN=localhost # Config for mails. Only used in production NOTE_MAIL=notekfet@localhost diff --git a/README.md b/README.md index 77cff049..26ced30c 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,8 @@ Le cahier des charges initial est disponible [sur le Wiki Crans](https://wiki.cr La documentation des classes et fonctions est directement dans le code et est explorable à partir de la partie documentation de l'interface d'administration de Django. **Commentez votre code !** -La documentation plus haut niveau sur le développement est disponible sur [le Wiki associé au dépôt Git](https://gitlab.crans.org/bde/nk20/-/wikis/home). +La documentation plus haut niveau sur le développement et sur l'utilisation +est disponible sur et également dans le dossier `docs`. ## FAQ diff --git a/apps/api/serializers.py b/apps/api/serializers.py index d59bdc43..c5e6f2b3 100644 --- a/apps/api/serializers.py +++ b/apps/api/serializers.py @@ -4,10 +4,14 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User -from rest_framework.serializers import ModelSerializer +from django.utils import timezone +from rest_framework import serializers +from member.api.serializers import ProfileSerializer, MembershipSerializer +from note.api.serializers import NoteSerializer +from note.models import Alias -class UserSerializer(ModelSerializer): +class UserSerializer(serializers.ModelSerializer): """ REST API Serializer for Users. The djangorestframework plugin will analyse the model `User` and parse all fields in the API. @@ -22,7 +26,7 @@ class UserSerializer(ModelSerializer): ) -class ContentTypeSerializer(ModelSerializer): +class ContentTypeSerializer(serializers.ModelSerializer): """ REST API Serializer for Users. The djangorestframework plugin will analyse the model `User` and parse all fields in the API. @@ -31,3 +35,42 @@ class ContentTypeSerializer(ModelSerializer): class Meta: model = ContentType fields = '__all__' + + +class OAuthSerializer(serializers.ModelSerializer): + """ + Informations that are transmitted by OAuth. + For now, this includes user, profile and valid memberships. + This should be better managed later. + """ + normalized_name = serializers.SerializerMethodField() + + profile = ProfileSerializer() + + note = NoteSerializer() + + memberships = serializers.SerializerMethodField() + + def get_normalized_name(self, obj): + return Alias.normalize(obj.username) + + def get_memberships(self, obj): + return serializers.ListSerializer(child=MembershipSerializer()).to_representation( + obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now())) + + class Meta: + model = User + fields = ( + 'id', + 'username', + 'normalized_name', + 'first_name', + 'last_name', + 'email', + 'is_superuser', + 'is_active', + 'is_staff', + 'profile', + 'note', + 'memberships', + ) diff --git a/apps/api/tests.py b/apps/api/tests.py index 203e592a..9fa371e7 100644 --- a/apps/api/tests.py +++ b/apps/api/tests.py @@ -3,6 +3,7 @@ import json from datetime import datetime, date +from decimal import Decimal from urllib.parse import quote_plus from warnings import warn @@ -152,6 +153,8 @@ class TestAPI(TestCase): value = value.isoformat() elif isinstance(value, ImageFieldFile): value = value.name + elif isinstance(value, Decimal): + value = str(value) query = json.dumps({field.name: value}) # Create sample permission diff --git a/apps/api/urls.py b/apps/api/urls.py index 7131c657..3e61d587 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -5,6 +5,7 @@ from django.conf import settings from django.conf.urls import url, include from rest_framework import routers +from .views import UserInformationView from .viewsets import ContentTypeViewSet, UserViewSet # Routers provide an easy way of automatically determining the URL conf. @@ -47,5 +48,6 @@ app_name = 'api' # Additionally, we include login URLs for the browsable API. urlpatterns = [ url('^', include(router.urls)), + url('^me/', UserInformationView.as_view()), url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')), ] diff --git a/apps/api/views.py b/apps/api/views.py new file mode 100644 index 00000000..9718336d --- /dev/null +++ b/apps/api/views.py @@ -0,0 +1,20 @@ +# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.contrib.auth.models import User +from rest_framework.generics import RetrieveAPIView + +from .serializers import OAuthSerializer + + +class UserInformationView(RetrieveAPIView): + """ + These fields are give to OAuth authenticators. + """ + serializer_class = OAuthSerializer + + def get_queryset(self): + return User.objects.filter(pk=self.request.user.pk) + + def get_object(self): + return self.request.user diff --git a/apps/member/auth.py b/apps/member/auth.py new file mode 100644 index 00000000..888adea1 --- /dev/null +++ b/apps/member/auth.py @@ -0,0 +1,17 @@ +# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from cas_server.auth import DjangoAuthUser # pragma: no cover +from note.models import Alias + + +class CustomAuthUser(DjangoAuthUser): # pragma: no cover + """ + Override Django Auth User model to define a custom Matrix username. + """ + + def attributs(self): + d = super().attributs() + if self.user: + d["normalized_name"] = Alias.normalize(self.user.username) + return d diff --git a/apps/member/migrations/0007_auto_20210313_1235.py b/apps/member/migrations/0007_auto_20210313_1235.py new file mode 100644 index 00000000..01ad2393 --- /dev/null +++ b/apps/member/migrations/0007_auto_20210313_1235.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.19 on 2021-03-13 11:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('member', '0006_create_note_account_bde_membership'), + ] + + operations = [ + migrations.AlterField( + model_name='membership', + name='roles', + field=models.ManyToManyField(related_name='memberships', to='permission.Role', verbose_name='roles'), + ), + migrations.AlterField( + model_name='profile', + name='promotion', + field=models.PositiveSmallIntegerField(default=2021, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'), + ), + ] diff --git a/apps/note/migrations/0005_auto_20210313_1235.py b/apps/note/migrations/0005_auto_20210313_1235.py new file mode 100644 index 00000000..09696c38 --- /dev/null +++ b/apps/note/migrations/0005_auto_20210313_1235.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.19 on 2021-03-13 11:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('note', '0004_remove_null_tag_on_charfields'), + ] + + operations = [ + migrations.AlterField( + model_name='alias', + name='note', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.Note'), + ), + ] diff --git a/apps/permission/backends.py b/apps/permission/backends.py index cde6998d..4b044d80 100644 --- a/apps/permission/backends.py +++ b/apps/permission/backends.py @@ -134,8 +134,6 @@ class PermissionBackend(ModelBackend): return False sess = get_current_session() - if sess is not None and sess.session_key is None: - return False if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42: return True diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 43a8c605..6646029a 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -3511,6 +3511,7 @@ 56, 57, 58, + 135, 137, 143, 147, @@ -3521,8 +3522,7 @@ 176, 177, 180, - 181, - 182 + 181 ] } }, diff --git a/apps/scripts b/apps/scripts index 8ec7d68a..0c7070ae 160000 --- a/apps/scripts +++ b/apps/scripts @@ -1 +1 @@ -Subproject commit 8ec7d68a169c1072aec427925f3bf2fd54eab5a3 +Subproject commit 0c7070aea177e12fae099488e2ea6f8146b97e4d diff --git a/apps/treasury/migrations/0003_auto_20210321_1034.py b/apps/treasury/migrations/0003_auto_20210321_1034.py new file mode 100644 index 00000000..2c36122a --- /dev/null +++ b/apps/treasury/migrations/0003_auto_20210321_1034.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.19 on 2021-03-21 09:34 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('treasury', '0002_invoice_remove_png_extension'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='quantity', + field=models.DecimalField(decimal_places=2, max_digits=7, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'), + ), + migrations.AlterField( + model_name='specialtransactionproxy', + name='remittance', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transaction_proxies', to='treasury.Remittance', verbose_name='Remittance'), + ), + ] diff --git a/apps/treasury/models.py b/apps/treasury/models.py index b2b2596c..a2a61d46 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -5,6 +5,7 @@ from datetime import date from django.contrib.auth.models import User from django.core.exceptions import ValidationError +from django.core.validators import MinValueValidator from django.db import models, transaction from django.db.models import Q from django.template.loader import render_to_string @@ -131,12 +132,15 @@ class Product(models.Model): verbose_name=_("Designation"), ) - quantity = models.PositiveIntegerField( - verbose_name=_("Quantity") + quantity = models.DecimalField( + decimal_places=2, + max_digits=7, + verbose_name=_("Quantity"), + validators=[MinValueValidator(0)], ) amount = models.IntegerField( - verbose_name=_("Unit price") + verbose_name=_("Unit price"), ) @property diff --git a/apps/treasury/templates/treasury/invoice_sample.tex b/apps/treasury/templates/treasury/invoice_sample.tex index b008aac3..f07f00f5 100644 --- a/apps/treasury/templates/treasury/invoice_sample.tex +++ b/apps/treasury/templates/treasury/invoice_sample.tex @@ -1,4 +1,5 @@ -{% load escape_tex %} +{% load escape_tex i18n %} +{% language fr %} \documentclass[a4paper,11pt]{article} \usepackage{fontspec} @@ -176,3 +177,4 @@ TVA non applicable, article 293 B du CGI. \end{center} \end{document} +{% endlanguage %} diff --git a/apps/wei/migrations/0002_auto_20210313_1235.py b/apps/wei/migrations/0002_auto_20210313_1235.py new file mode 100644 index 00000000..cb4dfbb2 --- /dev/null +++ b/apps/wei/migrations/0002_auto_20210313_1235.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.19 on 2021-03-13 11:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='weiclub', + name='year', + field=models.PositiveIntegerField(default=2021, unique=True, verbose_name='year'), + ), + migrations.AlterField( + model_name='weiregistration', + name='information_json', + field=models.TextField(default='{}', help_text='Information about the registration (buses for old members, survey for the new members), encoded in JSON', verbose_name='registration information'), + ), + ] diff --git a/docs/_static/img/create_transaction.png b/docs/_static/img/create_transaction.png new file mode 100644 index 00000000..4805b131 Binary files /dev/null and b/docs/_static/img/create_transaction.png differ diff --git a/docs/api/index.rst b/docs/api/index.rst index 32068d88..fc55fc5c 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -114,7 +114,7 @@ Les filtres disponibles sont indiqués sur chacune des pages de documentation. Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur à le droit de voir sont affichés. Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données. -Une requête à l'adresse ``/api//pk/`` affiche directement les informations du modèle demandé au format JSON. +Une requête à l'adresse ``/api///`` affiche directement les informations du modèle demandé au format JSON. POST ~~~~ diff --git a/docs/apps/activity.rst b/docs/apps/activity.rst index 52f51cbb..f4df6062 100644 --- a/docs/apps/activity.rst +++ b/docs/apps/activity.rst @@ -74,7 +74,7 @@ ne dépende pas de cette application, on procède de cette manière. Graphe ~~~~~~ -.. image:: /_static/img/graphs/activity.svg +.. image:: ../_static/img/graphs/activity.svg :alt: Graphe de l'application activités UI diff --git a/docs/apps/index.rst b/docs/apps/index.rst index d8f07ae1..d1fa1ee0 100644 --- a/docs/apps/index.rst +++ b/docs/apps/index.rst @@ -46,7 +46,7 @@ Applications indispensables Applications packagées ---------------------- * ``polymorphic`` - Utiliser pour la création de models polymorphiques (``Note`` et ``Transaction`` notamment) cf [Note](Note). + Utiliser pour la création de models polymorphiques (``Note`` et ``Transaction`` notamment) cf `Note `_. L'utilisation des models polymorphiques est détaillé sur la documentation du package: ``_ diff --git a/docs/apps/logs.rst b/docs/apps/logs.rst index dc25423c..afb3cf70 100644 --- a/docs/apps/logs.rst +++ b/docs/apps/logs.rst @@ -48,3 +48,10 @@ Exemple de Changelog, pour la création d'une transaction de 42424242 centimes d S'il est préférable de passer en console Postgresql pour parcourir les logs, ils sont trouvables via l'API dans ``/api/logs``, sous réserve d'avoir les droits suffisants (ie. être respo info). + + +Graphe +~~~~~~ + +.. image:: ../_static/img/graphs/logs.svg + :alt: Logs graphe diff --git a/docs/apps/member.rst b/docs/apps/member.rst index e8e57bcd..7ec7cd8d 100644 --- a/docs/apps/member.rst +++ b/docs/apps/member.rst @@ -93,7 +93,7 @@ génère en effet automatiquement une transaction de l'utilisateur vers le club Graphe ------ -.. image:: /_static/img/graphs/member.svg +.. image:: ../_static/img/graphs/member.svg :alt: Graphe de l'application member Adhésions @@ -109,15 +109,15 @@ de fin d'adhésion. On peut ajouter une adhésion à un utilisateur dans un club à tout non adhérent de ce club. La personne en charge d'adhérer quelqu'un choisit l'utilisateur, les rôles au sein du club et la date de début d'adhésion. Cette date de -début d'adhésion doit se situer entre les champs ``club``.``membership_start`` et ``club``.``membership_end``, -si ces champs sont non nuls. Si ``club``.``parent_club`` n'est pas nul, l'utilisateur doit être membre de ce club. -Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur (``club``.``membership_fee_paid`` -centimes pour les élèves et ``club``.``membership_fee_unpaid`` centimes pour les étudiants). La date de fin est calculée +début d'adhésion doit se situer entre les champs ``club.membership_start`` et ``club.membership_end``, +si ces champs sont non nuls. Si ``club.parent_club`` n'est pas nul, l'utilisateur doit être membre de ce club. +Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur (``club.membership_fee_paid`` +centimes pour les élèves et ``club.membership_fee_unpaid`` centimes pour les étudiants). La date de fin est calculée comme ce qui suit : -* Si ``club``.``membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration`` +* Si ``club.membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration`` * Sinon ``club``, ``date_end`` = ``date_start`` + 424242 jours (suffisant pour tenir au moins une vie) -* Si ``club``.``membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club``.``membership_end``) +* Si ``club.membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club.membership_end``) Si l'utilisateur n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est insuffisant. Une fois toute ces contraintes vérifiées, l'adhésion est créée. Une transaction de type @@ -127,13 +127,14 @@ Réadhésions ~~~~~~~~~~~ Pour les clubs nécessitant des adhésions (de durée limitée), il est possible de réadhérer au bout d'un an. Dès lors -que le jour actuel est après ``club``.``membership_start`` + 1 an, ``club``.``membership_start`` et -``club``.membership_end`` sont incrémentés d'un an. +que le jour actuel est après ``club.membership_start`` + 1 an, ``club.membership_start`` et +``club.membership_end`` sont incrémentés d'un an. Il est possible de réadhérer si : -* ``membership``.``date_start`` <= ``today`` <= ``membership``.``date_end`` (l'adhésion en cours est valide) -* ``membership``.``date_start`` < ``club``.``membership_start`` (si la date de début d'adhésion du club est postérieure à la date de début d'adhésion, qui a donc été mise à jour, on a changé d'année) -* Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership``.``date_start`` >= ``club``.``membership_start``) + +* ``membership.date_start`` <= ``today`` <= ``membership.date_end`` (l'adhésion en cours est valide) +* ``membership.date_start`` < ``club.membership_start`` (si la date de début d'adhésion du club est postérieure à la date de début d'adhésion, qui a donc été mise à jour, on a changé d'année) +* Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership.date_start`` >= ``club.membership_start``) Un bouton ``Réadhérer`` apparaît dans la liste des adhésions si le droit est permis et si ces contraintes sont vérifiées. En réadhérant, une nouvelle adhésion est créée pour l'utilisateur avec les mêmes rôles, commençant le lendemain de la diff --git a/docs/apps/note/consumptions.rst b/docs/apps/note/consumptions.rst index 5b91934c..8b2209fe 100644 --- a/docs/apps/note/consumptions.rst +++ b/docs/apps/note/consumptions.rst @@ -9,7 +9,8 @@ Elle est disponible à l'adresse ``/note/consos/``, et l'onglet n'est visible qu moins un bouton. L'affichage, comme tout le reste de la page, est géré avec Boostrap 4. Les boutons que l'utilisateur a le droit de voir sont triés par catégorie. -## Sélection des consommations +Sélection des consommations +--------------------------- Lorsque l'utilisateur commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait, récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur survole un alias, un appel à la page diff --git a/docs/apps/note/index.rst b/docs/apps/note/index.rst index 039efb3a..1291dad9 100644 --- a/docs/apps/note/index.rst +++ b/docs/apps/note/index.rst @@ -19,5 +19,6 @@ transferts/dons entre notes est détaillé sur la page `Transferts `_) +* ``field`` : le champ cible qui pourra être modifié. (tous les champs si vide) Pour savoir si un utilisateur a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprete comme tout ce qui suit @@ -147,5 +147,5 @@ modifiés en comparant l'ancienne et la nouvele instance. Graphe des modèles ------------------ -.. image:: /_static/img/graphs/permission.svg +.. image:: ../_static/img/graphs/permission.svg :alt: Graphe de l'application permission diff --git a/docs/apps/treasury.rst b/docs/apps/treasury.rst index 23a1c0ff..5fc5d6b2 100644 --- a/docs/apps/treasury.rst +++ b/docs/apps/treasury.rst @@ -217,5 +217,6 @@ Exemple de validation de crédit Société générale d'un étudiant non payé " Diagramme des modèles --------------------- -.. image:: /_static/img/graphs/treasury.svg - :alt: Graphe de l'application trésorerie \ No newline at end of file +.. image:: ../_static/img/graphs/treasury.svg + :width: 960 + :alt: Graphe de l'application trésorerie diff --git a/docs/apps/wei.rst b/docs/apps/wei.rst index 892c0a87..51d3375b 100644 --- a/docs/apps/wei.rst +++ b/docs/apps/wei.rst @@ -113,7 +113,8 @@ Graphe des modèles Pour une meilleure compréhension, le graphe des modèles de l'application ``member`` ont été ajoutés au schéma. -.. image:: /_static/img/graphs/wei.svg +.. image:: ../_static/img/graphs/wei.svg + :width: 960 :alt: Graphe des modèles de l'application WEI Fonctionnement diff --git a/docs/conf.py b/docs/conf.py index 51010fc0..50e96fdd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = 'Note Kfet 2020' -copyright = '2020, BDE ENS Paris-Saclay' +copyright = '2020-2021, BDE ENS Paris-Saclay' author = 'BDE ENS Paris-Saclay' # The full version, including alpha/beta/rc tags diff --git a/docs/documentation.rst b/docs/documentation.rst new file mode 100644 index 00000000..d1e182fd --- /dev/null +++ b/docs/documentation.rst @@ -0,0 +1,37 @@ +Documentation +============= + +La documentation est gérée grâce à Sphinx. Le thème est le thème officiel de +ReadTheDocs ``sphinx-rtd-theme``. + +Générer localement la documentation +----------------------------------- + +On commence par se rendre au bon endroit et installer les bonnes dépendances : + +.. code:: bash + + cd docs + pip install -r requirements.txt + +La documentation se génère à partir d'appels à ``make``, selon le type de +documentation voulue. + +Par exemple, ``make dirhtml`` construit la documentation web, +``make latexpdf`` construit un livre PDF avec cette documentation. + + +Documentation automatique +------------------------- + +Ansible compile et déploie automatiquement la documentation du projet, dans +le rôle ``8-docs``. Le rôle installe dans le bon environnement les dépendances +nécessaires, puis appelle sphinx pour placer la documentation compilée dans +``/var/www/documentation`` : + +.. code:: bash + + /var/www/note_kfet/env/bin/sphinx-build -b dirhtml /var/www/note_kfet/docs/ /var/www/documentation/ + +Ce dossier est exposé par ``nginx`` sur le chemin +`/doc `_. diff --git a/docs/external_services/cas.rst b/docs/external_services/cas.rst new file mode 100644 index 00000000..36dedf3e --- /dev/null +++ b/docs/external_services/cas.rst @@ -0,0 +1,80 @@ +Service d'Authentification Centralisé (CAS) +=========================================== + +Un `CAS `_ est +déployé sur la Note Kfet. Il est accessible à l'adresse ``_. +Il a pour but uniquement d'authentifier les utilisateurs via la note et ne communique +que peu d'informations. + +Configuration +------------- + +Le serveur CAS utilisé est implémenté grâce au paquet ``django-cas-server``. Il peut être +installé soit par PIP soit sur une machine Debian via +``apt install python3-django-cas-server``. + +On ajoute ensuite ``cas_server`` aux applications Django installées. On n'oublie pas ni +d'appliquer les migrations (``./manage.py migrate``) ni de collecter les fichiers +statiques (``./manage.py collectstatic``). + +On enregistre les routes dans ``note_kfet/urls.py`` : + +.. code:: python + + urlpatterns.append( + path('cas/', include('cas_server.urls', namespace='cas_server')) + ) + +Le CAS est désormais déjà prêt à être utilisé. Toutefois, puisque l'on utilise un site +Django-admin personnalisé, on n'oublie pas d'enregistrer les pages d'administration : + +.. code:: python + + if "cas_server" in settings.INSTALLED_APPS: + from cas_server.admin import * + from cas_server.models import * + admin_site.register(ServicePattern, ServicePatternAdmin) + admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin) + +Enfin, on souhaite pouvoir fournir au besoin le pseudo normalisé. Pour cela, on crée une +classe dans ``member.auth`` : + +.. code:: python + + class CustomAuthUser(DjangoAuthUser): + def attributs(self): + d = super().attributs() + if self.user: + d["normalized_name"] = Alias.normalize(self.user.username) + return d + + +Puis on source ce fichier dans les paramètres : + +.. code:: python + + CAS_AUTH_CLASS = 'member.auth.CustomAuthUser' + +Utilisation +----------- +Le service est accessible sur ``_. C'est ce lien qu'il faut +donner à votre application. + +L'application doit néanmoins être autorisée à accéder au CAS. Pour cela, rendez-vous +dans Django-admin (``_), dans +``Service Central d'Authentification/Motifs de services``, ajoutez une nouvelle entrée. +Choisissez votre position favorite puis le nom de l'application. + +Les champs importants sont les deux suivants : + +* **Motif :** il s'agit d'une expression régulière qui doit reconnaitre le site voulu. + Par exemple, pour autoriser Belenios (``_), on rentrera + le motif ``^https?://belenios\.crans\.org/.*$``. +* **Champ d'utilisateur :** C'est le pseudo que renverra le CAS. Par défaut, il s'agira + du nom de note principal, mais il arrive parfois que certains sites supportent mal + d'avoir des caractères UTF-8 dans le pseudo. C'est par exemple le cas de Belenios. + On rentrera alors ``normalized_name`` dans ce champ, qui correspond à la version + normalisée (sans accent ni espace ni aucun caractère non-ASCII) du pseudo, et qui + suffit à identifier une personne. + +On peut également utiliser le ``Single log out`` si besoin. diff --git a/docs/external_services/index.rst b/docs/external_services/index.rst new file mode 100644 index 00000000..ab1539aa --- /dev/null +++ b/docs/external_services/index.rst @@ -0,0 +1,28 @@ +Applications externes +===================== + +.. toctree:: + :maxdepth: 2 + :caption: Applications externes + + cas + oauth2 + +.. warning:: + L'utilisation de la note par des services externes est actuellement en beta. Il est + fort à parier que cette utilisation sera revue et améliorée à l'avenir. + +Puisque la Note Kfet recense tous les comptes des adhérents BDE, les clubs ont alors +la possibilité de développer leurs propres applications et de les interfacer avec la +note. De cette façon, chaque application peut authentifier ses utilisateurs via la note, +et récupérer leurs adhésion, leur nom de note afin d'éventuellement faire des transferts +via l'API. + +Deux protocoles d'authentification sont implémentées : + + * `CAS `_ + * `OAuth2 `_ + +À ce jour, il n'y a pas encore d'exemple d'utilisation d'application qui utilise ce +mécanisme, mais on peut imaginer par exemple que la Mediatek ou l'AMAP implémentent +ces protocoles pour récupérer leurs adhérents. diff --git a/docs/external_services/oauth2.rst b/docs/external_services/oauth2.rst new file mode 100644 index 00000000..3f1eee2c --- /dev/null +++ b/docs/external_services/oauth2.rst @@ -0,0 +1,133 @@ +OAuth2 +====== + +L'authentification `OAuth2 `_ est supportée par la +Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateurs, mais aussi +de transmettre des informations à un service tiers tels que des informations personnelles, +le solde de la note ou encore les adhésions de l'utilisateur, en l'avertissant sur +quelles données sont effectivement collectées. + +.. danger:: + L'implémentation actuelle ne permet pas de choisir quels droits on offre. Se connecter + par OAuth2 offre actuellement exactement les mêmes permissions que l'on n'aurait + normalement, avec le masque le plus haut, y compris en écriture. + + Faites alors très attention lorsque vous vous connectez à un service tiers via OAuth2, + et contrôlez bien exactement ce que l'application fait de vos données, à savoir si + elle ignore bien tout ce dont elle n'a pas besoin. + + À l'avenir, la fenêtre d'authentification pourra vous indiquer clairement quels + paramètres sont collectés. + +Configuration du serveur +------------------------ + +On utilise ``django-oauth-toolkit``, qui peut être installé grâce à PIP ou bien via APT, +via le paquet ``python3-django-oauth-toolkit``. + +On commence par ajouter ``oauth2_provider`` aux applications Django installées. On +n'oublie pas ni d'appliquer les migrations (``./manage.py migrate``) ni de collecter +les fichiers statiques (``./manage.py collectstatic``). + +On souhaite que l'API gérée par ``django-rest-framework`` puisse être accessible via +l'authentification OAuth2. On adapte alors la configuration pour permettre cela : + +.. code:: python + + REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.TokenAuthentication', + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + ... + ], + ... + } + +On ajoute les routes dans ``urls.py`` : + +.. code:: python + + urlpatterns.append( + path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')) + ) + +L'OAuth2 est désormais prêt à être utilisé. + + +Configuration client +-------------------- + +Contrairement au `CAS `_, n'importe qui peut en théorie créer une application OAuth2. +En théorie, car pour l'instant les permissions ne leur permettent pas. + +Pour créer une application, il faut se rendre à la page +`/o/applications/ `_. Dans ``client type``, +rentrez ``public`` (ou ``confidential`` selon vos choix), et vous rentrerez +généralement ``authorization-code`` dans ``Authorization Grant Type``. +Le champ ``Redirect Uris`` contient une liste d'adresses URL autorisées pour des +redirections post-connexion. + +Il vous suffit de donner à votre application : + +* L'identifiant client (client-ID) +* La clé secrète +* Les scopes : sous-ensemble de ``[read, write]`` (ignoré pour l'instant, cf premier paragraphe) +* L'URL d'autorisation : ``_ +* L'URL d'obtention de jeton : ``_ +* L'URL de récupération des informations de l'utilisateur : ``_ + +N'hésitez pas à consulter la page ``_ pour s'imprégner +du format renvoyé. + +Avec Django-allauth +################### + +Si vous utilisez Django-allauth pour votre propre application, vous pouvez utiliser +le module pré-configuré disponible ici : +``_. Pour l'installer, vous +pouvez simplement faire : + +.. code:: bash + + $ pip3 install git+https://gitlab.crans.org/bde/allauth-note-kfet.git + +L'installation du module se fera automatiquement. + +Il vous suffit ensuite d'inclure l'application ``allauth_note_kfet`` à vos applications +installées (sur votre propre client), puis de bien ajouter l'application sociale : + +.. code:: python + + SOCIALACCOUNT_PROVIDERS = { + 'notekfet': { + # 'DOMAIN': 'note.crans.org', + }, + ... + } + +Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, il +se connectera à ``note.crans.org`` si vous ne renseignez rien. + +En créant l'application sur la note, vous pouvez renseigner +``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection, +à adapter selon votre configuration. + +Vous devrez ensuite enregistrer l'application sociale dans la base de données. +Vous pouvez passer par Django-admin, mais cela peut nécessiter d'avoir déjà un compte, +alors autant le faire via un shell python : + +.. code:: python + + from allauth.socialaccount.models import SocialApp + SocialApp.objects.create( + name="Note Kfet", + provider="notekfet", + client_id="VOTRECLIENTID", + secret="VOTRESECRET", + key="", + ) + +Si vous avez bien configuré ``django-allauth``, vous êtes désormais prêts par à vous +connecter via la note :) Par défaut, nom, prénom, pseudo et adresse e-mail sont +récupérés. Les autres données sont stockées mais inutilisées. diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 00000000..d7971971 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,208 @@ +Foire aux questions +=================== + +Des transactions anormales sont apparues sur mon compte. +-------------------------------------------------------- + +.. note:: + Tu dois immédiatement contacter les trésoriers du BDE (voir ci-dessous) pour + signaler l'incident. Précise bien ton nom de note, l'heure de la transaction + ainsi que l'alias utilisé pour faire la transaction (en plaçant ta souris sur + ton pseudo sur la ligne de transaction, l'alias utilisé apparaît). La raison + la plus courante est que tu as un alias qui est trop proche d'un autre d'une + autre personne. Même si la Note Kfet 2020 essaie d'éviter ça, tu es invité⋅e + à supprimer l'alias problématique, ou tout du moins t'assurer que la confusion + ne puisse plus arriver. + + +Je souhaite consommer mais le solde de ma note est insuffisant +-------------------------------------------------------------- + +.. note:: + Le BDE ne fait pas crédit à ses adhérents. Il est de ton devoir de t'assurer + d'avoir en permanence un solde positif sur ta note. Les permanenciers à la + Kfet ont la possibilité de refuser une consommation qui fait passer en négatif, + et ont obligation de refuser si la consommation est alcoolisée, en accord avec + la règlementation en vigueur. + + Les trésoriers connaissent la liste des personnes en situation irrégulière et + n'hésiteront pas à faire des rappels pour recharger la note. + + +Comment recharger ma note ? +--------------------------- + +.. note:: + Le solde de la note peut être rechargé soit par espèces, par chèque à l'ordre + de l'amicale des élèves de l'ENS Cachan, par carte bancaire via un terminal + de paiement électronique ou encore par virement bancaire, dont les coordonnées + sont à demander auprès des trésoriers BDE. + + Les trois premières options sont à faire directement dans la Kfet. + + +Je pars en stage / en vacances. Puis-je bloquer ma note ? +--------------------------------------------------------- + +.. note:: + Bien sûr : il te suffit de te rendre sur ton compte et de cliquer sur le bouton + dédié. Ta note ne sera plus affichée par les autres personnes et les transferts + seront impossibles, sauf pour les trésoriers BDE et respo info. + + Il est toutefois de ton devoir de rembourser tout ce que tu dois. + + +Quelle est la limite maximale au nombre d'alias d'une note ? +------------------------------------------------------------ + +.. note:: + Certains parlent d'une dizaine d'alias par note. + + Sois conscient⋅e qu'ajouter des alias ne peut qu'augmenter la probabilité de + collisions avec une autre note, et peut aussi retarder la livraison de ta + commande lors d'un perm bouffe. + + +Je suis trésorier d'un club, qu'ai-je le droit de faire ? +--------------------------------------------------------- + +.. note:: + Être trésorier d'un club donne la responsabilité de gérer la trésorerie du + club, et donc de gérer sa note. Vous obtenez donc le droit d'effectuer + n'importe quelle transaction via la note en provenance ou à destination de + la note de votre club. Vous pouvez également gérer les adhésions de votre club, + en permettant à n'importe quel adhérent BDE de rejoindre votre club, en prélevant + d'éventuels frais d'adhésion. Les paramètres du club peuvent être également modifiés. + +.. danger:: + Avoir des droits sur la Note Kfet ne signifie pas que vous devez les utiliser. + Chaque opération nécessitant des droits doit être fait pour une bonne raison, + et doit avoir un lien avec votre club. Vous n'avez par exemple pas le droit + d'aller récupérer des informations personnelles d'adhérents pour une raison + personnelle. En revanche, faire le lien entre nom/prénom et nom de note est + bien sûr permis pour faciliter des transferts. Tout abus de droits constaté + pourra mener à des sanctions prises par le bureau du BDE. + +.. warning:: + Une fonctionnalité pour permettre de gérer plus proprement les remboursements + entre amis est en cours de développement. Temporairement et pour des raisons + de confort, les trésoriers de clubs ont le droit de prélever n'importe quelle + adhérente vers n'importe quelle autre note adhérente, tant que la source ne + descend pas sous ``- 50 €``. Ces droits seront retirés d'ici quelques semaines. + + +Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire des transactions +--------------------------------------------------------------------------------------------------- + +.. note:: + As-tu bien vérifié que tu t'es connecté⋅e initialement avec tous tes droits ? + Sinon, si tes droits sont tout récents, tu dois te déconnecter et te reconnecter + pour que tes droits soient bien pris en compte. + + La Note permet de se connecter avec différents filtres de permission afin de + pouvoir prêter son ordinateur avec une session ouverte pour faire quelques + opérations en empêchant l'accès à des opérations trop sensibles. + + +Je suis trésorier d'un club. Puis-je créer un bouton ? +------------------------------------------------------ + +.. note:: + Oui bien sûr ! Tant qu'il redirige bien vers la note de ton club. + + Pour cela, rends-toi à la page ``_ pour afficher la liste des + boutons, puis tu auras accès à l'interface pour créer un bouton. Une fois le + bouton créé, il apparaîtra dans l'onglet ``Consommations``. + + Il faut noter que tant qu'il n'y a pas de boutons visibles pour ton club, tu + n'auras pas accès à l'interface de consommations, et tu devras nécessairement + cliquer sur le lien ci-dessus pour accéder à l'interface d'édition des boutons. + Une fois qu'un bouton pour ton club est visible, l'interface consommations + devient accessible. + + +Après passation, je suis trésorier d'un club. Comment récupérer mes droits note ? +--------------------------------------------------------------------------------- + +.. note:: + Tu dois pour cela contacter les trésoriers BDE (voir ci-dessous). Ils vous + expliqueront en détails vos droits et vos interdits et vous donneront les + droits requis. + + +Je souhaite contacter un trésorier +---------------------------------- + +.. note:: + Pour contacter un trésorier, il te suffit d'envoyer un mail à l'adresse + `tresorerie.bde@lists.crans.org `_. Pense bien + à donner ton nom de note, voire à envoyer un scan de ta carte d'identité si ta + demande concerne un virement entre le compte du BDE et ton propre compte. + + +J'ai trouvé un bug, comment le signaler ? +----------------------------------------- + +.. note:: + La Note Kfet est développée bénévolement par des membres du BDE. Malgré tous nos + efforts pour fournir une plateforme sans erreur et la plus ergonomique possible. + Toutefois, il n'est évidemment pas exclu que des bugs soient présents :) + + Pour nous soumettre un bug, tu peux envoyer un mail à + `notekfet2020@lists.crans.org `_ + + Tu peux sinon ouvrir une issue sur `Gitlab `_. + + N'hésite pas à venir en discuter avec nous à la Kfet, ou bien sur + `l'IRC du Crans `_ sur le canal ``#note`` ! + + Le même processus s'applique en cas de demande de fonctionnalités. + + +Je souhaite contribuer +---------------------- + +.. note:: + La Note Kfet est essentiellement développée par des responsables informatiques du + BDE de l'ENS Paris-Saclay. Toutefois, si vous souhaitez contribuer, vous pouvez + bien sûr le faire en accord avec la licence GPLv3 avec laquelle la Note Kfet est + distribuée. Pour cela, si vous êtes adhérent Crans, vous pouvez proposer une + demande de fusion de votre code. Si ce n'est pas le cas, vous pouvez envoyer un + mail à `notekfet2020@lists.crans.org `_. + Dans les deux cas, merci de rejoindre le canal ``#note`` sur IRC :) + + +Contributeurs +------------- + +.. note:: + La version 2020 de la Note Kfet a été développée sous le mandat de la + Saper[list]popette. Son développement a commencé à l'été 2019. Après un mois de beta + à l'été 2020, son déploiement en production s'est fait le samedi 5 septembre 2020. + + Elle succède à presque 6 années de la + `Note Kfet 2015 `_, alors en production + depuis le 6 octobre 2014. + + Liste des contributeurs majeurs, par ordre alphabétique : + + * Pierre-André « PAC » COMBY + * Yohann « ÿnérant » D'ANELLO + * Benjamin « esum » GRAILLOT + * Alexandre « erdnaxe » IOOSS + + +Hébergement +----------- + +.. note:: + En accord entre de l'ENS Paris-Saclay et le Crans, l'instance de production présente + sur ``_ est hébergée sur l'un des serveurs du Crans. + Les données sont hébergées à l'adresse : + + .. code:: + + Association Crans + ENS Paris-Saclay + 4 Avenue des Sciences + 91190 Gif-sur-Yvette diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 00000000..77b83ab4 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,115 @@ +La note, c'est quoi ? +===================== + +La Note Kfet est un porte-monnaie virtuel proposé gratuitement à tous les adhérents BDE. +C'est le moyen de paiement privilégié au sein du campus, que ce soit pour payer des +activités du BDE, ou bien pour faire des remboursements entre amis. La note contient +également la base d'adhérents du BDE et contient de nombreux outils facilitant la vie +des différents bureaux de clubs, en particulier les trésoriers et surtout les trésoriers +BDE. + +La Note Kfet est accessible à l'adresse ``_. La version actuelle +a été développée par le BDE 2020-2021, et est maintenue par les respos infos du BDE de +l'ENS Paris-Saclay. + + +Fonctionnement général de la note +--------------------------------- + +C'est quoi une note ? +~~~~~~~~~~~~~~~~~~~~~ + +Chaque adhérent⋅e BDE dispose d'un compte appelé « note », créé lors de son adhésion. +Une note est associée à un solde en euros, et à un pseudo appelé « nom de note ». +Le solde associé à la note correspond au solde de l'adhérent⋅e, avec lequel il est +possible de payer tout ce qui est en lien avec le BDE. Le nom de note suffit à +identifier une personne, et il suffit par exemple de donner ce nom à un permanencier +à la Kfet pour acheter un produit. + +Faire une transaction +~~~~~~~~~~~~~~~~~~~~~ + +Tout⋅e adhérent⋅e peut faire une transaction de sa note vers n'importe quelle note +d'un⋅e autre adhérent⋅e, pourvu que le montant de la transaction n'excède pas son +propre solde. Pour cela, il suffit d'aller sur la page de transferts, qui est la +page par défaut après connexion : ``_ +Le formulaire pour effectuer un transfert apparaît alors. En cliquant sur le bouton +« Je suis l'émetteur », le champ « Émetteurs » est directement complété avec votre +propre note. Il vous suffit alors de remplir le champ « Destinataires » avec le ou +les noms de note que vous voulez créditer, de spécifier le montant et la raison de +votre transfert, puis de cliquer sur le bouton « Virement ». Après quelques secondes, +si votre solde est suffisant et que la transaction est acceptée, un message de +confirmation apparaîtra et la transaction apparaîtra dans l'historique ci-dessous. + +.. image:: _static/img/create_transaction.png + :alt: Créer une transaction + + +Consulter ses données personnelles +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +La Note Kfet sert non seulement à faire des transactions d'argent virtuel au sein +du BDE, mais aussi à permettre au BDE et à ses clubs de gérer leurs adhérents. +À cet effet, diverses informations personnelles sont collectées par la Note. + +Pour accéder à votre compte, une fois connecté⋅e, rendez-vous dans le menu en haut +à droite où se trouve votre pseudo. Votre solde est par ailleurs affiché à côté +de votre pseudo. + +L'interface est divisée en trois parties : + + * Vos données personnelles dans la partie de gauche + * Vos adhésions actives (si existantes) dans la partie haute + * Vos transactions récentes dans la partie basse + +Vous pouvez modifier vos données personnelles que la Note possède sur vous en +cliquant à tout moment sur le bouton « Modifier le profil ». Les informations +présentes sont : + + * Nom + * Prénom + * Pseudo + * Adresse e-mail + * Numéro de téléphone + * Section + * Département + * Promotion + * Adresse + * Élève/étudiant + * Inscription aux listes de diffusion du BDE, du BDA et du BDS + +Les trois premières informations sont obligatoires pour pouvoir vous contacter +et pouvoir tenir un registre d'adhérent. + +Le numéro de téléphone et l'adresse ne sont utilisés uniquement en cas d'urgence, +notamment en cas de WEI, et ne sont pas des champs obligatoires. De plus, +promotion, département et section ne sont utilisés sérieusement que en cas +de WEI, et ne sont pas utilisés sinon. + +La distinction élève/étudiant permet de distinguer les achats qui peuvent avoir +des prix différents pour les élèves et pour les étudiants, notamment en cas de +transferts d'argent, de WEI ou d'adhésion à certains clubs. + +Hormis le pseudo, toutes ces informations ont un caractère confidentiel qui les +rend privées et inaccessible aux utilisateurs. Les trésoriers BDE et les respos +info ont accès à toutes les informations (en cas de besoin uniquement, le cas +contraire est considéré comme de l'abus de droit qui est punissable), et les +trésoriers de club ont accès au nom et au prénom afin de faciliter l'association +à un nom de note. + +Dans la partie informations personnelles, il est également possible de modifier +la liste de ses alias (voir la section sur les alias), et de voir son propre +solde. Il est enfin possible de modifier l'image associée à la note, qui n'a +quant à elle pas de caractère privée puisqu'elle est affichée à chaque recherche +de nom de note. + +Le tableau des adhésions actives sur la partie du haut contient l'ensemble des +clubs auxquels vous êtes adhérent⋅es, avec les dates de début, de fin, votre +rôle au sein du club (généralement simple membre, mais cela peut également +être trésorier⋅ère par exemple) ainsi que la cotisation que vous avez éventuellement +payée au club. + +L'historique des transactions contient l'ensemble des transactions qui ont fait +intervenir votre note, triées par date décroissante. Il est possible en cliquant +sur le lien « Historique des transactions » d'avoir un meilleur aperçu, notamment +pour pouvoir filtrer des transactions. diff --git a/docs/index.rst b/docs/index.rst index 766053aa..2e7a2e2c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,8 +2,9 @@ Documentation de la Note Kfet 2020 ================================== -Bienvenue sur le Wiki de la NoteKfet2020. Ce wiki est plutot orienté vers un public de développeur, qui souhaitent -contribuer au développement, mais expliquent également aux plus curieux comment fonctionne la NoteKfet2020 sous le capot. +Bienvenue sur la documentation de la Note Kfet 2020. Cette documentation est à la fois +destinée aux adhérents BDE pour découvrir le fonctionnement de la note, mais aussi aux +respos info qui souhaitent découvrir comment fonctionne la note sous le capot. Des informations complémentaires sont également disponibles sur le `Wiki Crans `_. @@ -11,4 +12,11 @@ Des informations complémentaires sont également disponibles sur le `Wiki Crans :maxdepth: 2 :caption: Développement de la NK20 + getting_started apps/index + install-dev + install + documentation + scripts + faq + external_services/index diff --git a/docs/install-dev.rst b/docs/install-dev.rst new file mode 100644 index 00000000..d3e616b2 --- /dev/null +++ b/docs/install-dev.rst @@ -0,0 +1,169 @@ +Installer la Note Kfet sur sa propre machine +============================================ + +Jamais en production tu ne coderas. + +Pour pouvoir développer sur la Note, il est donc essentiel de pouvoir installer +localement une instance de la note, avec une base de données vierge et indépendante +de celle utilisée en production. + +Toutes les dépendances Python seront installées dans un environnement virtuel, +afin de ne pas polluer sa machine de dépendances de la note. + + +Dépendances de base +------------------- + +On a néanmoins besoin de dépendances de base. + +Sur un Ubuntu/Debian : + +.. code:: bash + + $ sudo apt update + $ sudo apt install --no-install-recommends -y \ + python3-setuptools python3-venv python3-dev \ + texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git + +Pour Arch Linux : + +.. code:: bash + + $ sudo pacman -Sy python-setuptools python-virtualenv \ + texlive-most gettext git + +Bootstrap 4 n'est pas dans les paquets officiels de Arch Linux, mais peut-être +trouvé dans l'AUR grâce au paquet ``bootstrap``. Néanmoins, il faut pour l'instant +penser à créer un lien symbolique : +``sudo ln -s /usr/share/javascript/bootstrap /usr/share/javascript/bootstrap4``. + +Néanmoins, font-awesome n'est pas disponible dans les paquets Arch Linux, mais non +essentiel en développement (n'ajoute que les symbole à côté des boutons). + +À noter que bootstrap et texlive sont optionnels également selon vos besoins. + + +Téléchargement de la note +------------------------- + +On récupère le dépôt Git de la note : + +.. code:: bash + + $ git clone git@gitlab.crans.org:bde/nk20.git + Clonage dans 'nk20'... + remote: Enumerating objects: 203, done. + remote: Counting objects: 100% (203/203), done. + remote: Compressing objects: 100% (125/125), done. + remote: Total 13438 (delta 98), reused 170 (delta 74), pack-reused 13235 + Réception d\'objets: 100% (13438/13438), 9.39 Mio | 5.50 Mio/s, fait. + Résolution des deltas: 100% (9028/9028), fait. + $ cd nk20 + +.. note:: + + Pour apprendre à utiliser Git, on peut regarder la page wiki dédiée : + ``_, + ou bien regarder les séminaires Crans dédiés : + ``_ + + +Création d'un environnement virtuel Python +------------------------------------------ + +Une fois le projet cloné, on peut créer un environnement virtuel qui contiendra +toutes les dépendances Python. + +Pour cela, on peut simplement faire : + +.. code:: bash + + $ python3 -m venv env + $ source env/bin/activate + (env) $ + +À noter que ``source`` peut s'abbréger par ``.`` uniquement. + +Vous êtes donc dans un environnement virtuel Python. Pour installer les dépendances +de la note : + +.. code:: bash + + (env) $ pip install -r requirements.txt + +Les dépendances s'installeront ensuite dans le dossier ``env``. + +Au besoin, l'environnement peut être quitté en tapant ``deactivate`` et rejoint en +resourçant ``env/bin/activate``. + +Plus d'informations sur les environnements virtuels dans la documentation officielle +de Python : ``_. + + +Lancement de la note +-------------------- + +La partie Python (qui peut s'appliquer au développement de n'importe quel projet +Python) est terminée, on peut commencer à s'occuper de Django. + +Pour rappel, Django est un cadre de développement web open source en Python extrêment +puissant. Il a pour but de rendre le développement web simple et rapide. + +La documentation officielle de Django, complète, excellente et même en français, +peut être trouvée ici : ``_. + +Pour lancer un serveur de développement, on peut donc commencer par compiler les +traductions : + +.. code:: bash + + (env) $ ./manage.py compilemessages + +On applique les migrations de la base de données (de test, qui sera créée) : + +.. code:: bash + + (env) $ ./manage.py migrate + +On importe quelques données de base et utiles : + +.. code:: bash + + (env) $ ./manage.py loaddata initial + +.. note:: + + Ces données sont stockées au format JSON dans les différents fichiers + ``apps/{app}/fixtures/initial.json``. + +Enfin, on peut lancer le serveur web de développement : + +.. code:: bash + + (env) $ ./manage.py runserver + Watching for file changes with StatReloader + Performing system checks... + + System check identified no issues (0 silenced). + April 15, 2021 - 15:14:37 + Django version 2.2.20, using settings 'note_kfet.settings' + Starting development server at http://127.0.0.1:8000/ + Quit the server with CONTROL-C. + +Ouvrez votre navigateur, tapez ``_, enjoy :) + +.. note:: + + En lançant le serveur web de la sorte, Django va recevoir un signal dès lors qu'un + fichier a été modifié. Vous n'avez donc pas besoin de redémarrer le serveur après + chaque modification, sauf erreurs. + + Attention : ce serveur n'est destiné qu'à des fins de développement et n'est pas + optimisé pour recevoir des requêtes en parallèle ou être utilisé en production. + + +Créer un super-utilisateur +-------------------------- + +La commande ``./manage.py createsuperuser`` vous permettra de créer un super-utilisateur +initial. diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..09d1e539 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,640 @@ +Installer la Note Kfet en production +==================================== + +Cette page détaille comment installer la Note Kfet sur un serveur de production, +dédié uniquement à l'utilisation de la note. On supposera que le serveur tourne +avec un Debian Buster à jour. + + +Ajout des dépôts buster-backports +--------------------------------- + +Debian c'est bien, c'est stable, mais les paquets sont vite obsolètes. +En particulier, la version stable de Django dans la version stable de Debian est la +version 1.11, qui n'est plus maintenue par Django depuis déjà quelques mois. +Les versions stables de Django sont les versions 2.2 et 3.2, Debian Bullseye +utilisant la version 2.2. + +Afin de permettre à ses utilisateurs d'utiliser les dernières fonctionnalités de +certains paquets, Debian a créé une distribution particulière, appelée +``buster-backports``. Cette distribution contient des paquets de la distribution +à venir « ``testing`` » (``bullseye`` à l'heure où cette documentation est écrite) +recompilés et réadapter pour fonctionner avec les paquets de la distribution stable +(``buster``). Ce qui nous intéresse est de pouvoir récupérer Django 2.2 depuis +cette distribution, et avoir donc une version maintenue de Django. + +Plus de détails sur le wiki de Debian : ``_. + +Pour activer les backports, il suffit d'ajouter dans le fichier ``/etc/apt/sources.list`` : + +.. code:: + + deb $MIRROR/debian buster-backports main contrib + +où ``$MIRROR`` est votre miroir Debian favori, comme ``http://ftp.debian.org`` ou +``http://mirror.crans.org`` pour ce qui est de la version utilisée en production au BDE +sur les serveurs du Crans. Il suffit ensuite de faire un ``sudo apt update``. +Vérifiez que les paquets sont bien récupérés, en cherchant cette ligne : + +.. code:: + + Get:4 http://mirror.crans.org/debian buster-backports InRelease [46.7 kB] + +.. warning:: + + Avis aux futurs respos info : pensez à bien actualiser cette documentation lorsque + Debian Bullseye sera sorti. En particulier, il ne sera pas déconnant de continuer + à utiliser non pas buster-backports mais bullseye-backports pour installer la + note avec Django 3.2 et non Django 2.2. + + Bien sûr, vous testerez sur un serveur qui n'est pas celui utilisé avant :) + + +Installation des dépendances nécessaires +---------------------------------------- + +On s'efforce pour récupérer le plus possible de dépendances via les paquets Debian +plutôt que via ``pip`` afin de faciliter les mises à jour et avoir une installation +plus propre. On peut donc installer tout ce dont on a besoin, depuis buster-backports : + +.. code:: bash + + $ sudo apt update + $ sudo apt install -t buster-backports --no-install-recommends \ + gettext git ipython3 \ # Dépendances basiques + fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web + python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \ + python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \ + python3-djangorestframework python3-memcache python3-phonenumbers \ + python3-pil python3-pip python3-psycopg2 python3-setuptools python3-venv \ + texlive-xetex memcached + +Ces paquets fournissent une bonne base sur laquelle travailler. + +Pour les mettre à jour, il suffit de faire ``sudo apt update`` puis ``sudo apt upgrade``. + + +Téléchargement de la note +------------------------- + +Tout comme en développement, on utilise directement le Gitlab du Crans pour récupérer +les sources. + +On suppose que l'on veut cloner le projet dans le dossier ``/var/www/note_kfet``. + +On clone donc le dépôt en tant que ``www-data`` : + +.. code:: bash + + $ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git /var/www/note_kfet + +Par défaut, le dépôt est configuré pour suivre la branche ``master``, qui est la branche +stable, notamment installée sur ``_. Pour changer de branche, +notamment passer sur la branche ``beta`` sur un serveur de pré-production (un peu comme +``_), on peut faire : + +.. code:: bash + + $ sudo -u www-data git checkout beta + +.. warning:: + Avis aux successeurs : notamment pour le serveur de production derrière + ``_, il peut être intéressant de créer un paquet Python + de sorte à pouvoir installer la note en faisant directement + ``pip install git+https://gitlab.crans.org/bde/nk20.git``, et avoir ainsi une + installation plus propre en décourageant le développement en production. + + Voir par exemple comment le futur site d'inscription du Crans, Constellation, + gère cela : ``_. + + +Installation des dépendances Python non présentes dans les dépôts APT +--------------------------------------------------------------------- + +Même s'il est préférable d'installer les dépendances Python via les paquets APT, +tous ne sont malheureusement pas disponibles. + +On doit donc récupérer les dépendances manquantes via pip. + +Tout comme en développement, on préfère avoir un environnement virtuel dédié, +les ``sudo pip`` étant rarement compatibles avec les dépendances APT. + +On construit donc un environnement virtuel et on installe les dépendances manquantes +dans cet environnement : + +.. code:: bash + + $ cd /var/www/note_kfet + $ python3 -m venv env + $ . env/bin/activate + (env) $ pip install -r requirements.txt + +Normalement, seules les dépendances manquantes sont installées, les autres sont trouvées +globalement. + +Plus d'informations sur les environnements virtuels dans la documentation officielle +de Python : ``_. + + +Configuration de la note +------------------------ + +La configuration de la note se gère essentiellement via des paramètres d'environnement. +Ceux-ci sont lus via le fichier ``.env`` s'il existe, qui doit être placé à la racine +du projet cloné (donc dans ``/var/www/note_kfet/.env``). Un fichier d'exemple est situé +dans le fichier ``.env_example``, on peut donc faire un ``sudo cp .env_example .env``. + +Attention aux permissions : le fichier doit être lu par ``www-data`` et écrit (rien +n'empêche de l'écrire en tant que root). + +Le contenu de ce fichier : + +.. code:: env + + DJANGO_APP_STAGE=prod + DJANGO_DEV_STORE_METHOD=sqlite + DJANGO_DB_HOST=localhost + DJANGO_DB_NAME=note_db + DJANGO_DB_USER=note + DJANGO_DB_PASSWORD=CHANGE_ME + DJANGO_DB_PORT= + DJANGO_SECRET_KEY=CHANGE_ME + DJANGO_SETTINGS_MODULE=note_kfet.settings + CONTACT_EMAIL=tresorerie.bde@localhost + NOTE_URL=localhost + NOTE_MAIL=notekfet@localhost + EMAIL_HOST=smtp.localhost + EMAIL_PORT=25 + EMAIL_USER=notekfet@localhost + EMAIL_PASSWORD=CHANGE_ME + WIKI_USER=NoteKfet2020 + WIKI_PASSWORD= + +Le paramètre ``DJANGO_APP_STAGE`` accepte comme valeur ``dev`` ou ``prod``. +En développement, les mails ne sont pas envoyés mais affichés dans les logs du +serveur. Les messages d'erreur sont directement affichés au lieu d'être envoyés +par mail. Les paramètres d'envoi de mail n'ont donc aucun effet. En développement, +il est également possible de choisir si l'on souhaite une base de données sqlite +(par défaut) ou si on veut se connecter à une base de données PostgreSQL (rentrer +``postgres`` dans ``DJANGO_DEV_STORE_METHOD``), auquel cas les paramètres de +base de données seront interprétés. + +Les champs ``DJANGO_DB_`` sont relatifs à la connexion à la base de données PostgreSQL. + +Le champ ``DJANGO_SECRET_KEY`` est utilisé pour la protection CSRF (voir la documentation +``_ pour plus de détails). Il s'agit d'une +clé sous forme de chaîne de caractère suffisamment longue (64 caractères paraît bien) +qui n'est pas à transmettre et qui évite d'autres sites malveillants de faire des requêtes +directement sur la note. + +Le champ ``CONTACT_EMAIL`` correspond l'adresse mail que les adhérent⋅e⋅s peuvent contacter +en cas de problème. C'est là où le champ ``Nous contacter`` redirigera. + +Le champ ``NOTE_URL`` correspond au nom de domaine autorisé à accéder au site. C'est également +le nom de domaine qui sera utilisé dans l'envoi de mails pour générer des liens. En +production, cela vaut ``note.crans.org``. + +Le champ ``NOTE_MAIL`` correspond au champ expéditeur des mails envoyés, que ce soit +pour les rapports quotidiens / hebdomadaires / mensuels ou les mails d'erreur. +En production, ce champ vaut ``notekfet2020@crans.org``. + +Les champs ``EMAIL_`` sont relatifs à la connexion au serveur SMTP pour l'envoi de mails. +En production, ``EMAIL_HOST`` vaut ``smtp.crans.org``, ``EMAIL_PORT`` vaut 25 (on reste sur +le réseau interne du Crans) et ``EMAIL_USER`` et ``EMAIL_PASSWORD`` sont vides (ce qui est +valide car la note est sur le réseau du Crans, qui est déjà pré-autorisé à envoyer des mails). + +Les champs ``WIKI_USER`` et ``WIKI_PASSWORD`` servent à s'authentifier sur le compte Wiki +Crans ``NoteKfet2020``, pour notamment exporter automatiquement la liste des activités sur +le wiki. + +Pour configurer la note, il est également possible de créer un fichier +``note_kfet/settings/secrets.py`` qui redéfinit certains paramètres, notamment la +liste des administrateurs ou certaines applications optionnelles, ou encore certains +éventuels mots de passe. + +En production, ce fichier contient : + +.. code:: python + + OPTIONAL_APPS = [ + 'cas_server', + # 'debug_toolbar' + ] + + # When a server error occured, send an email to these addresses + ADMINS = ( + ('Note Kfet', 'notekfet2020@lists.crans.org'), + ) + + +Configuration des tâches récurrentes +------------------------------------ + +Certaines opérations se font périodiquement, comme le rappel hebdomadaire pour les +personnes en négatif. On utilise pour cela un cron. Il suffit pour cela de copier +le fichier ``note.cron`` vers ``/etc/cron.d/note``, en veillant à ce qu'il appartienne +bien à ``root``. + +Ce fichier contient l'ensemble des tâches récurrentes associées à la note. +Une page de documentation dédiée fera bientôt son apparition. + +Sur un serveur de pré-production, on peut ne pas souhaiter activer ces tâches récurrentes. + + +Installation de la base de données PostgreSQL +--------------------------------------------- + +En production, on utilise une vraie base de données PostgreSQL et non un fichier +sqlite. Beaucoup plus facile pour faire éventuellement des requêtes (bien que pas +adapté pour Django) mais surtout bien mieux optimisé pour un serveur de production. + +Pour installer la base de données, on commence par installer PostgreSQL : + +.. code:: bash + + $ sudo apt install --no-install-recommends postgresql postgresql-contrib + +PostgreSQL est désormais installé et lancé. On crée un compte ``note``, avec un +bon mot de passe (le même que donné à Django) : + +.. code:: bash + + $ sudo -u postgres createuser -P note + +Et on crée enfin une base de données nommée ``note_db`` appartenant à ``note``. + +.. code:: bash + + $ sudo -u postgres createdb note_db -O note + +La base de données est désormais prête à être utilisée. + + +Finir l'installation de Django +------------------------------ + +On commence par construire la base de données à partir des migrations enregistrées : + +.. code:: bash + + $ ./manage.py migrate + +On doit compiler les traductions (pour pouvoir les lire plus vite par la suite) : + +.. code:: bash + + $ ./manage.py compilemessages + +Les fichiers statiques (fiches de style, fichiers Javascript, images, ...) doivent +être exportées dans le dossier ``static`` : + +.. code:: bash + + $ ./manage.py collectstatic + +Et on peut enfin importer certaines données de base : + +.. code:: bash + + $ ./manage.py loaddata initial + +La note est désormais prête à être utilisée. Ne reste qu'à configurer un serveur Web. + + +Configuration de UWSGI +---------------------- + +On dispose d'une instance de la note fonctionnelle et bien configurée. Cependant, nous +n'avons pas encore de socket permettant d'intéragir avec le serveur. C'est le travail +de UWSGI. + +On rappelle que la commande ``./manage.py runserver`` n'est pas conçue pour des serveurs +de production, contrairement à UWSGI. + +On commence par installer UWSGI : + +.. code:: bash + + $ sudo apt install --no-install-recommends uwsgi uwsgi-plugin-python3 + +On place ensuite le fichier de configuration UWSGI dans les applications installées. +Un fichier de configuration est présent à la racine du projet, contenant : + +.. code:: ini + + [uwsgi] + uid = www-data + gid = www-data + # Django-related settings + # the base directory (full path) + chdir = /var/www/note_kfet + # the virtualenv (full path) + home = /var/www/note_kfet/env + wsgi-file = /var/www/note_kfet/note_kfet/wsgi.py + plugin = python3 + # process-related settings + # master + master = true + # maximum number of worker processes + processes = 10 + # the socket (use the full path to be safe + socket = /var/www/note_kfet/note_kfet.sock + # ... with appropriate permissions - may be needed + chmod-socket = 664 + # clear environment on exit + vacuum = true + # Touch reload + touch-reload = /var/www/note_kfet/note_kfet/settings/__init__.py + # Enable threads + enable-threads = true + +Il suffit donc de créer le lien symbolique : + +.. code:: bash + + $ sudo ln -s /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/apps-enabled/uwsgi_note.ini + +On peut désormais relancer UWSGI : + +.. code:: bash + + $ sudo systemctl restart uwsgi + + +Configuration de NGINX +---------------------- + +Nous avons désormais un socket qui nous permet de faire des connexions au serveur web, +placé dans ``/var/www/note_kfet/note_kfet.sock``. Cependant, ce socket n'est pas accessible +au reste du monde, et ne doit pas l'être : on veut un serveur Web Nginx qui s'occupe des +connexions entrantes et qui peut servir de reverse-proxy, notamment utile pour desservir +les fichiers statiques ou d'autres sites sur le même serveur. + +On commence donc par installer Nginx : + +.. code:: bash + + $ sudo apt install nginx + +On place ensuite dans ``/etc/nginx/sites-available/nginx_note.conf`` le fichier de +configuration Nginx qui va bien, en remplaçant ``note.crans.org`` par ce qu'il faut : + +.. code:: + + # the upstream component nginx needs to connect to + upstream note { + server unix:///var/www/note_kfet/note_kfet.sock; # file socket + } + + # Redirect HTTP to nk20 HTTPS + server { + listen 80 default_server; + listen [::]:80 default_server; + + location / { + return 301 https://note.crans.org$request_uri; + } + } + + # Redirect all HTTPS to nk20 HTTPS + server { + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + location / { + return 301 https://note.crans.org$request_uri; + } + + ssl_certificate /etc/letsencrypt/live/note.crans.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/note.crans.org/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + } + + # configuration of the server + server { + listen 443 ssl; + listen [::]:443 ssl; + + # the port your site will be served on + # the domain name it will serve for + server_name note.crans.org; + charset utf-8; + + # max upload size + client_max_body_size 75M; + + # Django media + location /media { + alias /var/www/note_kfet/media; + } + + location /static { + alias /var/www/note_kfet/static; + } + + location /doc { + alias /var/www/documentation; + } + + # Finally, send all non-media requests to the Django server. + location / { + uwsgi_pass note; + include /etc/nginx/uwsgi_params; + } + + ssl_certificate /etc/letsencrypt/live/note.crans.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/note.crans.org/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + } + +On peut enfin activer le site : + +.. code:: + + $ sudo ln -s /etc/nginx/sites-available/nginx_note.conf /etc/nginx/sites-enabled/nginx_note.conf + +Si on peut se dire que recharger Nginx suffira, il n'en est rien : voir paragraphe suivant. + + +Génération d'un certificat SSL +------------------------------ + +Nginx va essayer de lire les certificats présents dans +``/etc/letsencrypt/live/note.crans.org/``, mais ce dossier n'existe pas encore. + +On doit donc générer un certificat pour permettre les connexions HTTPS. Cela est permis +grâce à ``certbot``, qu'on s'empresse d'installer : + +.. code:: bash + + $ sudo apt install certbot python3-certbot-nginx + +Le plugin pour nginx permet de certifier que le serveur a bien les droits pour +``note.crans.org`` grâce à Nginx, le BDE n'ayant a priori aucune raison de pouvoir +gérer le nom de domaine ``crans.org``. + +On place dans le dossier ``/etc/letsencrypt/conf.d`` (qu'on crée au besoin) un fichier +nommé ``nk20.ini`` : + +.. code:: ini + + # To generate the certificate, please use the following command + # certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly + + # Use a 4096 bit RSA key instead of 2048 + rsa-key-size = 4096 + + # Always use the staging/testing server + # server = https://acme-staging.api.letsencrypt.org/directory + + # Uncomment and update to register with the specified e-mail address + email = notekfet2020@lists.crans.org + + # Uncomment to use a text interface instead of ncurses + text = True + + # Use Nginx challenge + authenticator = nginx + +En exécutant ``certbot``, il va lire les fichiers de configuration Nginx et générer les +certificats qu'il faut en créant un point d'entrée pour le serveur. + +Il faut néanmoins que la configuration soit valide. Les certificats n'existant pas encore, +la configuration nginx est donc pour l'instant invalide. Il faut alors temporairement +commenter les parties de la configuration qui traitent des certificats et relancer ``nginx`` +(``sudo systemctl reload nginx``). + +On peut ensuite exécuter ``certbot`` : + +.. code:: bash + + $ certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly + +L'instruction ``certonly`` indique à ``certbot`` qu'il se contente de générer le certificat, +sans chercher à l'installer. Si tout s'est bien passé, l'installation se fait simplement +en décommentant les lignes préalablement commentées. + +Un certificat généré de la sorte expire au bout de 3 mois. Néanmoins, certbot tourne +régulièrement pour renouveler les certificats actifs automatiquement. Il n'y a donc +plus rien à faire. + +Après avoir rechargé la configuration de ``Nginx``, rendez-vous sur +``_ (ou votre site) pour vérifier que tout fonctionne correctement :) + + +Mettre à jour la note +--------------------- + +Pour mettre à jour la note, il suffit a priori, après avoir mis à jour les paquets APT, +de faire un ``git pull`` dans le dossier ``/var/www/note_kfet``. + +Les éventuelles nouvelles migrations de la base de données doivent être appliquées : + +.. code:: bash + + $ ./manage.py migrate + +Les nouvelles traductions compilées : + +.. code:: bash + + $ ./manage.py compilemessages + +Les nouveaux fichiers statiques collectés : + +.. code:: bash + + $ ./manage.py collectstatic + +Et enfin les nouvelles fixtures installées : + +.. code:: bash + + $ ./manage.py loaddata initial + +Une fois tout cela fait, il suffit de relancer le serveur UWSGI : + +.. code:: bash + + $ sudo systemctl restart uwsgi + + +Avec Ansible +------------ + +Tout ce travail peut sembler très laborieux et peut mériter d'être automatisé. +Toutefois, il est essentiel de bien comprendre comment chaque étape de l'installation +fonctionne. + +Un playbook Ansible a été écrit permettant de réaliser toutes les tâches décrites ci-dessus. +Il se trouve dans le dossier ``ansible``. + +Ansible s'installe sur votre propre machine (et non sur le serveur) en installant simplement +le paquet ``ansible``. + +Pour déployer la note sur un serveur vierge, commencez par copier le fichier ``hosts_example`` +en le nommant ``hosts``. Ajoutez votre propre serveur, dans la section correspondante. + +Dans le dossier ``host_vars``, créez un fichier dont le nom est l'adresse du serveur, avec +l'extension ``.yml``. + +Dans ce fichier, remplissez : + +.. code:: yaml + + --- + note: + server_name: note.crans.org + git_branch: master + cron_enabled: true + email: notekfet2020@lists.crans.org + + +en adaptant à votre configuration. + +Il suffit ensuite de lancer ``./base.yml -l urldevotreserveur``. + +Pour une première installation, vous devrez renseigner le mot de passe de la base de données +pour créer le compte ``note``. Vous devrez ensuite également refaire quelques ajustements +pour générer le certificat, voir la partie ``certbot``. La configuration du fichier +``.env`` sera également à faire à la main. + +Cependant, pour mettre à jour, lancer cette commande suffit. + + +Copier une base de données +-------------------------- + +On peut vouloir périodiquement copier la base de données de production vers le serveur +de développement, afin de travailler avec des données à jour. + +On aura besoin de pouvoir accéder aux deux bases de données. On commence donc si ce n'est +pas déjà fait par créer un utilisateur sur les deux serveurs : + +.. code:: bash + + ynerant@bde-note:~$ sudo -u postgres createuser -l ynerant + +On réinitialise **sur le serveur de développement** la base de données présente, en +éteignant tout d'abord le serveur Web : + +.. code:: bash + + ynerant@bde-note-dev:~$ sudo systemctl stop uwsgi + ynerant@bde-note-dev:~$ sudo -u postgres dropdb note_db + ynerant@bde-note-dev:~$ sudo -u postgres createdb -O note note_db + +Et on copie enfin la base de données, en une seule ligne via SSH : + +.. code:: bash + + ynerant@bde-note:~$ pg_dump note_db | ssh note-dev.crans.org "psql note_db -f -" + +On peut enfin redémarrer le serveur Web. Les données ont bien été copiées. + +.. caution:: + + On ne copiera **jamais** des données d'adhérent⋅e⋅s sur une machine personnelle. + Ce type d'opération doit s'effectuer impérativement entre des serveurs du BDE. diff --git a/docs/scripts.rst b/docs/scripts.rst new file mode 100644 index 00000000..4e273b6d --- /dev/null +++ b/docs/scripts.rst @@ -0,0 +1,308 @@ +Les scripts de la Note Kfet 2020 +================================ + +Django permet la création de scripts permettant l'interaction en ligne de commande +avec le site. Les scripts sont gérés dans un projet à part : +`nk20-scripts `_. + +Il s'agit d'un module Python à part contenant tous les scripts interagissant avec la note. +Pour l'installer, il suffit d'importer le sous module (``git submodule init apps/scripts``) +ou bien d'avoir ajouté l'option ``--recursive`` lorsque le dépôt a été cloné. Il faut +ensuite ajouter le module au tableau ``INSTALLED_APPS`` dans les paramètres. + + +Scripts Python +############## + +Structure d'un script +--------------------- + +Un script est un fichier Python placé dans ``apps/scripts/management/commands``. Le nom +de fichier sans l'extension ``.py`` caractérise le nom du script. On supposera dans la suite +qu'on aura créé le script ``toto.py``. Pour lancer le script, il suffira de lancer +``./manage.py toto``. Django se charge de trouver l'emplacement du script. Un simple +``./manage.py`` affichera par ailleurs la liste des scripts par application. + +Ce fichier Python doit contenir une classe nommée ``Command`` héritant de +``django.core.management.base.BaseCommand``. + +Il suffit enfin de créer une fonction ``handle(self, *args, **options) -> None``. +C'est cette fonction qui servira à l'exécution du script. ``options`` contient +l'ensemble des options transmises. + +Pour gérer les options, créez une fonction ``add_arguments(self, parser)``. +``parser`` vous permet de gérer vos arguments. Plus d'informations dans la documentation +du module ``argparse`` de Python : https://docs.python.org/fr/3/library/argparse.html + +.. warning:: + + Bonne pratique : si votre script doit écrire sur la base de données, pensez à + ajouter un décorateur ``@transaction.atomic`` (du module ``django.db``) sur + votre fonction ``handle``. Cela aura pour effet de ne pas effectuer les modifications + indiquées en cas d'erreur dans le script. Cela évite de vous retrouver avec une base + de données dans un état instable en cas d'erreur dans le script, ou en développement + (sur un serveur de développement bien sûr). + +.. warning:: + + Par défaut, chaque commande dispose d'un certain nombre d'options, dont ``--help`` qui + affiche l'aide et les options disponibles, et ``--verbosity {0, 1, 2, 3}`` qui permet + de contrôler la verbosité du script. Il est important d'en tenir compte, en particulier + un niveau de verbosité nul ne devrait afficher rien d'autre que des erreurs. + +Plus d'informations dans la documentation officielle : +https://docs.djangoproject.com/fr/2.2/howto/custom-management-commands/ + + +Anonymisation de données +------------------------ + +Le script s'appelle ``anonymize_data``. + +Ce script était utilisé lors de la beta de la Note Kfet 2020, afin d'anonymiser certaines +données personnelles et limiter le risque de fuites de données. Ainsi, les noms, prénoms, +adresses électroniques, numéros de téléphone et adresses étaient normalisées. + +Ce script étant dangereux, l'option ``--force`` est requise. + + +Vérification de l'intégrité des données +--------------------------------------- + +Le script s'appelle ``check_consistency``. + +Son but est de s'assurer que la somme des montants de toutes les notes vaut bien zéro, +et que pour chaque note, la somme des transactions donne bien le solde de la note. + +En cas de problème, les erreurs détectées sont affichées. + +Le script prend plusieurs options : + +* ``--sum-all, -s`` : vérifie si la somme des notes vaut bien 0 +* ``--check-all, -a`` : vérifie si le solde de toutes les notes est cohérent +* ``--check, -c`` : si l'option précédente n'est pas présente, indique les identifiants + des notes à vérifier +* ``--fix, -f`` : Rétablit le solde des notes à la somme des transactions. À n'utiliser + qu'après avoir identifié le problème. + +Ce script est appelé tous les jours à 4h du matin avec les options ``--sum-all --check-all``, +et en cas d'erreur un mail est envoyé. + + +Compilation des messages JavaScript +----------------------------------- + +Le script s'appelle ``compilejsmessages``. + +Django gère nativement la traduction des chaînes de caractères, avec notamment les scripts +``makemessages`` et ``compilemessages``. Django permet également de gérer la traduction +dans les fichiers statiques JavaScript, comme l'indique la documentation officielle : +https://docs.djangoproject.com/fr/2.2/topics/i18n/translation/#internationalization-in-javascript-code + +Comme l'indique cette documentation, cela revient à ajouter un fichier Javascript contenant +l'ensemble des traductions et le navigateur s'occupe de récupérer les bonnes traductions. + +Cependant, la façon standard de gérer cela est d'avoir une vue dédiée qui générera le bon +fichier Javascript. Les traductions ne changeant pas souvent (à chaque mise à jour +uniquement), il n'est pas essentiel de les recompiler à chaque chargement de page. + +Le protocole choisi est donc de générer des fichiers statiques, qui seront donc directement +servis par Nginx (et éventuellement mis en cache par le client) et non recalculés à chaque +fois. On optimise donc les requêtes. + +Pour rappel, pour générer les fichiers de traduction Javascript : + +.. code:: bash + + ./manage.py makemessages -i env -e js -d djangojs + +Et on peut donc appeler ce script ``compilejsmessages`` pour créer les fichiers Javascript +correspondant et le placer dans le dossier des fichiers statiques. + + +Extraction des listes de diffusion +---------------------------------- + +Le script s'appelle ``extract_ml_registrations``. + +Il a pour but d'extraire une liste d'adresses mail pour les inclure directement dans les listes +de diffusion utiles. + +Il prend 2 options : + +* ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``, + ``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérents + actuels (pour la liste ``adherents.bde@lists.crans.org), des clubs (pour + ``clubs@lists.crans.org``), des personnes à abonner à ``evenements@lists.crans.org``, + à ``all.bda@lists.crans.org`` et enfin à ``bds@lists.crans.org``. +* ``--lang``, qui prend en argument ``fr`` ou ``en``. N'est utile que pour la ML événements, + qui a pour projet d'être disponible à la fois en anglais et en français. + +Le script sort sur la sortie standard la liste des adresses mails à inscrire. + +Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est +malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives. + +À terme, il pourrait être envisageable de synchroniser automatiquement les listes avec la note. + + +Suppression d'un utilisateur +---------------------------- + +Le script s'appelle ``force_delete_user``. + +.. caution:: + + Ce script est dangereux. À n'utiliser qu'avec de très grosses pincettes si vous savez + ce que vous faites. Seul cas d'usage pour l'instant recensé : supprimer des comptes en + double qui se sont malencontreusement retrouvés validés pour raison de Sogé et de mauvaise + communication au sein des trésorier⋅ère⋅s. + + Il n'est certainement pas prévu de supprimer des vrais comptes existants via ce script. + On ne supprime pas l'historique. Si jamais quelqu'un demanderait à supprimer son compte, + on se contente de l'anonymiser, et non de le supprimer. + +Ce script est utile lorsqu'il faut supprimer un compte créer par erreur. Tant que la validation +n'est pas faite, il suffit en général de cliquer sur le bouton « Supprimer le compte » sur +l'interface de validation. Cela supprimera l'utilisateur et le profil associé, sans toucher +à une quelconque note puisqu'elle ne sera pas créée. + +Ce script supprime donc un compte ainsi que toutes les données associées (note, alias, +transactions). Il n'est donc pas à prendre à la légère, et vous devez savoir ce que vous +faites. + +Il prend en arguments les identifiants numériques ou un alias de la ou des personnes à +supprimer. + +Sans rien ajouter, il affichera uniquement les éléments qui seront supprimés. + +Avec l'option ``--force``, il commencera à créer une transaction dans la base de données +pour supprimer tout ce qu'il faut, et une validation manuelle sera requise pour confirmer +la suppression. L'option ``--doit`` évite cette confirmation manuelle. +**Vous n'avez jamais à utiliser cette option en théorie.** + +À la fin du processus, un mail est envoyé aux administrateurs pour les prévenir des +élements supprimés. + +Des données réelles jamais tu ne supprimeras. + + +Importation de la Note Kfet 2015 +-------------------------------- + +Les scripts commençant par ``import_`` sont destinés à l'import des données depuis +la Note Kfet 2015. + +.. warning:: + + TODO: Pour la postérité et la conservation d'archives, documenter comment l'import + s'est déroulé. + + +Ajouter un super-utilisateur +---------------------------- + +Le script s'appelle ``make_su``. + +Il prend en argument un pseudo. + +Avec l'option ``--SUPER, -S``, la personne avec ce pseudo devient super-utilisateur, +et obtiens donc les pleins pouvoirs sur la note. À ne donner qu'aux respos info. + +Avec l'option ``--STAFF, -s``, la personne avec ce pseudo acquiert le statut équipe, +et obtiens l'accès à django-admin. À ne donner qu'aux respos info. + + +Rafraîchissement des activités +------------------------------ + +Le script s'appelle ``refresh_activities``. + +Il a pour but de mettre à jour le Wiki du Crans automatiquement en ajoutant les +activités de la Note Kfet sur le calendrier, à savoir les pages +``_ et +``_. + +Il prend diverses options : + +* ``--human, -H`` : met à jour la version lisible de la page des activités +* ``--raw, -r`` : met à jour la version brute de la page des activités, interprétable + par le calendrier +* ``--comment, -c`` : définit le commentaire à ajouter à la modification du wiki +* ``--stdout, -o`` : affiche la page sur la sortie standard +* ``--wiki, -w`` : applique effectivement les modifications sur le wiki + +Ce script est appelé tous les jours à 5h30 avec les options +``--raw --human --comment refresh --wiki``. + + +Rafraîchissement des boutons mis en avant +----------------------------------------- + +Le script s'appelle ``refresh_highlighted_buttons``. + +Il permet d'actualiser la liste des boutons mis en avant sur la page de consommations +qui servent de raccourcis. + +Ce script récupère la liste des 10 boutons les plus cliqués les 30 derniers jours et +les met en avant. + + +Envoi des rappels de négatif +---------------------------- + +Le script s'appelle ``send_mail_to_negative_balances``. + +Il sert à rappeler aux adhérent⋅e⋅s et clubs en négatif qu'ils le sont, mais également +à envoyer aux trésorier⋅ère⋅s et respos info la liste des adhérent⋅e⋅s en négatif. + +Il prend les options suivantes : + +* ``--spam, -s`` : envoie à chaque adhérent⋅e en négatif un rappel par mail pour recharger +* ``--report, -r`` : envoie le rapport aux trésorier⋅ère⋅s et respos info +* ``--negative-amount,-n`` : définit le solde maximal en-dessous duquel les notes + apparaitront sur le rapport / seront spammées +* ``--add-years, -y`` : ajoute également les adhérent⋅e⋅s des ``n`` dernières années + +Ce script est appelé tous les mardis à 5h00 pour spammer les utilisateur⋅rice⋅s en +négatif et tous les 6 du mois pour envoyer le rapport des notes d'adhérent⋅e⋅s ou de +vieux/vieilles adhérent⋅e⋅s de moins d'un an sous -10 €. + + +Envoi des rapports +------------------ + +Le script s'appelle ``send_reports``. + +Les utilisateurs ont la possibilité de recevoir sur demande un rapport à la fréquence de +leur choix (en jours) des transactions effectuées sur leur note. + +Le script prend 2 options : + +* ``--notes, -n`` : sélectionne les notes auxquelles envoyer un rapport. Si non spécifié, + envoie un mail à toutes les notes demandant un rapport. +* ``--debug, -d`` : affiche les rapports dans la sortie standard sans les envoyer par mail. + Les dates de dernier rapport ne sont pas actualisées. + +Ce script est appelé tous les jours à 6h55. + + +Scripts bash +############ + +À quelques fins utiles, certains scripts bash sont également présents, dans le dossier +``scripts/shell``. + + +Sauvegardes de la base de données +--------------------------------- + +Le script ``backup_db`` permet de faire une sauvegarde de la base de données PostgreSQL +et l'envoie sur ``club-bde@zamok.crans.org``. Le script doit être lancé en tant que root. + + +Tabularasa +---------- + +Ce script n'a un intérêt qu'en développement, afin de détruire la base de données et la +recréer en appliquant les migrations et en chargeant les données initiales. diff --git a/entrypoint.sh b/entrypoint.sh index f04bf16d..1a5341c1 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -5,7 +5,7 @@ if [ -z ${NOTE_URL+x} ]; then echo "Warning: your env files are not configurated." else - sed -i -e "s/example.com/$DOMAIN/g" /var/www/note_kfet/apps/member/fixtures/initial.json + sed -i -e "s/example.com/$NOTE_URL/g" /var/www/note_kfet/apps/member/fixtures/initial.json sed -i -e "s/localhost/$NOTE_URL/g" /var/www/note_kfet/note_kfet/fixtures/initial.json sed -i -e "s/\"\.\*\"/\"https?:\/\/$NOTE_URL\/.*\"/g" /var/www/note_kfet/note_kfet/fixtures/cas.json sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /var/www/note_kfet/note_kfet/fixtures/cas.json diff --git a/note.cron b/note.cron index 44db347a..90deac08 100644 --- a/note.cron +++ b/note.cron @@ -1,24 +1,27 @@ # {{ ansible_managed }} # Les cronjobs dont a besoin la Note Kfet +# Envoi des mails aux respos info +MAILTO=notekfet2020@lists.crans.org + # m h dom mon dow user command # Envoyer les mails en attente - * * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -c 1 - * * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -c 1 - 00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -c 1 + * * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -v 0 + * * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -v 0 + 00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -v 0 # Faire une sauvegarde de la base de données 00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db # Vérifier la cohérence de la base et mailer en cas de problème - 00 4 * * * root cd /var/www/note_kfet && env/bin/python manage.py check_consistency --sum-all --check-all --mail + 00 4 * * * root cd /var/www/note_kfet && env/bin/python manage.py check_consistency --sum-all --check-all --mail -v 0 # Mettre à jour le wiki (modification sans (dé)validation, activités passées) - 30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --human --comment refresh --wiki + 30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --human --comment refresh --wiki -v 0 # Spammer les gens en négatif - 00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam + 00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 0 -v 0 # Envoyer le rapport mensuel aux trésoriers et respos info - 00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report + 00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0 # Envoyer les rapports aux gens - 55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports + 55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0 # Mettre à jour les boutons mis en avant - 00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons + 00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons -v 0 # Vider les tokens Oauth2 - 00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens + 00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0 diff --git a/note_kfet/admin.py b/note_kfet/admin.py index fa192b57..06543102 100644 --- a/note_kfet/admin.py +++ b/note_kfet/admin.py @@ -52,3 +52,9 @@ if "rest_framework" in settings.INSTALLED_APPS: from rest_framework.authtoken.admin import * from rest_framework.authtoken.models import * admin_site.register(Token, TokenAdmin) + +if "cas_server" in settings.INSTALLED_APPS: + from cas_server.admin import * + from cas_server.models import * + admin_site.register(ServicePattern, ServicePatternAdmin) + admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin) diff --git a/note_kfet/settings/__init__.py b/note_kfet/settings/__init__.py index 3d995367..3aa115f9 100644 --- a/note_kfet/settings/__init__.py +++ b/note_kfet/settings/__init__.py @@ -12,7 +12,7 @@ def read_env(): directory. """ try: - with open('.env') as f: + with open(os.path.join(BASE_DIR, '.env')) as f: content = f.read() except IOError: content = '' @@ -30,6 +30,7 @@ def read_env(): # Try to load environment variables from project .env +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) read_env() # Load base settings diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 982634c8..67948f09 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -239,6 +239,7 @@ REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.TokenAuthentication', + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 20, @@ -273,3 +274,6 @@ PIC_RATIO = 1 # Custom phone number format PHONENUMBER_DB_FORMAT = 'NATIONAL' PHONENUMBER_DEFAULT_REGION = 'FR' + +# We add custom information to CAS, in order to give a normalized name to other services +CAS_AUTH_CLASS = 'member.auth.CustomAuthUser' diff --git a/note_kfet/urls.py b/note_kfet/urls.py index d4341bc6..ab19d763 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -45,6 +45,11 @@ if "oauth2_provider" in settings.INSTALLED_APPS: path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')) ) +if "cas_server" in settings.INSTALLED_APPS: + urlpatterns.append( + path('cas/', include('cas_server.urls', namespace='cas_server')) + ) + if "debug_toolbar" in settings.INSTALLED_APPS: import debug_toolbar urlpatterns = [ diff --git a/requirements.txt b/requirements.txt index d889dd54..0071bc83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ beautifulsoup4~=4.7.1 Django~=2.2.15 django-bootstrap-datepicker-plus~=3.0.5 +django-cas-server~=1.2.0 django-colorfield~=0.3.2 django-crispy-forms~=1.7.2 django-extensions~=2.1.4