Utilisez la puissance de System.Web.Security dans vos applications ASP.Net

En travaillant un peu sous ASP.Net, j'ai découvert quelques nouveautés de la version 2. Je suis tombé sur tous les contrôles de Login, la gestion des rôles et les profils. En creusant un peu, j'ai remarqué qu'ils s'appuyaient sur une API très bien conçue : simple de mise en oeuvre et puissante. Voyons un peu comment tout cela fonctionne.

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Présentation générale

Pour ceux qui ont déjà un peu manipulé tous ces outils avec un site ASP.Net, il ne devrait pas y avoir trop de problèmes pour comprendre. Les autres non plus, enfin j'espère !

ASP.Net nous fournit un ensemble d'outils destinés à gérer les utilisateurs :

  • MembershipManager pour la gestion des utilisateurs
  • RoleManager pour la gestion des permissions des utilisateurs
  • ProfileManager pour la gestion des profils utilisateurs

Ces outils offrent une étonnante combinaison de puissance et de souplesse. Outre les fonctionnalités offertes, vous pouvez passer d'un système de gestion à l'autre sans rien recompiler. Chaque fonctionnalité repose sur un socle unique nommé provider. Chaque type de provider (un pour chaque fonction) définit un contrat de base que doit remplir un provider, un peu à la manière d'une interface. Les providers de base sont d'ailleurs des classes abstraites.

Le .Net Framework fournit, par exemple, deux MemberShipProvider : SQLMemberShipProvider et ActiveDirectoryMembershipProvider. Ils dérivent tous deux de la classe MembershipProvider qui elle-même dérive de ProviderBase.

En se basant sur cet héritage, il est donc possible de créer un Provider qui s'appuye sur n'importe quelle source de données. De plus, ceci vous laisse la possibilité d'écrire vos propres providers. Consultez l'article de Didier Danse sur la création de son propre MembershipProvider pour vous faire une idée de la simplicité de la chose.

Pour ce qui est de la configuration, tout se fait par XML. Les Managers se chargent de charger et d'initaliser le(s) Provider(s) adéquat(s) en fonction de la configuration de l'application.

providers.png
Voici une idée des possibilités

Etudions d'un peu plus près les différents types de Providers offerts par le .Net Framework. Afin de ne pas nous perdre, j'ai préféré tout réaliser avec les providers SQL Server du .Net Framework. Mis-à-part le fichier de configuration de l'application, cela n'influera en rien sur le reste de ce tutoriel.

Pour pouvoir utiliser SQL Server comme provider, il faut avant tout le préparer, créer les tables, vues et autres Stored Procédures nécessaires via l'assistant situé ici : \WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe. Il vous demandera de choisir une base de données. Afin de ne pas tout mélanger, j'en utilise une dédiée, mais vous pouvez très bien utiliser une base existante.

2. MembershipProvider

Le MembershipProvider est le provider essentiel. Si l'application n'a pas d'utilisateurs, à quoi bon avoir des rôles et des profils.

Comme vous l'aurez sans doutes compris, le MembershipProvider s'occupe de la gestion des utilisateurs au sein d'une application. Il prend en charge toutes les fonction d'identification, de gestion du mot de passe, les informations sur les dates de sessions, ... L'élément de base pour ce provider est le MembershipUser.

2-1. La classe MembershipUser

La classe MembershipUser est donc la brique de base du MembershipProvider. Elle représente un utilisateur, mais ne contient aucune information de plus que le strict nécessaire :

  • UserName : le login / nom d'utilisateur
  • ProviderName : le nom du provider qui gère cet utilisateur
  • ProviderUserKey : clé unique qui identifie l'utilisateur au sein du provider
  • CreationDate : date de création de l'utilisateur
  • LastLoginDate : denière date de login de l'utilisateur
  • LastActivityDate : date de la dernière activité enregistrée de l'utilisateur
  • LastPasswordChangedDate : date du dernier changement de mot de passe
  • LastLockoutDate : dernière fois où l'utilsateur a été bloqué suite à des tentatives de login erronées
  • IsOnline : si l'utilisateur est considéré comme en ligne
  • IsLockedOut : si l'utilisateur s'est bloqué après plusieurs tentatives infructueuses de login
  • IsApproved : si l'utilisateur est activé. Cela peut permettre aux administrateurs de valider un compte avec son utilisation.
  • Email : l'adresse e-mail de l'utilisateur, notament pour pouvoir envoyer le mot de passe ou un nouveau mot de passe en cas de perte
  • PasswordQuestion : question à laquelle doit répondre l'utilisateur lors de la perte de son mot de passe
  • Comment : une description rapide de l'utilisateur

Cette classe dispose également de quelques méthodes intéressantes :

  • ChangePassword : Permet de changer le mot de passe de l'utilisateur
  • ChangePasswordQuestionAndAnswer : Permet de changer la question et la réponse pour l'éventuelle récupération de mot de passe
  • ResetPassword : Permet de générer aléatoirement un nouveau mot de passe en cas de perte et si le provider ne supporte pas la récupération du mot de passe
  • GetPassword : Si le provider le permet, récupération du mot de passe perdu
  • UnlockUser : Dans le cas où celui-ci serait bloqué suite à ses échecs de connection, cette méthode permet de le réabiliter

Comme vous pouvez le voir, cette classe offre toutes les possibilités de base pour la gestion d'un utilisateur au sein d'une application.

2-2. Membership

Membership est le MembershipProviderManager. Toutes ses méthodes et propriétés sont statiques (shared en Visual Basic).

Pour les méthodes nous avons donc :

  • CreateUser : Permet de créer un utilisateur. Regardez les différentes surcharges de cette méthode pour voir toutes ses possibilités
  • DeleteUser : Supprime un utilisateur et toutes les données qui lui sont relatives ou non, au choix
  • FindUsersByEmail : Permet de retrouver des utilisateurs avec leur adresse email approchant. Une surcharge permet d'utiliser des fonctions de pagination
  • FindUsersByName : Permet de retrouver des utilisateurs par nom d'utilisateur approchant. Une surcharge permet d'utiliser des fonctions de pagination
  • GeneratePassword : Génère un mot de passe
  • GetAllUsers : Retourne tous les utilisateurs de l'application. Une surcharge permet d'utiliser des fonctions de pagination
  • GetNumberOfUsersOnline : Retourne le nombre d'utilisateurs actuellement en ligne
  • GetUser : Permet de récupérer un utilisateur, ou par son identifiant unique ou par son login. Elle permet également d'actualiser l'état en ligne de l'utilisateur
  • GetUserNameByEmail : Permet de récupérer un utilisateur grâce à son adresse e-mail
  • UpdateUser : Met à jour l'utilisateur dans la source de données
  • ValidateUser : Valide le login et le mot de passe d'un utilisateur

Pour les propriétés :

  • ApplicationName : Retourne le nom de "l'application" utilisée par le provider
  • EnablePasswordReset : Indique si le Provider sélectionné est capable de réinitialiser le mot de passe de l'utilisateur
  • EnablePasswordRetrieval : Indique si le Provider sélectionné est capable d'afficher le mot de passe en clair
  • HashAlgorithmType : Indique l'algorithme de hashage utilisé pour le stockage du mot de passe
  • MaxInvalidPasswordAttempts : Nombre d'échecs d'authentification avant que l'utilisateur ne soit bloqué
  • MinRequiredNonAlphanumericCharacters : Indique le nombre minimum de caractères spéciaux (*$£@+- ...) que doit contenir le mot de passe
  • MinRequiredPasswordLength : Indique la taille minimale des mots de passe
  • PasswordAttemptWindow : Indique le nombre de minutes durant lesquelles l'utilisateur peut effectuer des saisies erronnées de mot de passe ou de réponse à la question avant d'être bloqué
  • PasswordStrengthRegularExpression : Permet de définir une Expression Régulière pour valider la robustesse mot de passe
  • Provider : Retourne le MembershipProvider utilisé par défaut dans l'application
  • Providers : Retourne l'ensemble des MembershipProviders définis pour l'application
  • RequiresQuestionAndAnswer : Indique si l'utilisateur doit répondre à une question pour récupérer son mot de passe
  • UserIsOnlineTimeWindow : Temps à partir de la dernière action durant lequel l'utilisateur est considéré "en ligne"

