221 lines
10 KiB
TeX
221 lines
10 KiB
TeX
\chapter{Authentification}
|
||
|
||
Comme on l'a vu dans la partie sur le modèle, nous souhaitons que le créateur d'une liste puisse retrouver facilement les éléments qu'il aura créée.
|
||
Ce dont nous n'avons pas parlé cependant, c'est la manière dont l'utilisateur va pouvoir créer son compte et s'authentifier.
|
||
La \href{https://docs.djangoproject.com/en/stable/topics/auth/}{documentation} est très complète, nous allons essayer de la simplifier au maximum.
|
||
Accrochez-vous, le sujet peut être complexe.
|
||
|
||
\section{Utilisateurs}
|
||
|
||
Dans les spécifications, nous souhaitions pouvoir associer un utilisateur à une liste (\textbf{le propriétaire}) et un utilisateur à une part (\textbf{le donateur}).
|
||
Par défaut, Django offre une gestion simplifiée des utilisateurs (pas de connexion LDAP, pas de double authentification, \ldots~: juste un utilisateur et un mot de passe.
|
||
Pour y accéder, un paramètre par défaut est défini dans votre fichier de settings : \texttt{AUTH\_USER\_MODEL}.
|
||
|
||
|
||
\section{Mécanisme d'authentification}
|
||
|
||
On peut schématiser le flux d'authentification de la manière suivante :
|
||
En gros:
|
||
|
||
\begin{enumerate}
|
||
\item
|
||
La personne accède à une URL qui est protégée (voir les décorateurs @login\_required et le mixin LoginRequiredMixin)
|
||
\item
|
||
Le framework détecte qu'il est nécessaire pour la personne de se connecter (grâce à un paramètre type LOGIN\_URL)
|
||
\item
|
||
Le framework présente une page de connexion ou un mécanisme d'accès pour la personne (template à définir)
|
||
\item
|
||
Le framework récupère les informations du formulaire, et les transmets aux différents backends d'authentification, dans l'ordre
|
||
\item
|
||
Chaque backend va appliquer la méthode \texttt{authenticate} en cascade, jusqu'à ce qu'un backend réponde True ou qu'aucun ne réponde
|
||
\item
|
||
La réponse de la méthode authenticate doit être une instance d'un utilisateur, tel que définit parmi les paramètres généraux de l'application.
|
||
\end{enumerate}
|
||
|
||
En résumé (bis):
|
||
|
||
\begin{enumerate}
|
||
\item
|
||
Une personne souhaite se connecter;
|
||
\item
|
||
Les backends d'authentification s'enchaîne jusqu'à trouver une bonne correspondance. Si aucune correspondance n'est trouvée, on envoie la personne sur les roses.
|
||
\item
|
||
Si OK, on retourne une instance de type current\_user, qui pourra être utilisée de manière uniforme dans l'application.
|
||
\end{enumerate}
|
||
|
||
Ci-dessous, on définit deux backends différents pour mieux comprendre
|
||
les différentes possibilités:
|
||
|
||
\begin{enumerate}
|
||
\item
|
||
Une authentification par jeton
|
||
\item
|
||
Une authentification LDAP
|
||
\end{enumerate}
|
||
|
||
\begin{minted}{python}
|
||
from datetime import datetime
|
||
|
||
from django.contrib.auth import backends, get_user_model
|
||
from django.db.models import Q
|
||
|
||
from accounts.models import Token
|
||
|
||
UserModel = get_user_model()
|
||
|
||
class TokenBackend(backends.ModelBackend):
|
||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||
"""Authentifie l'utilisateur sur base d'un jeton qu'il a reçu.
|
||
On regarde la date de validité de chaque jeton avant d'autoriser
|
||
l'accès.
|
||
"""
|
||
token = kwargs.get("token", None)
|
||
current_token = Token.objects.filter(
|
||
token=token, validity_date__gte=datetime.now()
|
||
).first()
|
||
|
||
if current_token:
|
||
user = current_token.user
|
||
current_token.last_used_date = datetime.now()
|
||
current_token.save()
|
||
|
||
return user
|
||
|
||
return None
|
||
\end{minted}
|
||
|
||
Ceci sous-entend qu'on a bien une classe qui permet d'accéder à ces jetons.
|
||
|
||
\begin{minted}{python}
|
||
from django.contrib.auth import backends, get_user_model
|
||
from ldap3 import Server, Connection, ALL
|
||
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError
|
||
|
||
from config import settings
|
||
|
||
UserModel = get_user_model()
|
||
|
||
|
||
class LdapBackend(backends.ModelBackend):
|
||
"""Implémentation du backend LDAP pour la connexion des utilisateurs à
|
||
l'Active Directory.
|
||
"""
|
||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||
"""Authentifie l'utilisateur au travers du serveur LDAP.
|
||
"""
|
||
ldap_server = Server(settings.LDAP_SERVER, get_info=ALL)
|
||
ldap_connection = Connection(
|
||
ldap_server,
|
||
user=username,
|
||
password=password
|
||
)
|
||
|
||
try:
|
||
if not ldap_connection.bind():
|
||
raise ValueError("Login ou mot de passe incorrect")
|
||
except (LDAPPasswordIsMandatoryError, ValueError) as ldap_exception:
|
||
raise ldap_exception
|
||
|
||
user, _ = UserModel.objects.get_or_create(username=username)
|
||
|
||
return user
|
||
\end{minted}
|
||
|
||
|
||
On peut résumer le mécanisme d'authentification de la manière suivante:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
Si vous voulez modifier les informations liées à un utilisateur, orientez-vous vers la modification du modèle. Comme nous le verrons ci-dessous, il existe trois manières de prendre ces modifications en compte. Voir également \href{https://docs.djangoproject.com/en/stable/topics/auth/customizing/}{ici}.
|
||
\item
|
||
Si vous souhaitez modifier la manière dont l'utilisateur se connecte, alors vous devrez modifier le \textbf{backend}.
|
||
\end{itemize}
|
||
|
||
\section{Modélisation}
|
||
|
||
Dans un premier temps, Django a besoin de manipuler \href{https://docs.djangoproject.com/en/1.9/ref/contrib/auth/\#user-model}{des instances de type \texttt{django.contrib.auth.User}}.
|
||
Cette classe implémente les champs suivants:
|
||
\begin{itemize}
|
||
\item
|
||
\texttt{username}
|
||
\item
|
||
\texttt{first\_name}
|
||
\item
|
||
\texttt{last\_name}
|
||
\item
|
||
\texttt{email}
|
||
\item
|
||
\texttt{password}
|
||
\item
|
||
\texttt{date\_joined}.
|
||
\end{itemize}
|
||
|
||
D'autres champs, comme les groupes auxquels l'utilisateur est associé, ses permissions, savoir s'il est un super-utilisateur, \ldots\hspace{0pt} sont moins pertinents pour le moment.
|
||
Avec les quelques champs déjà définis ci-dessus, nous avons de quoi identifier correctement nos utilisateurs.
|
||
Inutile d'implémenter nos propres classes, puisqu'elles existent déjà.
|
||
|
||
Si vous souhaitez ajouter un champ, il existe trois manières de faire.
|
||
|
||
\subsection{Extension du modèle existant}
|
||
|
||
Le plus simple consiste à créer une nouvelle classe, et à faire un lien de type \texttt{OneToOne} vers la classe \texttt{django.contrib.auth.User}.
|
||
De cette manière, on ne modifie rien à la manière dont Django authentife ses utlisateurs: tout ce qu'on fait, c'est un lien vers une table nouvellement créée, comme on l'a déjà vu au point {[}\ldots\hspace{0pt}voir l'héritage de modèle{]}.
|
||
L'avantage de cette méthode, c'est qu'elle est extrêmement flexible, et qu'on garde les mécanismes Django standard.
|
||
Le désavantage, c'est que pour avoir toutes les informations de notre utilisateur, on sera obligé d'effectuer une jointure sur le base de données, ce qui pourrait avoir des conséquences sur les performances.
|
||
|
||
\subsection{Substitution du modèle}
|
||
|
||
Avant de commencer, sachez que cette étape doit être effectuée \textbf{avant la première migration}.
|
||
Le plus simple sera de définir une nouvelle classe héritant de \texttt{django.contrib.auth.User} et de spécifier la classe à utiliser dans votre fichier de paramètres.
|
||
Si ce paramètre est modifié après que la première migration ait été effectuée, il ne sera pas pris en compte.
|
||
Tenez-en compte au moment de modéliser votre application.
|
||
|
||
\begin{minted}{python}
|
||
AUTH_USER_MODEL = 'myapp.MyUser'
|
||
\end{minted}
|
||
|
||
|
||
Notez bien qu'il ne faut pas spécifier le package \texttt{.models} dans cette injection de dépendances: le schéma à indiquer est bien \texttt{\textless{}nom\ de\ l’application\textgreater{}.\textless{}nom\ de\ la\ classe\textgreater{}}.
|
||
|
||
\subsection{Social-auth}
|
||
Voir ici : \href{https://github.com/omab/python-social-auth}{pythonsocial auth}
|
||
|
||
|
||
\subsection{OAuth}
|
||
OAuth est un standard libre définissant un ensemble de méthodes à implémenter pour l'accès (l'autorisation) à une API. Son fonctionnement se base sur un système de jetons (Tokens), attribués par le possesseur de la ressource à laquelle un utilisateur souhaite accéder.
|
||
|
||
Le client initie la connexion en demandant un jeton au serveur.
|
||
Ce jeton est ensuite utilisée tout au long de la connexion, pour accéder aux différentes ressources offertes par ce serveur.
|
||
`wikipedia \textless{}\url{http://en.wikipedia.org/wiki/OAuth\%3E\%60_}.
|
||
|
||
Une introduction à OAuth est \href{http://hueniverse.com/oauth/guide/intro/}{disponible ici}.
|
||
Elle introduit le protocole comme étant une \texttt{valet\ key}, une clé que l'on donne à la personne qui va garer votre voiture pendant que vous profitez des mondanités.
|
||
Cette clé donne un accès à votre voiture, tout en bloquant un ensemble de fonctionnalités.
|
||
Le principe du protocole est semblable en ce sens: vous vous réservez un accès total à une API, tandis que le système de jetons permet d'identifier une personne, tout en lui donnant un accès restreint à votre application.
|
||
|
||
L'utilisation de jetons permet notamment de définir une durée d'utilisation et une portée d'utilisation.
|
||
L'utilisateur d'un service A peut par exemple autoriser un service B à accéder à des ressources qu'il
|
||
possède, sans pour autant révéler son nom d'utilisateur ou son mot de passe.
|
||
|
||
L'exemple repris au niveau du \href{http://hueniverse.com/oauth/guide/workflow/}{workflow} est le suivant : un utilisateur(trice), Jane, a uploadé des photos sur le site faji.com (A).
|
||
Elle souhaite les imprimer au travers du site beppa.com (B).
|
||
Au moment de la commande, le site beppa.com envoie une demande au site faji.com pour accéder aux ressources partagées par Jane.
|
||
Pour cela, une nouvelle page s'ouvre pour l'utilisateur, et lui demande d'introduire sa "pièce d'identité".
|
||
Le site A, ayant reçu une demande de B, mais certifiée par l'utilisateur, ouvre alors les ressources et lui permet d'y accéder.
|
||
|
||
|
||
|
||
\section{Templates}
|
||
|
||
Ce qui n'existe pas par contre, ce sont les vues.
|
||
Django propose donc tout le mécanisme de gestion des utilisateurs, excepté le visuel (hors administration).
|
||
En premier lieu, ces paramètres sont fixés dans le fichier `settings \textless{}\url{https://docs.djangoproject.com/en/1.8/ref/settings/\#auth\%3E\%60_}.
|
||
On y trouve par exemple les paramètres suivants :
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\texttt{LOGIN\_REDIRECT\_URL}: si vous ne spécifiez pas le paramètre \texttt{next}, l'utilisateur sera automatiquement redirigé vers cette page.
|
||
\item
|
||
\texttt{LOGIN\_URL}: l'URL de connexion à utiliser.
|
||
Par défaut, l'utilisateur doit se rendre sur la page \texttt{/accounts/login}.
|
||
\end{itemize} |