Gérer "programmatiquement" les règles d’accès destinées à l’authentification par formulaire

English Version

Introduction :

L'authentification par formulaire est massivement employée dans les applications ASP.net. Ce type d'authentification vise principalement les applications web ASP.NET accédées d'internet car elle permet, en particulier, d'authentifier les utilisateurs sans avoir à s'appuyer sur un annuaire d'entreprise tel que "Active Directory".

La manière dont l'authentification par formulaire autorise l'accès à différentes parties d'une application ASP.net est basée sur un système de permissions (règles d'accès) décrit dans le fichier "web.config" de l'application. L'objectif de cet article n'est pas de décrire en détail la section du "web.config" qui est responsable de l'attribution des permissions ou d'expliquer comment les formulaires ASP.net fonctionnent. Si vous voulez plus d'informations sur ces sujets, je vous recommande les 3 liens suivants :

Le but de cet article est de vous montrer comment, programmatiquement, vous pouvez ajouter ou modifier ces règles d'accès à partir de votre application.

Scénario :

Supposons que vous construisiez une application ASP.net possédant la structure de répertoire suivante :

Nous supposerons également que vous avez deux rôles, un appelé "Administrators" et un autre appelé "SiteUsers". Tous les utilisateurs authentifiés sur votre site web seront membres du rôle "SiteUsers" et seulement les administrateurs du site seront membres du rôle "Administrators".

Dans cette configuration, nous souhaitons que :

  • Seules les personnes membres du rôle "Administrators" puissent accéder au répertoire AdminFolder de l'application
  • Seules les personnes membres du rôle "SiteUsers" et "Administrators" puissent accéder au répertoire UsersFolder de l'application
  • Tout le monde (même les utilisateurs non-authentifiés) peut accéder au répertoire PublicFolder

Pour réaliser ces objectifs, nous avons deux possibilités :

  • coder toutes ces règles directement dans le fichier "web.config" de l'application
  • vérifier programmatiquement que les règles sont bien dans le "web.config", et si cela n'est pas le cas, les ajouter.

Implémentation :

La première chose qu'il faut savoir, est qu'une application ASP.net peut avoir plusieurs "web.config" à différents emplacements. Chaque "web.config" décrit les paramètres applicatifs qui s'appliquent pour le répertoire dans lequel il est situé, ainsi que tous les sous-répertoires. Il va également hériter de la configuration du "web.config" situé dans le dossier parent. Par exemple, le "web.config" situé dans le répertoire "AdminFolder" va hériter de la configuration du "web.config" situé dans le répertoire "PublicFolder" sauf pour les points qu'il modifie.

Pour implémenter le scénario ci-dessus, nous devons surmonter les obstacles suivants :

  • Comment accéder aux informations qui sont stockées dans le "web.config"
  • Comment récupérer la section qui paramètre les règles d'authentification pour l'authentification par formulaire
  • Comment faire la différence entre une règle déclarée dans le "web.config" courant et une règle déclarée dans le "web.config" parent
  • Comment stocker l'information que nous aurons à indiquer dans le "web.config"

Accéder au "web.config" :

Pour accéder au "web.config", l'équipe de développement .Net nous a gentiment fourni une classe qui instancie des objets qui représentent un fichier ".config". Dans le cas de notre application web nous allons utiliser la classe WebConfigurationManager avec sa méthode OpenWebConfiguration (System.String). L'argument de cette méthode nous permet de spécifier l'emplacement du "web.config" que nous souhaitons configurer. Par exemple "/" indique à ASP.net que nous souhaitons utiliser le fichier "web.config" situé dans le répertoire racine de l'application, qui est, dans notre cas, le répertoire nommé "PublicFolder".

L'appel à WebConfigurationManager.OpenWebConfiguration(System.String) va retourner un objet de type System.Configuration.Configuration qui réprésente le "web.config" que nous venons d'ouvrir.

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/");

L'implémentation de l'objet nous permet de configurer n'importe quelle section du "web.config" que nous pourrions avoir besoin de lire ou de modifier. Ceci est simplement fait par la méthode GetSection(System.String) Nous pouvons donc écrire le code suivant :

AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

Nous remarquons que, comme la plupart des éléments en .Net, une section est un objet. Nous avons différents types de sections, celle qui nous intéresse aujourd'hui est AuthorizationSection. En regardant le tag <Authorizations> dans le "web.config", on remarque qu'il contient une série de règles d'inclusion et/ou d'exclusion qui autorisent ou refusent l'accès aux utilisateurs et rôles de l'application. L'objet AuthorizationSection contient une liste d'objets AuthorizationRule. Pour inspecter les règles qui sont présentes à chaque niveau de répertoire, tout ce que nous avons à faire est d'itérer les règles de AuthorizationSection. Pour le dossier PublicFolder le code ressemblerait à ceci :

foreach (AuthorizationRule ar in auth.Rules)
{
//iteration logic will go here...
}

Nous allons donc vérifier que les deux règles suivantes existent :

  • Nous autorisons à tout utilisateur non-authentifié l'accès aux pages de ce dossier
  • Nous autorisons également, à tout utilisateur authentifié, l'accès aux pages de ce dossier

Comme ces règles ne vont pas être trouvées, nous devons les ajouter à la liste des règles. Nous pouvons faire ceci en déclarant un nouvel objet de type AuthorizationRule et ensuite configurer ses propriétés correctement. Le code ci-dessous montre comment faire ceci pour une règle d'accès autorisant à tout utilisateur non-authentifié l'accès au site :

//create the rule - allow access to un-authenticated users
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Users.Add("?");
auth.Rules.Add(accessRule);

La sauvegarde des changements peut simplement être réalisée en utilisant l'objet Configuration de la méthode Save(System.Configuration.ConfigurationSaveMode). En utilisant la méthode ConfigurationSaveMode.Minimal, nous allons seulement sauvegarder les propriétés qui diffèrent de celles dont on hérite. Ceci deviendra plus clair quand nous traiterons les "web.config" hiérarchiquement situés en-dessous du "web.config" racine dans ce scénario :

config.Save(ConfigurationSaveMode.Minimal);

Maintenant que nous nous sommes assuré que les permissions pour le répertoire PublicFolder étaient bonnes, nous pouvons nous concentrer sur le répertoire AdminFolder. Ici nous voulons qu'uniquement les utilisateurs membres du rôle Admin aient accès aux pages. Les autres (utilisateurs authentifiés et non-authentifiés) n'auront aucun accès. Les règles d'accès sont stockées dans un "web.config" situé dans le répertoire AdminFolder, sous le répertoire PublicFolder. Si nous jetons un œil à ce fichier de configuration nous remarquerons que seules les sections qui diffèrent du web.config situé à la racine de l'application sont présentes. Pour récupérer la section <Authorizations>, nous pouvons créer un autre objet Configuration et le charger en indiquant le chemin approprié : "/AdminFolder" :

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/AdminFolder/");
AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

L'inspection des règles est réalisée exactement comme nous l'avons vu dans l'exemple précédent – On itère tous les objets AuthorizationRule contenus dans la section AuthorizationSection que nous avons récupérée. Nous allons vérifier si nous trouvons les règles suivantes :

  • Refuser l'accès à tout utilisateur non-authentifié
  • Refuser l'accès à tous les membres du rôle SiteUsers
  • Autoriser l'accès à tous les membres du rôle AdminUsers

Il y a également un autre obstacle à surmonter : nous avons hérité des règles d'accès du "web.config" du répertoire PublicFolder (le répertoire à la racine de l'application). Nous devons identifier ces règles et les ignorer puisque les règles d'accès que nous recherchons vont écraser toute règle héritée. En général, configurer une règle dans un web.config qui est plus bas dans la hiérarchie va écraser la configuration du web.config racine de l'application. Pour les règles d'accès, les règles de refus vont prendre le pas sur les règles d'autorisation. Donc si nous autorisons l'accès à tous les utilisateurs non-authentifiés pour toutes les pages du PublicFolder et ses sous-dossiers, le fait d'ajouter une règle de refus pour ces utilisateurs non-authentifiés dans le répertoire AdminFolder (qui est un sous-répertoire de PublicFolder) va écraser la règle d'autorisation.

Pour identifier les règles héritées nous allons inspecter l'ElementInformation pour chaque AccessRules que nous itèrerons. Il y a deux objets PropertyInformation qui sont : "users" et "roles". Une règle d'accès peut autoriser ou refuser l'accès à un utilisateur spécifique ou à tous les utilisateurs d'un rôle. Donc, nous pouvons trouver aussi bien des règles qui s'appliquent à des utilisateurs qu'à des rôles. Nous avons besoins de récupérer les deux objets PropertyInformation pour être capable de décider si nous traitons le premier cas ou le second. Pour n'importe quelle règle, un seul de ces objets va contenir une valeur, les autres seront null. Pour décider si la propriété est héritée, nous regardons la ValueOrigin membre de l'objet PropertyInformation et comparons celle-ci à PropertyValueOrigin.Inherited :