La majorité de ces propriétés sont basées sur la lecture des informations de configuration du Provider par défaut, propriété Provider. Toutes ces propriétés, à l'exception de ApplicationName sont en lecture seule.

2-3. Déclatation d'un MembershipProvider dans une application

2-3-1. Initialisation

Il n'y a rien à faire. Comme toute utilisation du Provider se fait par l'intermédiaire du Manager, c'est celui-ci qui, lors de son premier appel, s'initialise en s'appuyant sur le fichier de configuration de l'application.

2-3-2. Configuration XML

Comme je vous l'ai dit précédemment, nous allons utiliser SQLServer 2005 comme source de données, et donc le SqlMembershipProvider pour l'exploiter.

La section connectionstrings
Sélectionnez
<connectionStrings>
	<add name="TestForm.My.MySettings.loginCS" 
		connectionString="Data Source=RAIDERS;Initial Catalog=aspnetdb;Integrated Security=True"
		providerName="System.Data.SqlClient"
	/>
</connectionStrings>
La section system.web contenant la déclaration de notre provider
Sélectionnez
<system.web>
	<membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="20">
		<providers>
			<clear/>
			<add name="SqlProvider"
				type="System.Web.Security.SqlMembershipProvider"
				connectionStringName="TestForm.My.MySettings.loginCS"
				enablePasswordRetrieval="false"
				enablePasswordReset="true"
				requiresUniqueEmail="false"
				requiresQuestionAndAnswer="true"
				passwordFormat="Hashed"
				applicationName="/"
			/>
		</providers>
	</membership>
<system.web>

Comme vous le voyez, le Provider est configurable. Vous pouvez définir des paramètres comme l'obligation d'avoir une adresse e-mail différente pour chaque utilisateur ou non, ...

Il y a un paramètre qui est très important dans cette configuration : applicationName. En effet, un provider peut servir pour différentes applications sur une même source. Imaginez par exemple un développeur sur sa machine, qui travaille pour plusieurs clients différents à la fois. Cette option lui permet de ne pas mélanger les utilisateurs entre les différentes applications.

Etant donné que c'est la configuration de mon application Winforms, je ne me fie pas à la configuration par défaut de la machine (à raison, elle était configurée sur une instance de SQLExpres non installée sur ma machine). J'ai donc déclaré ma propre instance de mon SQLMemberShipProvider. Et là, surprise : le provider par défaut était toujours celui de la machine. Un petit tour dans le code pour afficher quel provider était utilisé et là, surprise : c'était l'instance AspNetSqlMembershipProvider du SqlMembershipProvider qui était utilisée. C'est donc pour cela que la section membership comprend un attribut defaultProvider qui désigne celui que j'ai déclaré. Et pour la forme, un petit "clear" de la liste des providers afin que l'instance AspNetSqlMembershipProvider n'apparaîsse même plus :p Ceci est valable pour les deux autres providers que nous verrons ensuite.

Il est également possible de configurer les providers par programation et ainsi de se passer du fichier de configuration. Mais pourquoi faire compliqué quand on peut faire simple. Pour initialiser un provider, il faut passer par sa méthode Initialise. Je vous laisse le soin de découvrir son utilisation, des plus sympathiques. Vous perdez par contre l'utilisation de Membership, puisqu'après initialisation, Providers est passée en lecture seule.

2-4. Quelques exemples d'utilisation de gestion d'utilisateur

2-4-1. Création d'utilisateur

Voici la surchage que vous utiliserez le plus souvent (VB)
Sélectionnez
Public Shared Function CreateUser ( _
	username As String, _
	password As String, _
	email As String, _
	passwordQuestion As String, _
	passwordAnswer As String, _
	isApproved As Boolean, _
	<OutAttribute> ByRef status As MembershipCreateStatus _
) As MembershipUser
Création de l'utilisateur en VB.Net
Sélectionnez
Dim status As MembershipCreateStatus = MembershipCreateStatus.UserRejected
Dim user As MembershipUser = Nothing
user = Membership.CreateUser("login", "motdepasse", "email@domaine.com", _
		"site numéro 1 des développeurs francophones", "developpez.com", False, status)
