SharePoint : RunWithElevatedPrivileges, attention à la portée des variables

November 09, 2010

Lorsque votre code SharePoint doit exécuter des actions pour lesquelles l’utilisateur n’a pas les droits, vous pouvez utiliser la méthode SPSecurity.RunWithElevatedPrivileges. Cette dernière attend un delegate en argument. Le delegate est alors appelé sans restriction d’autorisation, au lieu de brider les appels aux autorisations de l’utilisateur connecté. Bien entendu à utiliser avec précaution, pensez à bien verrouiller la portée de votre code.

Toutefois, l’utilisation de cette méthode peut amener quelques surprise. Par exemple, j’ai sur un de mes site SharePoint un groupe dont l’option Qui peut consulter l'appartenance au groupe? est positionnée sur Membres du groupe. Ainsi, un utilisateur non administrateur ne peut consulter les membres de ce groupe. Si dans votre code, vous devez découvrir les membres de ce groupe (on peut imaginer un scénario ou on a un groupe de personne responsable d’une étape de workflow, sans que les utilisateurs les connaissent), vous écrirez naturellement le code suivant :

protected void btnIncorrect_Click(object sender, EventArgs e)
{
    // get the id of the group from the UI
    var groupID = Convert.ToInt32(ddlGroups.SelectedValue);
    // run code with higher privileges
    SPSecurity.RunWithElevatedPrivileges(() =>
    {
        var grp = SPContext.Current.Web.SiteGroups.GetByID(
            groupID
            );
        if (grp == null)
        {
            lblMessage.Text = string.Format(
                "Could not find the group {0}",
                ddlGroups.SelectedItem.Text
            );
        }
        else
        {
            // Get the members of the group
            var users = from SPUser u in grp.Users
            select u.Name;
            // and display them
            lblMessage.Text = string.Join(",", users.ToArray());
        }
    });
}

Si vous exécutez ce code avec votre compte administrateur de la machine, aucun problème. Arrive les tests, avec un compte sans droit particulier (vous testez bien vos développements avec un utilisateur simple, n’est ce pas ?), vous obtiendrez une erreur (pour SharePoint 2010) :

System.Threading.ThreadAbortException: Le thread a été abandonné.
    à System.Threading.Thread.AbortInternal()
    à System.Threading.Thread.Abort(Object stateInfo)
    à System.Web.HttpResponse.End()
    à Microsoft.SharePoint.Utilities.SPUtility.Redirect(String url, SPRedirectFlags flags, HttpContext context, String queryString)
    à Microsoft.SharePoint.Utilities.SPUtility.RedirectToAccessDeniedPage(HttpContext context)
    à Microsoft.SharePoint.Utilities.SPUtility.HandleAccessDenied(HttpContext context)
    à Microsoft.SharePoint.Utilities.SPUtility.HandleAccessDenied(Exception ex)
    à Microsoft.SharePoint.Library.SPRequest.GetUsersDataAsSafeArray(String bstrUrl, UInt32 dwUsersScope, String bstrValue, UInt32 dwValue, UInt32& pdwColCount, UInt32& pdwRowCount, Object& pvarDataSet)
    à Microsoft.SharePoint.SPUserCollection.InitUsers(Boolean fCustomUsers, String[] strIdentifiers)
    à Microsoft.SharePoint.SPUserCollection.Undirty()
    à Microsoft.SharePoint.SPBaseCollection.GetEnumerator()
    à System.Linq.Enumerable.<CastIterator>d__aa1.MoveNext()
    à System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
    à System.Linq.Buffer1..ctor(IEnumerable1 source)
    à System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
    à Hand.RunElevated.VisualWebPart1.VisualWebPart1UserControl.<>c__DisplayClass4.<btnIncorrect_Click>b__2()

Cette erreur est suivie d’une redirection automatique vers la page Access Denied.

Si vous observez la pile, vous verrez l’appel à une méthode HandleAccessDenied. Surprise! Vous pensiez avoir tous les droits, et il se trouve que non.

Remarque : pour SharePoint 2007, le problème est similaire, mais le message d’erreur est différent (un horrible HRESULT ave E_ACCESS_DENIED à l’ancienne). La résolution est similaire.

L’origine du problème se trouve dans la ligne suivante :

var grp = SPContext.Current.Web.SiteGroups.GetByID(
    groupID
    );

En effet, le code utilise ici le contexte de l’utilisateur connecté pour récupérer le groupe. Ce contexte a déjà été créé, et ne dispose que des autorisations de l’utilisateur connecté. Même si le code s’exécute dans un mode élevé, les autorisations sont déjà positionnées.

Pour contourner ce problème, il suffit de créer une nouvelle instance de la classe SPSite à l’intérieur du code élevé. Le code précédent mis à jour est alors le suivant :

// get the id of the group from the UI
var groupID = Convert.ToInt32(ddlGroups.SelectedValue);
// run code with higher privileges
SPSecurity.RunWithElevatedPrivileges(() = > {
// the using statement is your best friend to avoid memory leak
using(var tempSite = new SPSite(SPContext.Current.Site.ID)) {
    var grp = tempSite.RootWeb.SiteGroups.GetByID(
    groupID);
    if (grp == null) {
        lblMessage.Text = string.Format( & quot; Could not find the group {
            0
        } & quot;,
        ddlGroups.SelectedItem.Text);
    } else {
        // Get the members of the group
        var users = from SPUser u in grp.Users
        select u.Name;
        // and display them
        lblMessage.Text = string.Join( & quot;, & quot;, users.ToArray());
    }
}
});

Ce code cette fois ci s’exécute sans aucun problème. La variable tempsite est créée facilement à partir du contexte. Tous les appels suivants sont faits à partir de cette variable.

Notez au passage l’utilisation du bloc using sur la variable de type SPSite pour éviter les problèmes de fuite mémoire.

Conclusion :

Cet article vous aura permis, je l’espère, de voir comment on peut se faire avoir avec la méthode RunWithElevatedPrivileges, mais aussi de comprendre l’origine du problème et la solution pour le corriger.