Impersonate c# (User Impersonation)

Cet article a été initialement publié dans le magazine programmez. Il s’agit des détails techniques sur le bref article présent ici. La méthode est également disponible dans un package nuget ayant à ce jour +7000 téléchargement.

Cette procédure consiste à changer le contexte de sécurité dans lequel vous vous trouvez. Cela signifie que vous pourrez exécuter du code avec des droits qui correspond à un autre utilisateur. Cela peut être utilisé pour écrire des fichiers dans un répertoire particulier pour lequel seul un utilisateur a accès.

Exemple d’un cas concret :

Pour une application web (MVC), il faut que celle-ci écrive dans un répertoire se trouvant sur un autre serveur web. Cependant pour des règles de sécurité propre au client, il est impossible d’attribuer un utilisateur de l’active directory pour l’exécution de l’application pool. Afin de contourner cette règle de sécurité, l’utilisation de l’impersonation peut être utile. Grâce à cela, en créant un utilisateur sur l’active directory et en utilisant l’impersonation nous pourrons écrire des fichiers avec cet utilisateur.

Voyons en détail la mise en place de l’impersonation. Pour cela, il faut appeler une fonction qui réside dans la DLL advapi32.dll. Il s’agit de la fonction LogonUser.

Syntax du code non managé en C++

BOOL LogonUser(
  _In_     LPTSTR  lpszUsername,
  _In_opt_ LPTSTR  lpszDomain,
  _In_opt_ LPTSTR  lpszPassword,
  _In_     DWORD   dwLogonType,
  _In_     DWORD   dwLogonProvider,
  _Out_    PHANDLE phToken
);

Vous pouvez retrouver tous les détails de cette méthode ici :

Comme il s’agit de code non managé, il faut utiliser l’attribut DLLImport. Cette utilisation permet de faire appel à la fonction qui réside dans la DLL et de lancer son traitement.

[DllImport("advapi32.dll")]
  private static extern bool LogonUser(String lpszUserName,
      String lpszDomain,
      String lpszPassword,
      int dwLogonType,
      int dwLogonProvider,
      ref IntPtr phToken);

Les trois premiers paramètres sont utiles pour l’authentification de l’utilisateur. Il s’agit simplement du nom de l’utilisateur ainsi que du domaine et évidemment de son mot de passe. Pour les paramètres suivant, vous trouverez les valeurs possibles pour le logon type (dwLogonType) et le provider (dwLogonProvider), dans le fichier WinBase.h Ce fichier se trouve dans le répertoire suivant : C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include.

Tableau des valeurs possibles pour le paramètre dwLogonProvider

//
// Logon Support APIs
//

#define LOGON32_LOGON_INTERACTIVE       2
#define LOGON32_LOGON_NETWORK            3
#define LOGON32_LOGON_BATCH                  4
#define LOGON32_LOGON_SERVICE              5
#define LOGON32_LOGON_UNLOCK               7
#if(_WIN32_WINNT >= 0x0500)
#define LOGON32_LOGON_NETWORK_CLEARTEXT 8
#define LOGON32_LOGON_NEW_CREDENTIALS   9
#endif // (_WIN32_WINNT >= 0x0500)

Afin d’avoir une utilisation interactive avec le système, l’utilisation du type « LOGON32_LOGON_INTERACTIVE » convient. Donc pour le paramètre dwLogonType qui est un type DWORD la valeur entière 2 sera utilisée.

Tableau des valeurs possibles pour le paramètre dwLogonType

#define LOGON32_PROVIDER_DEFAULT    0
#define LOGON32_PROVIDER_WINNT35    1
#if(_WIN32_WINNT >= 0x0400)
#define LOGON32_PROVIDER_WINNT40    2
#endif /* _WIN32_WINNT >= 0x0400 */
#if(_WIN32_WINNT >= 0x0500)
#define LOGON32_PROVIDER_WINNT50    3
#endif // (_WIN32_WINNT >= 0x0500)
#if(_WIN32_WINNT >= 0x0600)
#define LOGON32_PROVIDER_VIRTUAL    4
#endif // (_WIN32_WINNT >= 0x0600)

Nous utiliserons le provider par défaut (LOGON32_PROVIDER_DEFAULT) ce qui correspond à la valeur 0 Pour ce qui est du phToken, en réalité il s’agit d’un type PHANDLE. Ce type est un pointer d’Handle (descripteur) vers le token. En C# nous utiliserons donc le type IntPtr qui correspondra à ce pointeur. Comme il s’agit d’un paramètre « OUT » nous recevrons grâce au passage en référence le pointeur du token. Le pointeur du Token sera utilisé dans le constructeur de l’objet WindowsIdentity qui représente l’utilisateur. Grâce à cet objet nous pourrons dès lors faire l’emprunt de l’identité de l’utilisateur en question.

WindowsIdentity newIdentity = new WindowsIdentity(phToken);
_windowsImpersonationContext = newIdentity.Impersonate();

En retour de l’appel de la méthode Impersonate, vous recevrez un WindowsImpersonationContext. Cela correspond à l’utilisateur avant l’emprunt de son identité. Suite à l’ensemble de ces informations, j’ai créé un objet UserImpersonation. Pour en faire une utilisation correcte et facile cette classe implémente IDisposable. De cette manière, il vous sera facile de l’utiliser via un USING habituel. Cela aura également pour effet d’implémenter un dispose sur l’objet UserImpersonation. Dans ce dispose, la méthode Undo de l’objet WindowsImpersonationContext est appelée ce qui a pour effet de rétablir le contexte de l’utilsateur. Le token qui vous a été renvoyé lors de la connexion de l’utilisateur doit impérativement être proprement clôturé.Pour se faire, un DLLImport de kernel32.dll doit être effectué et la méthode CloseHandle sera utilisée.

Syntax du code non managé en C++

BOOL WINAPI CloseHandle(
  _In_ HANDLE hObject
);

Vous pouvez retrouver tous les détails de cette méthode ici. Cette méthode prend en paramètre le Handle du token.

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);

Ce qui nous donne pour le Dipose de l’objet UserImpersonation ceci

public void Dispose()
{
      if (_windowsImpersonationContext != null)
          _windowsImpersonationContext.Undo();
      if (_tokenHandle != IntPtr.Zero)
          CloseHandle(_tokenHandle);
}

Voici un exemple d’utilisation de l’objet.

using (UserImpersonation user = new UserImpersonation("login", "domain","password"))
{
        if (user.ImpersonateValidUser())
        {
             File.WriteAllText("Programmez.txt", "UserImpersonation");
        }
}

Vous pourrez constater que le propriétaire du fichier créé est bien l’utilisateur que vous passez en paramètre. Si vous désirez utiliser ce mécanisme, un package Nuget est à votre disposition https://www.nuget.org/packages/UserImpersonation/

Le code source est également disponible sur Github. https://github.com/michelcedric/UserImpersonation

Code complet :

/// <summary>
/// Object to change the user authticated
/// </summary>
public class UserImpersonation : IDisposable
{
    /// <summary>
    /// Logon method (check athetification) from advapi32.dll
    /// </summary>
    /// <param name="lpszUserName"></param>
    /// <param name="lpszDomain"></param>
    /// <param name="lpszPassword"></param>
    /// <param name="dwLogonType"></param>
    /// <param name="dwLogonProvider"></param>
    /// <param name="phToken"></param>
    /// <returns></returns>
    [DllImport("advapi32.dll")]
    private static extern bool LogonUser(String lpszUserName,
        String lpszDomain,
        String lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
 
    /// <summary>
    /// Close
    /// </summary>
    /// <param name="handle"></param>
    /// <returns></returns>
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern bool CloseHandle(IntPtr handle);
 
    private WindowsImpersonationContext _windowsImpersonationContext;
    private IntPtr _tokenHandle;
    private string _userName;
    private string _domain;
    private string _passWord;
 
    const int LOGON32_PROVIDER_DEFAULT = 0;
    const int LOGON32_LOGON_INTERACTIVE = 2;
 
    /// <summary>
    /// Initialize a UserImpersonation
    /// </summary>
    /// <param name="userName"></param>
    /// <param name="domain"></param>
    /// <param name="passWord"></param>
    public UserImpersonation(string userName, string domain, string passWord)
    {
        _userName = userName;
        _domain = domain;
        _passWord = passWord;
    }
 
    /// <summary>
    /// Valiate the user inforamtion
    /// </summary>
    /// <returns></returns>
    public bool ImpersonateValidUser()
    {
        bool returnValue = LogonUser(_userName, _domain, _passWord,
                LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                ref _tokenHandle);
 
        if (false == returnValue)
        {
            return false;
        }
 
        WindowsIdentity newId = new WindowsIdentity(_tokenHandle);
        _windowsImpersonationContext = newId.Impersonate();
        return true;
    }
 
    #region IDisposable Members
 
    /// <summary>
    /// Dispose the UserImpersonation connection
    /// </summary>
    public void Dispose()
    {
        if (_windowsImpersonationContext != null)
            _windowsImpersonationContext.Undo();
        if (_tokenHandle != IntPtr.Zero)
            CloseHandle(_tokenHandle);
    }
 
    #endregion
}

Un commentaire sur “Impersonate c# (User Impersonation)

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.