private bool IsRuleInherited(AuthorizationRule rule)
{
//to see if an access rule is inherited from the web.config above
//the current one in the hierarchy, we look at two PropertyInformation
//objects - one corresponding to roles and one corresponding to
//users

PropertyInformation usersProperty = rule.ElementInformation.Properties["users"];
PropertyInformation rolesProperty = rule.ElementInformation.Properties["roles"];

//only one of these properties will be non null. If the property
//is equal to PropertyValueOrigin.Inherited, the this access rule
//if not returned in this web.config
if (usersProperty != null)
{
if (usersProperty.ValueOrigin == PropertyValueOrigin.Inherited)
return true;
}

if (rolesProperty != null)
{
if (rolesProperty.ValueOrigin == PropertyValueOrigin.Inherited)
return true;
}

return false;
}

Armé de cette nouvelle méthode, nous pouvons maintenant mettre à jour le code qui itère les AuthorizationRules à ce niveau de l'application, en ajoutant la logique suivante : si une règle est héritée, on l'ignore étant donné que ça n'est pas une règle d'autorisation qui nous intéresse dans l'itération du web.config courant.

foreach (AuthorizationRule ar in auth.Rules)
{
if (IsRuleInherited(ar))
{
continue;
}

//check if the rule is one of the rules that are of interest
//...
}

Avec l'implémentation de cet algorithme, nous pouvons faire la même-chose avec le web.config qui est dans le répertoire UsersFolder. Pour retrouver ce fichier nous allons utiliser WebConfigurationManager.OpenWebConfiguration(System.String) exactement comme avant, en indiquant l'emplacement du fichier de configuration en démarrant à partir de la racine du site web ("/UsersFolder"). Nous obtenons alors une partie de l'objet AuthorizationSection et inspectons ces règles une par une en ignorant toutes celles qui sont héritées :

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/UsersFolder/");

AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

//check all rules in the AuthorizationSection of the web.config
//ignore the rules that are inherited and set the flags below if
//we find any of the rules that are of interest

bool foundAllowSiteUsers = false;
bool foundDenyAnonymous = false;
bool foundAllowAdmins = false;

foreach (AuthorizationRule ar in auth.Rules)
{
if (IsRuleInherited(ar))
{
continue;
}

//check if we are denying access to Un-Authenticated Users
//note that Un-Authenticated users are represented by the
// ? (question mark) sign

if (ar.Users.Contains("?") &&
ar.Action == AuthorizationRuleAction.Deny)
{
foundDenyAnonymous = true;
continue;
}

//check if we are allowing access to the users that
//belong to the Administrators role
if (ar.Roles.Contains("Administrators") &&
ar.Action == AuthorizationRuleAction.Allow)
{
foundAllowAdmins = true;
continue;
}

//check if we are denying access to the users that
//belong to the SiteUsers role
if (ar.Roles.Contains("SiteUsers") &&
ar.Action == AuthorizationRuleAction.Allow)
{
foundAllowSiteUsers = true;
continue;
}
}

Nous recherchons à avoir les règles suivantes :

  • Refuser l'accès à tous les utilisateurs non-authentifiés
  • Autoriser l'accès à tous les utilisateurs du rôle Administrators
  • Autoriser l'accès à tous les utilisateurs du rôle SiteUsers

Si elles ne sont pas présentes nous les ajouterons et sauvegarderons la nouvelle configuration :

//looking at the flags decide if we need to update the config
bool updateConfig = false;
AuthorizationRule accessRule;

if (!foundAllowAdmins || !foundDenyAnonymous || !foundAllowSiteUsers)
{
//clear all pre-existing rules.
auth.Rules.Clear();

//create the rule - deny access to users belonging to the SiteUsers role
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Roles.Add("SiteUsers");
auth.Rules.Add(accessRule);

//create the rule - deny access to Un-Authenticated users
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Deny);
accessRule.Users.Add("?");
auth.Rules.Add(accessRule);

//create the rule - allow access to all users belonging to the Administrators role
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Roles.Add("Administrators");
auth.Rules.Add(accessRule);

//set the update flag to true
updateConfig = true
}

if (updateConfig)
{
config.Save(ConfigurationSaveMode.Minimal);
}

Conclusion :

Nous espérons que ce petit exemple pourra vous être utile pour vérifier par programme que les règles d'accès destinées à l'authentification par formulaire sont en places et, le cas échéant, pour les ajouter. Une petite note relative au déploiement de notre petite application : assurez vous que l'accès en écriture au niveau du site web est autorisé (Cette permission est nécessaire pour que vous puissiez sauvegarder sur disque les changements que vous avez effectués dans le fichier web.config) :

Code Source :

  • Code Source(dézipper la solution dans un répertoire local à votre ordinateur et l'ouvrir avec Visual Studio 2005 ou 2008).

Article écrit par Paul Cociuba / traduction par Sylvain Lecerf (équipe support IIS)