Il arrive de nombreuses fois que l’on ait besoin d’envoyer des requêtes à l’Active Directory de la société dans SharePoint lors de développements.
Cependant il faut avoir quelques notions de « contexte d’exécution » car cela peut rapidement devenir un casse-tête le cas échéant.
Pour rappel, tout code .NET managé dans IIS est exécuté avec le compte de service aspnet qui a un minimum de privilèges.
Lorsque SharePoint doit effectuer des opérations nécessitant plus de droits il exécute du code en mode de privilèges élevés en utilisant ainsi le compte de service définit pour le pool de Web Application.
Une autre méthode pour augmenter les privilèges est d’utiliser l’impersonnification pour utiliser l’identité de l’utilisateur connecté.
Selon ce que vous êtes en train de réaliser et à quelle ressource vous accédez l’une ou l’autre méthode sera plus indiquée et plus sécurisée.
Autre information, les timerjobs sont exécutés par défaut avec le compte de service de l’OWS timer (le service). Ceci qui explique pourquoi vos requêtes vers l’active directory ont pu se passer sans problème dans un timerjob et pourquoi elles génèrent des erreurs quand elles sont émises depuis une application web. En effet le compte de l’OWS timer doit avoir un accès en lecture/écriture sur l’active directory.
Dans mes exemples je code un web service pour SharePoint qui sert à importer un utilisateur de l’active directory dans SharePoint à partir de son adresse e-mail et à lui rajouter des droits de lecture sur un document Word.
Le code n’est volontairement pas optimisé pour être le plus identique possible dans les trois exemples afin que vous remarquiez plus facilement les changements importants. Je vous conseille de copier/coller ce code dans visual studio pour plus de confort si vous souhaitez l’étudier en détails. Pour plus de clarté vous trouverez aussi en pièce jointe de cet article les trois fichiers de code bruts.
Premier exemple, la requête seule (que vous pourriez mettre dans un timer job normalement)
public class Classic : System.Web.Services.WebService
{
private string UMail, ULogon, UDName;
public void ChangeUserRights(Guid SiteID, Guid WebID, Guid ListID, Guid ItemID, string UserEmail, int RoleID)
{
using (SPSite site = new SPSite(SiteID))
{
using (SPWeb web = site.OpenWeb(WebID))
{
SPUser user;
try
{
user = web.SiteUsers.GetByEmail(UserEmail);
}
catch (SPException e)// On regarde juste si l'utilisateur existe déjà, le cas échéant on l'importe
{
try
{
UMail = UserEmail;
adgo();//on exécute le code de recherche de manière classique, mais cela risque de poser problème si on est pas dans un timerjob
web.SiteUsers.Add(ULogon, UserEmail, UDName, "");//On ajoute notre utilisateur et le tour est joué
web.Update();
}
catch (DirectoryServicesCOMException f)
{
string ldapPath = f.Message.ToString();
}
user = web.SiteUsers.GetByEmail(UserEmail);
}
SPList list = web.Lists[ListID];// cette partie créé le role asignment pour donner le droit à l'utilisateur sur le document
SPListItem item = list.Items[ItemID];
SPRoleDefinition RoleDefinition = web.RoleDefinitions[RoleID];
SPRoleAssignment RoleAssignment = new SPRoleAssignment(user.LoginName, user.Email, user.Name, string.Empty);
RoleAssignment.RoleDefinitionBindings.Add(RoleDefinition);
if (!item.HasUniqueRoleAssignments)
{
item.BreakRoleInheritance(true);
}
item.RoleAssignments.Add(RoleAssignment);
item.SystemUpdate();
}
}
}
private void adgo()
{
DirectoryEntry entry = newDirectoryEntry("LDAP://DC=toto,DC=com"); //ouverture de connexion à l'active directory
DirectorySearcherADSearcher = new DirectorySearcher(entry, "(mail=" + UMail + ")", newstring[] { "displayName", "sAMAccountName" }); //requête
SearchResult result = ADSearcher.FindOne(); //on ne veut qu'un seul résultat
ULogon = Environment.UserDomainName + "\\" + result.Properties["sAMAccountName"][0]; //on récupère le logon et le displayname
UDName = result.Properties["displayName"][0].ToString();
}
}
Second exemple, code exécuté en mode de privilèges élevés.
public class Elevated : System.Web.Services.WebService
{
private string UMail, ULogon, UDName;
public void ChangeUserRights(Guid SiteID, Guid WebID, Guid ListID, Guid ItemID, string UserEmail, int RoleID)
{
using (SPSite site = new SPSite(SiteID))
{
using (SPWeb web = site.OpenWeb(WebID))
{
SPUser user;
try
{
user = web.SiteUsers.GetByEmail(UserEmail);
}
catch (SPException e)
{
try
{
UMail = UserEmail;
SPSecurity.CodeToRunElevated thecode = new SPSecurity.CodeToRunElevated(adgo);
SPSecurity.RunWithElevatedPrivileges(thecode); //on exécute le code en privilèges élevés, le problème c'est qu'on ne peut pas passer d'arguments à la méthode
web.SiteUsers.Add(ULogon, UserEmail, UDName, "");
web.Update();
}
catch (DirectoryServicesCOMException f)
{
string ldapPath = f.Message.ToString();
}
user = web.SiteUsers.GetByEmail(UserEmail);
}
SPList list = web.Lists[ListID];
SPListItem item = list.Items[ItemID];
SPRoleDefinition RoleDefinition = web.RoleDefinitions[RoleID];
SPRoleAssignment RoleAssignment = new SPRoleAssignment(user.LoginName, user.Email, user.Name, string.Empty);
RoleAssignment.RoleDefinitionBindings.Add(RoleDefinition);
if (!item.HasUniqueRoleAssignments)
{
item.BreakRoleInheritance(true);
}
item.RoleAssignments.Add(RoleAssignment);
item.SystemUpdate();
}
}
}
private void adgo()
{
DirectoryEntry entry = new DirectoryEntry("LDAP://DC=toto,DC=com");
DirectorySearcher ADSearcher = new DirectorySearcher(entry, "(mail=" + UMail + ")", newstring[] { "displayName", "sAMAccountName" });
SearchResult result = ADSearcher.FindOne();
ULogon = Environment.UserDomainName + "\\" + result.Properties["sAMAccountName"][0];
UDName = result.Properties["displayName"][0].ToString();
}
}
Et enfin le code de la méthode par impersonification.
public class Impersonate : System.Web.Services.WebService
{
private string UMail, ULogon, UDName;
public void ChangeUserRights(Guid SiteID, Guid WebID, Guid ListID, Guid ItemID, string UserEmail, int RoleID)
{
using (SPSite site = new SPSite(SiteID))
{
using (SPWeb web = site.OpenWeb(WebID))
{
SPUser user;
try
{
user = web.SiteUsers.GetByEmail(UserEmail);
}
catch (SPException e)
{
try
{
UMail = UserEmail;
System.Security.Principal.WindowsImpersonationContext impersonationContext;//on récupère l'identité de l'utilisateur connecté
impersonationContext = ((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate(); //on passe l'exécution du code avec son identité
adgo();
impersonationContext.Undo(); //fin de la portion de code avec son identité
web.SiteUsers.Add(ULogon, UserEmail, UDName, "");
web.Update();
}
catch (DirectoryServicesCOMException f)
{
string ldapPath = f.Message.ToString();
}
user = web.SiteUsers.GetByEmail(UserEmail);
}
SPList list = web.Lists[ListID];
SPListItem item = list.Items[ItemID];
SPRoleDefinition RoleDefinition = web.RoleDefinitions[RoleID];
SPRoleAssignment RoleAssignment = new SPRoleAssignment(user.LoginName, user.Email, user.Name, string.Empty);
RoleAssignment.RoleDefinitionBindings.Add(RoleDefinition);
if (!item.HasUniqueRoleAssignments)
{
item.BreakRoleInheritance(true);
}
item.RoleAssignments.Add(RoleAssignment);
item.SystemUpdate();
}
}
}
private void adgo()
{
DirectoryEntry entry = new DirectoryEntry("LDAP://DC=toto,DC=com");
DirectorySearcher ADSearcher = new DirectorySearcher(entry, "(mail=" + UMail + ")", newstring[] { "displayName", "sAMAccountName" });
SearchResult result = ADSearcher.FindOne();
ULogon = Environment.UserDomainName + "\\" + result.Properties["sAMAccountName"][0];
UDName = result.Properties["displayName"][0].ToString();
}
}
Notez bien que ces trois méthodes fonctionnent, tout dépend du contexte d’exécution à chaque fois, il vous appartient de choisir la méthode qui convient le mieux. Classique pour timer jobs, élevée pour des applications accessibles à des utilisateurs qui n’ont pas accès à l’AD mais qui doivent quand même pouvoir lire des informations dedans, et impersonnifiée pour coller aux droits de l’AD.
Autre chose, l’assembly qui permet d’envoyer des requêtes à Active Directory est System.DirectoryServices
Vous savez tout désormais, n’hésitez pas à laisser des commentaires ou à me contacter si vous avez des questions/remarques/suggestions.