If status = MembershipCreateStatus.Success Then
	Page.Title("Création OK")
Else
	Select Case status
		Case MembershipCreateStatus.DuplicateUserName
			' ...
		Case MembershipCreateStatus.DuplicateEmail
			' ...
		Case Else
			' ...
	End Select
End If
Voici la surchage que vous utiliserez le plus souvent (C#)
Sélectionnez
public static MembershipUser CreateUser (
	string username,
	string password,
	string email,
	string passwordQuestion,
	string passwordAnswer,
	bool isApproved,
	out MembershipCreateStatus status
)
Création de l'utilisateur en C#
Sélectionnez
MembershipCreateStatus status = MembershipCreateStatus.UserRejected;
MembershipUser user = null;
user = Membership.CreateUser("login", "motdepasse", "email@domaine.com", 
		"site numéro 1 des développeurs francophones", "developpez.com", false, status);
if (status = MembershipCreateStatus.Success)
{
	Page.Title("Création OK");
}else{
	switch(status){
		case MembershipCreateStatus.DuplicateUserName:
			// ...
		case MembershipCreateStatus.DuplicateEmail:
			// ...
		default:
			// ...
	}
}

isApproved est un booléen qui indique si l'utilisateur est opérationnel après sa création ou s'il doit y avoir validation par un autre utilisateur pour que celui-ci soit activé.

status, de type MembershipCreateStatus, vous donne des indications sur la création du compte utilisateur. "Success" si tout s'est bien déroulé. Dans le cas contraire, la valeur de l'énumération est suffisamment explicite pour connaître le problème rencontré lors de la création.

Pour le reste des paramètres, leur nom est suffisamment explicite pour ne pas rentrer dans les détails.

Quand vous insérez un nouvel utilisateur, vous aurez sans doute la surprise de voir que la date de dernier login et de dernière activité correspondent à la date et l'heure du moment où vous avez inséré l'utilisateur. C'est pas bien méchant, mais je ne trouve pas ca logique du tout.

3. RoleProvider

On retrouve souvent, mais pas toujours, une gestion des permissions accordées aux utilisateurs en fonction de leur appartenance à un groupe. C'est à ca que va servir de le RoleManager.

Contrairement au MembershipProvider, le RoleProvider ne possède pas de type dédié à la gestion des rôles. En effet, dans la plupart des cas, l'unique information nécessaire, c'est de savoir si un utilisateur appartient ou non à un groupe. Nous allons donc travailler principalement avec des booléens et des tableaux de chaînes.

3-1. L'objet Roles

L'objet Roles est l'équivalent de Membership. Il sera le pont entre votre application et le provider.

L'objet Roles possède de nombreuses propriétés. La plupart de celles-ci sont destinées à être utilisées avec un navigateur. Toutes celles dont le nom commence par Cookie sont de ce type et ne serviront pas ici. ApplicationName est la même que pour Membership, Provider et Providers ont les mêmes fonctions que pour Membership. Une propriété très importante est Enabled. En effet, il est possible d'utiliser les utilisateurs, mais pas forcément les Roles. Pour cette raison, le RoleManager peut être désactivé.

Le plus intéressant de cet objet se situe au niveau de ses nombreuses méthodes :

  • AddUsersToRole : Permet d'ajouter une liste d'utilisateurs dans un rôle
  • AddUsersToRoles : Permet d'ajouter une liste d'utilisateurs à une liste de rôles
  • AddUserToRole : Permet d'ajouter un utilisateur à un rôle
  • AddUserToRoles : Permet d'ajouter un utilisateur à une liste de rôles
  • CreateRole : Crée un nouveau rôle
  • DeleteRole : Permet de supprimer un rôle. Attention si vous utilisez la première surcharge de cette méthode qui ne prend que le nom du rôle en argument. Si le rôle en question est encore attribué à des utilisateurs, vous aurez une jolie exception. Utilisez plutôt la seconde surcharge avec le second argument pour éviter ce désagrément.
  • FindUsersInRole : Retourne une liste des utilisateurs dans le rôle dont le nom ressemble à la chaîne de recherche
  • GetAllRoles : Retourne une liste de tous les rôles
  • GetRolesForUser : Retourne la liste des rôles pour l'utilisateur spécifié. La deuxième surcharge ne fonctionne qu'en mode Web.
  • GetUsersInRole : Retourne la liste des utilisateurs associés au rôle spécifié
  • IsUserInRole : Vérifie si l'utilisateur appartient bien au rôle spécifié
  • RemoveUserFromRole : Enleve l'utilisateur spécifié du rôle spécifié
  • RemoveUserFromRoles : Retire l'utilisateur spécifié des rôles sélectionnés
  • RemoveUsersFromRole : Retire une liste d'utilisateurs d'un rôle
  • RemoveUsersFromRoles : Retire une liste de d'utilisateurs d'une liste de rôles
  • RoleExists : Vérifie si le rôle en question existe

Comme vous le voyez, tout est prévu pour gérer les rôles. De plus, pas besoin d'avoir une classe pour représenter un rôle. Pour l'ajout, la suppression ou l'obtention d'information, un tableau de chaînes est largement suffisant.

3-2. Déclaration d'un RoleProvider

Comme pour le MembershipProvider, l'initialisation est transparente. Pas besoin de revenir dessus. Les mêmes remarques sont d'ailleurs applicables.

Déclaration du RoleManager dans le fichier de configuration
Sélectionnez
<roleManager enabled="true" defaultProvider="SqlRoleProvider">
	<providers>
		<clear/>
		<add name="SqlRoleProvider" 
			connectionStringName="SampleApp.My.MySettings.loginCS"
			applicationName="/"
			type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, 
			PublicKeyToken=b03f5f7f11d50a3a" />
		</providers>
</roleManager>

Du fait qu'il sera utilisé en WinForms, la majorité des informations de configuration, concernant les cookies, sont ignorées. On retrouve donc le Nom, la chaine de connection utilisée puisque c'est un provider SQL Server et l'application de laquelle dépend le Provider. La seule petite nouveauté par rapport au MembershipManager c'est l'apparition de l'attribut "enabled". C'est en plaçant celui-ci à la valeur "True" que vous activerez la gestion des rôles dans l'application, ou la désactiverez en la plaçant à "False".

3-3. Quelques exemples d'utilisation du RoleManager

3-3-1. Création de rôle

Création d'un nouveau rôle en VB.Net
Sélectionnez
If Not Roles.RoleExists("roles") Then
	Roles.CreateRole("roles")
End If
Création d'un nouveau rôle en VB.Net
Sélectionnez
if (!Roles.RoleExists("roles"))
{
	Roles.CreateRole("roles");
}

Si vous ne vérifiez pas l'existence d'un rôle avant de le créer et que celui-ci existe déjà, vous obtiendrez une System.Configuration.Provider.ProviderException. C'est cette exception que vous obtiendrez chaque fois que vous tenterez d'éxécuter une action de ce type.

3-3-2. Suppression de rôle

Suppression d'un rôle en VB.Net
Sélectionnez
Dim result as Boolean = Roles.DeleteRole("roleexample", False)
Suppression d'un rôle en C#
Sélectionnez
bool result = Roles.DeleteRole("roleexample", false);

Vous vous exposez à une System.ArgumentException si le nom du rôle est une chaîne vide ou contient une virgule. Si le rôle que vous tentez de supprimer contient encore des utilisateurs et que vous utilisez la surcharge de cette méthode qui prend seulement le nom de rôle en paramètre ou cette surcharge avec le second attribut à False, c'est une System.Configuration.Provider.ProviderException qui sera lancée.

3-3-3. Récupération des utilisateurs d'un rôle

Récupération de la liste des utilisateurs d'un rôle en VB.Net
Sélectionnez
If Roles.RoleExists("roles") Then
	Dim users() As String = Roles.GetUsersInRole("roles")
	For i As Integer = 0 To users.Length - 1
		Trace.WriteLine(users(i))
	Next
End If
Récupération de la liste des utilisateurs d'un rôle en C#
Sélectionnez
if (!Roles.RoleExists("roles"))
{
	string[] users = Roles.GetUsersInRole("roles");
	for (int i = 0; i++; i < users.Length - 1)
	{
		Trace.WriteLine(users(i));
	}
}

Comme vous le voyez, même avec des contrôles de vérification, l'utilisation du RoleManager est des plus simples.

Passons au dernier type de Provider.

4. ProfileProvider

Le ProfileManager est sans doute la partie la plus complexe à traiter en WinForms.

Le ProfileManager vous permet de définir un profil, c'est-à-dire des propriétés personnalisées en fonction de chaque utilisateur. Ce profil peut très bien ressembler au profil que vous possédez sur le forum : signature, avatar, date de naissance, adresse MSN, ... Du fait que d'une application à une autre le contenu du profil, ses champs, peuvent varier, les concepteurs de ce provider ont décidé de laisser leur définition dans la configuration du ProfileManager.

Définition du profil qui nous servira d'exemple
Sélectionnez
<properties>
<clear/>
<add name="FullName" serializeAs="String"/>
<add name="WebSite" serializeAs="String" />
<add name="Birthdate" serializeAs="Xml" type="System.DateTime"/>
<add name="Voiture" serializeAs="Xml" type="SampleProfile.SampleClass, SampleProfile"/>
<add name="PreferedColor" serializeAs="String" type="System.Drawing.Color, System.Drawing, 
	Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
<group name="Address">
	<add name="FirstLine" serializeAs="String"/>
	<add name="SecondLine" serializeAs="String"/>
	<add name="PostalCode"/>
	<add name="City" serializeAs="String" type="System.String"/>
	<add name="Country" serializeAs="String"/>
</group>
</properties>

Comme vous le voyez, un profil est un ensemble de propriétés, définies par un nom. D'autres attributs comme le type sont définissables, ou encore la forme sous laquelle la propriété est sauvegardée dans la banque de données, ici, SQL Server. Vous pouvez également faire appel à des librairies autres que les librairies standards du Framework, comme le champs Voiture. La classe SampleProfile n'est pas des plus compliquées, elle contient un constructeur par défaut et 2 propriétés de type String.

Laissons ça de côté pour le moment, regardons un peu comment fonctionne le ProfileManager avant de continuer.

4-1. ProfileManager

Le ProfileManager possède peu de propriétés. Celles que nous allons utiliser sont les mêmes que pour le MembershipManager et le RoleManager. L'unique nouvelle propriété ne s'applique pas dans notre cas.

Au niveau des méthodes, c'est déjà un peu plus intéressant :

  • DeleteInactiveProfiles : Efface les profils inactifs avant la date spécifiée
  • DeleteProfile : Efface le profil de l'utilisateur spécifié
  • DeleteProfiles : Efface les profils des utilisateurs spécfiés
  • FindInactiveProfilesByUserName : Retourne les profils inactifs par nom d'utilisateurs similaires à la recherche
  • FindProfilesByUserName : Retourne les profils par nom d'utilisateurs similaires à la recherche
  • GetAllInactiveProfiles : Retourne les profils inactifs avant la date spécifiée
  • GetAllProfiles : Retourne tous les profils. Cette méthode offre des fonctions de pagination
  • GetNumberOfInactiveProfiles : Retourne le nombre de profils inactifs avant la date spécifiée
  • GetNumberOfProfiles : Retourne le nombre de profils actuellement stockés

Dans le cadre de ce tutoriel, je n'utiliserai pas le ProfileManager. Nous passerons par le ProfileBase pour toutes les opérations sur les profils.

4-2. ProfileBase

Comme je vous le disais plus haut, la partie complexe du ProfileManager, c'est le profil lui-même.

Le désavantage d'utiliser les profils dans une application WinForms, c'est qu'ASP.Net ne nous génère pas la classe ProfileCommon, qui hérite de ProfileBase. La classe ProfileCommon a l'avantage de rendre le profil fortement typé. Chaque champs du profil est mappé en propriété fortement typée, c'est-à-dire que le type de la propriété correspond au type du champs.

5. Conclusion

Comme vous pouvez le constater, vous disposez d'un système puissant de gestion des utilisateurs et des rôles dans votre application ASP.Net.

5-1. Remerciements

Merci à tous ceux qui ont pris le temps de relire et critiquer ce tuto avant sa publication : Ditch, mehdi_tn

5-2. Liens

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2006 Olivier Delmotte. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.