Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@hikalkan
Last active March 20, 2020 07:10
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save hikalkan/8862d9f7ae8b4874976d to your computer and use it in GitHub Desktop.
Save hikalkan/8862d9f7ae8b4874976d to your computer and use it in GitHub Desktop.
User Impersonation for ASP.NET Boilerplate
/* SAMPLE AJAX CALL to this action:
(This is enough since it's automatically redirected to the target tenant's ImpersonateSignIn action)
abp.ajax({
url: abp.appPath + 'Account/Impersonate',
data: JSON.stringify({
tenantId: 1, //Target tenant id (can be null if target user is a host user)
userId: 2 //Target user id
})
});
*/
[AbpMvcAuthorize(AppPermissions.Pages_Administration_Users_Impersonation)]
public virtual async Task<JsonResult> Impersonate(ImpersonateModel model)
{
CheckModelState();
if (AbpSession.ImpersonatorUserId.HasValue)
{
throw new UserFriendlyException(L("CascadeImpersonationErrorMessage"));
}
if (AbpSession.TenantId.HasValue)
{
if (!model.TenantId.HasValue)
{
throw new UserFriendlyException(L("FromTenantToHostImpersonationErrorMessage"));
}
if (model.TenantId.Value != AbpSession.TenantId.Value)
{
throw new UserFriendlyException(L("DifferentTenantImpersonationErrorMessage"));
}
}
return await SaveImpersonationTokenAndGetTargetUrl(model.TenantId, model.UserId, false);
}
[UnitOfWork]
public virtual async Task<ActionResult> ImpersonateSignIn(string tokenId)
{
var cacheItem = await _cacheManager.GetImpersonationCache().GetOrDefaultAsync(tokenId);
if (cacheItem == null)
{
throw new UserFriendlyException(L("ImpersonationTokenErrorMessage"));
}
//Switch to requested tenant
using (_unitOfWorkManager.Current.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, cacheItem.TargetTenantId))
{
//Get the user from tenant
var user = await _userManager.FindByIdAsync(cacheItem.TargetUserId);
//Create identity
var identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
if (!cacheItem.IsBackToImpersonator)
{
//Add claims for audit logging
if (cacheItem.ImpersonatorTenantId.HasValue)
{
identity.AddClaim(new Claim(AbpClaimTypes.ImpersonatorTenantId, cacheItem.ImpersonatorTenantId.Value.ToString(CultureInfo.InvariantCulture)));
}
identity.AddClaim(new Claim(AbpClaimTypes.ImpersonatorUserId, cacheItem.ImpersonatorUserId.ToString(CultureInfo.InvariantCulture)));
}
//Sign in with the target user
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
//Remove the cache item to prevent re-use
await _cacheManager.GetImpersonationCache().RemoveAsync(tokenId);
return RedirectToAction("Index", "Application");
}
}
/* SAMPLE AJAX CALL to this action:
(This is enough since it's automatically redirected to the host's ImpersonateSignIn action)
abp.ajax({
url: abp.appPath + 'Account/BackToImpersonator'
});
*/
public virtual async Task<JsonResult> BackToImpersonator()
{
if (!AbpSession.ImpersonatorUserId.HasValue)
{
throw new UserFriendlyException(L("NotImpersonatedLoginErrorMessage"));
}
return await SaveImpersonationTokenAndGetTargetUrl(AbpSession.ImpersonatorTenantId, AbpSession.ImpersonatorUserId.Value, true);
}
private async Task<JsonResult> SaveImpersonationTokenAndGetTargetUrl(int? tenantId, long userId, bool isBackToImpersonator)
{
//Create a cache item
var cacheItem = new ImpersonationCacheItem(
tenantId,
userId,
isBackToImpersonator
);
if (!isBackToImpersonator)
{
cacheItem.ImpersonatorTenantId = AbpSession.TenantId;
cacheItem.ImpersonatorUserId = AbpSession.GetUserId();
}
//Create a random token and save to the cache
var tokenId = Guid.NewGuid().ToString();
await _cacheManager
.GetImpersonationCache()
.SetAsync(tokenId, cacheItem, TimeSpan.FromMinutes(1));
//Find tenancy name
string tenancyName = null;
if (tenantId.HasValue)
{
tenancyName = (await _tenantManager.GetByIdAsync(tenantId.Value)).TenancyName;
}
//Create target URL
var targetUrl = _webUrlService.GetSiteRootAddress(tenancyName) + "Account/ImpersonateSignIn?tokenId=" + tokenId;
return Json(new MvcAjaxResponse { TargetUrl = targetUrl });
}
#endregion
[Serializable]
public class ImpersonationCacheItem
{
public const string CacheName = "AppImpersonationCache";
public int? ImpersonatorTenantId { get; set; }
public long ImpersonatorUserId { get; set; }
public int? TargetTenantId { get; set; }
public long TargetUserId { get; set; }
public bool IsBackToImpersonator { get; set; }
public ImpersonationCacheItem()
{
}
public ImpersonationCacheItem(int? targetTenantId, long targetUserId, bool isBackToImpersonator)
{
TargetTenantId = targetTenantId;
TargetUserId = targetUserId;
IsBackToImpersonator = isBackToImpersonator;
}
}
public static class ImpersonationCacheManagerExtensions
{
public static ITypedCache<string, ImpersonationCacheItem> GetImpersonationCache(this ICacheManager cacheManager)
{
return cacheManager.GetCache<string, ImpersonationCacheItem>(ImpersonationCacheItem.CacheName);
}
}
@hikalkan
Copy link
Author

This is the code we use in ASP.NET ZERO (http://aspnetzero.com) for user impersonation.

  • A tenant user can login as another tenant user (for same tenant).
  • A host user can login as any users of any tenant.

@antpstevens
Copy link

Hi,
I was trying to implement user impersonation. Couldn't find the ImpersonateModel class. Please could you suggest how to get it. Am I missing any reference?

@Osmandiyaka
Copy link

Please do u know how I can impersonate from a background job?

@hikalkan
Copy link
Author

If you are asking for AspNet Boilerplate, see https://aspnetboilerplate.com/Pages/Documents/Abp-Session#overriding-current-session-values

public class MyService
{
    private readonly IAbpSession _session;

    public MyService(IAbpSession session)
    {
        _session = session;
    }

    public void Test()
    {
        using (_session.Use(42, null))
        {
            var tenantId = _session.TenantId; //42
            var userId = _session.UserId; //null
        }
    }
}

@marifdev
Copy link

Hi, I have two different test site on Azure. When I click "Login as this user" or "Login as this tenant" on second site (for example testsite2.azurewebsites.net), Impersonate method returns the first sites url (testsite1.azurewebsites.net/Account/ImpersonateSignIn?tokenId=" + tokenId) and I get error like "Impersonation token is invalid or expired". This only happens on second site. First site is working as expected. I did not change anything on Account/Impersonate method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment