Last active
March 30, 2023 20:29
-
-
Save BenMakesGames/870e096be50781d97e50859e1557d739 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.DirectoryServices.AccountManagement; | |
using System.Runtime.InteropServices; | |
using System.Security.Principal; | |
using Microsoft.Win32.SafeHandles; | |
namespace YourNamespaceHere; | |
// Notes: | |
// * This class is a service. Hopefully you've got a DI/IoC container you can register it with. (For a desktop application, | |
// it'd probably make the most sense as a singleton.) | |
// * This code assumes nullable reference types are enabled, because enabling nullable reference types is the correct way | |
// to live. | |
// * Buy Me a Coffee? :) https://ko-fi.com/A0A12KQ16 | |
public sealed class WindowsImpersonationHelper: IDisposable | |
{ | |
private SafeAccessTokenHandle? ImpersonationToken { get; set; } | |
private const int InteractiveLogonType = 2; | |
/// <summary> | |
/// Once called, any code that calls WindowsImpersonationHelper.RunImpersonated will execute as the impersonated | |
/// user. Call WindowsImpersonationHelper.StopImpersonation to forget the user. | |
/// </summary> | |
/// <returns>True if login succeeded; false, otherwise.</returns> | |
public bool Impersonate(string? domain, string username, string password) | |
{ | |
StopImpersonation(); | |
// calls LogonUser, from advapi32.dll (DllImport below) | |
var success = LogonUser(username, domain, password, InteractiveLogonType, default, out SafeAccessTokenHandle token); | |
if (!success) | |
return false; | |
ImpersonationToken = token; | |
return true; | |
} | |
/// <summary> | |
/// Stops impersonation. | |
/// </summary> | |
public void StopImpersonation() | |
{ | |
ImpersonationToken?.Dispose(); | |
ImpersonationToken = null; | |
} | |
/// <summary> | |
/// Run `callback` as the impersonated user. | |
/// </summary> | |
/// <param name="callback"></param> | |
public void RunImpersonated(Action callback) | |
{ | |
// TODO: if your application demands it, you might throw an exception here if ImpersonationToken is null | |
if (ImpersonationToken == null) | |
callback.Invoke(); | |
else | |
WindowsIdentity.RunImpersonated(ImpersonationToken, callback); | |
} | |
/// <summary> | |
/// Get the impersonated user's identity. | |
/// </summary> | |
/// <returns></returns> | |
public UserPrincipal? GetImpersonatedUser() | |
{ | |
// TODO: this method depends on System.DirectoryServices.AccountManagement. if you don't need this method, | |
// and/or don't want that dependency, just delete this method | |
UserPrincipal? identity = null; | |
RunImpersonated(() => | |
{ | |
try | |
{ | |
identity = UserPrincipal.Current; | |
} | |
catch (NoMatchingPrincipalException) | |
{ | |
// identity is left null | |
} | |
}); | |
return identity; | |
} | |
public void Dispose() | |
{ | |
ImpersonationToken?.Dispose(); | |
} | |
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
private static extern bool LogonUser(string username, string? domain, string password, int logonType, int logonProvider, out SafeAccessTokenHandle tokenHandle); | |
} | |
// example usage: | |
public sealed class SomeDbFactoryServiceOrWhatever | |
{ | |
private WindowsImpersonationHelper Impersonator { get; } | |
public SomeDbFactoryServiceOrWhatever(WindowsImpersonationHelper impersonator) | |
{ | |
Impersonator = impersonator; | |
} | |
public MyDbContext CreateDbContext(DbContextOptions<MyDbContext> options) | |
{ | |
MyDbContext? db = null; | |
Impersonator.RunImpersonated(() => | |
{ | |
db = new FmsDbContext(options); | |
db.Database.OpenConnection(); | |
}); | |
return db!; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment