-
-
Save tamys/7af9f1667fcc9424b96d0b10c5e4421f to your computer and use it in GitHub Desktop.
User Impersonation for ASP.NET Boilerplate
This file contains hidden or 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
| /* 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 |
This file contains hidden or 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
| /* 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 |
This file contains hidden or 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
| [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; | |
| } | |
| } |
This file contains hidden or 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
| [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; | |
| } | |
| } |
This file contains hidden or 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
| public static class ImpersonationCacheManagerExtensions | |
| { | |
| public static ITypedCache<string, ImpersonationCacheItem> GetImpersonationCache(this ICacheManager cacheManager) | |
| { | |
| return cacheManager.GetCache<string, ImpersonationCacheItem>(ImpersonationCacheItem.CacheName); | |
| } | |
| } |
This file contains hidden or 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
| public static class ImpersonationCacheManagerExtensions | |
| { | |
| public static ITypedCache<string, ImpersonationCacheItem> GetImpersonationCache(this ICacheManager cacheManager) | |
| { | |
| return cacheManager.GetCache<string, ImpersonationCacheItem>(ImpersonationCacheItem.CacheName); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment