Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 30 additions & 39 deletions src/OrchardCoreContrib.Users/AdminMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,42 @@
using OrchardCore.Modules;
using OrchardCore.Mvc.Core.Utilities;
using OrchardCore.Navigation;
using OrchardCoreContrib.Navigation;
using OrchardCoreContrib.Users.Controllers;

namespace OrchardCoreContrib.Users
{
using OrchardCoreContrib.Navigation;
namespace OrchardCoreContrib.Users;

/// <summary>
/// Represents an admin menu for the impersonation feature.
/// </summary>
[Feature("OrchardCoreContrib.Users.Impersonation")]
public class AdminMenu : AdminNavigationProvider
{
private readonly HttpContext _httpContext;
private readonly IStringLocalizer S;
using OrchardCoreContrib.Navigation;

/// <summary>
/// Initializes a new instance of <see cref="AdminMenu"/>.
/// </summary>
/// <param name="localizer">The <see cref="IStringLocalizer<AdminMenu>"/>.</param>
public AdminMenu(
IHttpContextAccessor httpContextAccessor,
IStringLocalizer<AdminMenu> localizer)
{
_httpContext = httpContextAccessor.HttpContext;
S = localizer;
}
/// <summary>
/// Represents an admin menu for the impersonation feature.
/// </summary>
/// <remarks>
/// Initializes a new instance of <see cref="AdminMenu"/>.
/// </remarks>
/// <param name="S">The <see cref="IStringLocalizer<AdminMenu>"/>.</param>
[Feature("OrchardCoreContrib.Users.Impersonation")]
public class AdminMenu(
IHttpContextAccessor httpContextAccessor,
IStringLocalizer<AdminMenu> S) : AdminNavigationProvider
{
private readonly HttpContext _httpContext = httpContextAccessor.HttpContext;

/// <inheritdoc/>
public override void BuildNavigation(NavigationBuilder builder)
/// <inheritdoc/>
public override void BuildNavigation(NavigationBuilder builder)
{
var isImpersonatingClaim = _httpContext.User.FindFirst(ClaimTypesExtended.IsImpersonating);

if (isImpersonatingClaim?.Value == "true")
{
var isImpersonatingClaim = _httpContext.User.FindFirst(ClaimTypesExtended.IsImpersonating);

if (isImpersonatingClaim?.Value == "true")
{
builder
.Add(S["Security"], NavigationConstants.AdminMenuSecurityPosition, security => security
.AddClass("security").Id("security")
.Add(S["End Impersonation"], S["End Impersonation"].PrefixPosition(), users => users
.AddClass("endImpersonation").Id("endImpersonation")
.Action(nameof(ImpersonationController.EndImpersonatation), typeof(ImpersonationController).ControllerName(), "OrchardCoreContrib.Users")
.LocalNav()
)
);
}
builder
.Add(S["Security"], NavigationConstants.AdminMenuSecurityPosition, security => security
.AddClass("security").Id("security")
.Add(S["End Impersonation"], S["End Impersonation"].PrefixPosition(), users => users
.AddClass("endImpersonation").Id("endImpersonation")
.Action(nameof(ImpersonationController.EndImpersonatation), typeof(ImpersonationController).ControllerName(), "OrchardCoreContrib.Users")
.LocalNav()
)
);
}
}
}
25 changes: 12 additions & 13 deletions src/OrchardCoreContrib.Users/ClaimTypesExtended.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
namespace OrchardCoreContrib.Users
namespace OrchardCoreContrib.Users;

/// <summary>
/// Represents a claim types that be used for impersonation process.
/// </summary>
public static class ClaimTypesExtended
{
/// <summary>
/// Represents a claim types that be used for impersonation process.
/// Gets the impersonator name.
/// </summary>
public static class ClaimTypesExtended
{
/// <summary>
/// Gets the impersonator name.
/// </summary>
public static readonly string ImpersonatorNameIdentifier = nameof(ImpersonatorNameIdentifier);
public static readonly string ImpersonatorNameIdentifier = nameof(ImpersonatorNameIdentifier);

/// <summary>
/// Gets whether the current user is impersonated or not.
/// </summary>
public static readonly string IsImpersonating = nameof(IsImpersonating);
}
/// <summary>
/// Gets whether the current user is impersonated or not.
/// </summary>
public static readonly string IsImpersonating = nameof(IsImpersonating);
}
102 changes: 41 additions & 61 deletions src/OrchardCoreContrib.Users/Controllers/ImpersonationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,91 +2,71 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrchardCore.Admin;
using OrchardCore.Modules;
using OrchardCore.Users;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace OrchardCoreContrib.Users.Controllers
namespace OrchardCoreContrib.Users.Controllers;

[Authorize]
[Feature("OrchardCoreContrib.Users.Impersonation")]
public class ImpersonationController(
SignInManager<IUser> signInManager,
UserManager<IUser> userManager,
IAuthorizationService authorizationService,
IOptions<AdminOptions> adminOptions,
ILogger<ImpersonationController> logger) : Controller
{
[Authorize]
[Feature("OrchardCoreContrib.Users.Impersonation")]
public class ImpersonationController : Controller
private readonly AdminOptions _adminOptions = adminOptions.Value;

public async Task<IActionResult> ImpersonateUser(string userId)
{
private readonly SignInManager<IUser> _signInManager;
private readonly UserManager<IUser> _userManager;
private readonly IAuthorizationService _authorizationService;
private readonly AdminOptions _adminOptions;
private readonly ILogger _logger;
private readonly IStringLocalizer S;

public ImpersonationController(
SignInManager<IUser> signInManager,
UserManager<IUser> userManager,
IAuthorizationService authorizationService,
IOptions<AdminOptions> adminOptions,
ILogger<ImpersonationController> logger,
IStringLocalizer<ImpersonationController> stringLocalizer)
if (!await authorizationService.AuthorizeAsync(User, UsersPermissions.ManageImpersonationSettings))
{
_signInManager = signInManager;
_userManager = userManager;
_authorizationService = authorizationService;
_adminOptions = adminOptions.Value;
_logger = logger;
S = stringLocalizer;
return Forbid();
}

public async Task<IActionResult> ImpersonateUser(string userId)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageImpersonationSettings))
{
return Forbid();
}
var currentUserId = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
var impersonatedUser = await userManager.FindByIdAsync(userId);
var impersonatedUserPrincipal = await signInManager.CreateUserPrincipalAsync(impersonatedUser);

var currentUserId = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
var impersonatedUser = await _userManager.FindByIdAsync(userId);
var impersonatedUserPrincipal = await _signInManager.CreateUserPrincipalAsync(impersonatedUser);
impersonatedUserPrincipal.Identities.First().AddClaim(new Claim(ClaimTypesExtended.ImpersonatorNameIdentifier, currentUserId));
impersonatedUserPrincipal.Identities.First().AddClaim(new Claim(ClaimTypesExtended.IsImpersonating, "true"));

impersonatedUserPrincipal.Identities.First().AddClaim(new Claim(ClaimTypesExtended.ImpersonatorNameIdentifier, currentUserId));
impersonatedUserPrincipal.Identities.First().AddClaim(new Claim(ClaimTypesExtended.IsImpersonating, "true"));
await signInManager.SignOutAsync();
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, impersonatedUserPrincipal);

await _signInManager.SignOutAsync();
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, impersonatedUserPrincipal);
logger.LogInformation(1, "User '{0}' logged as '{1}'.", User.Identity.Name, impersonatedUser.UserName);

_logger.LogInformation(1, S["User '{0}' logged as '{1}'.", User.Identity.Name, impersonatedUser.UserName]);

return Redirect($"~/{_adminOptions.AdminUrlPrefix}");
}
return Redirect($"~/{_adminOptions.AdminUrlPrefix}");
}

public async Task<IActionResult> EndImpersonatation()
public async Task<IActionResult> EndImpersonatation()
{
var isImpersonatingClaim = HttpContext.User.FindFirst(ClaimTypesExtended.IsImpersonating);
if (isImpersonatingClaim == null || isImpersonatingClaim?.Value != "true")
{
var isImpersonatingClaim = HttpContext.User.FindFirst(ClaimTypesExtended.IsImpersonating);
if (isImpersonatingClaim == null || isImpersonatingClaim?.Value != "true")
{
return Forbid();
}
return Forbid();
}

var impersonatorUserId = HttpContext.User.Claims.First(c => c.Type == ClaimTypesExtended.ImpersonatorNameIdentifier).Value;
var impersonatorUserId = HttpContext.User.Claims.First(c => c.Type == ClaimTypesExtended.ImpersonatorNameIdentifier).Value;

var impersonatedUserId = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
var impersonatedUser = await _userManager.FindByIdAsync(impersonatedUserId);
var impersonatedUserPrincipal = await _signInManager.CreateUserPrincipalAsync(impersonatedUser);
var impersonatedUserId = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
var impersonatedUser = await userManager.FindByIdAsync(impersonatedUserId);
var impersonatedUserPrincipal = await signInManager.CreateUserPrincipalAsync(impersonatedUser);

await _signInManager.SignOutAsync();
await signInManager.SignOutAsync();

var impersonaterUser = await _userManager.FindByIdAsync(impersonatorUserId);
var impersonaterUserPrincipal = await _signInManager.CreateUserPrincipalAsync(impersonaterUser);
var impersonaterUser = await userManager.FindByIdAsync(impersonatorUserId);
var impersonaterUserPrincipal = await signInManager.CreateUserPrincipalAsync(impersonaterUser);

await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, impersonaterUserPrincipal);
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, impersonaterUserPrincipal);

_logger.LogInformation(1, S["Swicthed back to the '{0}' user.", impersonaterUser.UserName]);
logger.LogInformation(1, "Swicthed back to the '{0}' user.", impersonaterUser.UserName);

return Redirect($"~/{_adminOptions.AdminUrlPrefix}");
}
return Redirect($"~/{_adminOptions.AdminUrlPrefix}");
}
}
21 changes: 17 additions & 4 deletions src/OrchardCoreContrib.Users/Drivers/ImpersonationDisplayDriver.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using OrchardCore.DisplayManagement.Handlers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Modules;
using OrchardCore.Users.Models;
Expand All @@ -7,9 +9,20 @@
namespace OrchardCoreContrib.Users.Drivers;

[Feature("OrchardCoreContrib.Users.Impersonation")]
public class ImpersonationDisplayDriver : DisplayDriver<User>
public class ImpersonationDisplayDriver(
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService) : DisplayDriver<User>
{
public override IDisplayResult Display(User user, BuildDisplayContext context)
=> Initialize<SummaryAdminUserViewModel>("ImpersonationButton", model => model.User = user)
private readonly HttpContext _httpContext = httpContextAccessor.HttpContext;

public async override Task<IDisplayResult> DisplayAsync(User user, BuildDisplayContext context)
{
if (!await authorizationService.AuthorizeAsync(_httpContext.User, UsersPermissions.ManageImpersonationSettings))
{
return null;
}

return Initialize<SummaryAdminUserViewModel>("ImpersonationButton", model => model.User = user)
.Location("SummaryAdmin", "Actions:2");
}
}
2 changes: 1 addition & 1 deletion src/OrchardCoreContrib.Users/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Name = "Users",
Author = ManifestConstants.Author,
Website = ManifestConstants.Website,
Version = "1.5.1",
Version = "1.7.0",
Category = "Security"
)]

Expand Down
4 changes: 3 additions & 1 deletion src/OrchardCoreContrib.Users/OrchardCoreContrib.Users.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<VersionPrefix>1.6.0</VersionPrefix>
<VersionPrefix>1.7.0</VersionPrefix>
<Authors>The Orchard Core Contrib Team</Authors>
<Company />
<Description>Provides a list of users features such as Impersonation.</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/OrchardCoreContrib/OrchardCoreContrib.Modules/tree/main/src/OrchardCoreContrib.Users/README.md</PackageProjectUrl>
<RepositoryUrl>https://github.com/OrchardCoreContrib/OrchardCoreContrib.Modules</RepositoryUrl>
Expand All @@ -26,6 +27,7 @@

<ItemGroup>
<None Include="../../images/icon.png" Pack="true" PackagePath="icon.png" />
<None Include="README.md" Pack="true" PackagePath="\"/>
</ItemGroup>

<ItemGroup>
Expand Down
58 changes: 23 additions & 35 deletions src/OrchardCoreContrib.Users/Permissions.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,32 @@
using OrchardCore.Modules;
using OrchardCore.Security;
using OrchardCore.Security.Permissions;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OrchardCoreContrib.Users
namespace OrchardCoreContrib.Users;

/// <summary>
/// Represents a permissions that will be applied into users module.
/// </summary>
[Feature("OrchardCoreContrib.Users.Impersonation")]
public class Permissions : IPermissionProvider
{
/// <summary>
/// Represents a permissions that will be applied into users module.
/// </summary>
[Feature("OrchardCoreContrib.Users.Impersonation")]
public class Permissions : IPermissionProvider
{
/// <summary>
/// Gets a permission for managing a impersonation settings.
/// </summary>
public static readonly Permission ManageImpersonationSettings = new Permission("ManageImpersonationSettings", "Manage Impersonation Settings", isSecurityCritical: true);
private readonly IEnumerable<Permission> _allPermissions =
[
UsersPermissions.ManageImpersonationSettings,
];

/// <inheritdoc/>
public Task<IEnumerable<Permission>> GetPermissionsAsync()
{
return Task.FromResult(new[]
{
ManageImpersonationSettings
}
.AsEnumerable());
}
/// <inheritdoc/>
public Task<IEnumerable<Permission>> GetPermissionsAsync() => Task.FromResult(_allPermissions);

/// <inheritdoc/>
public IEnumerable<PermissionStereotype> GetDefaultStereotypes()
{
return new[]
/// <inheritdoc/>
public IEnumerable<PermissionStereotype> GetDefaultStereotypes()
{
return
[
new PermissionStereotype
{
new PermissionStereotype
{
Name = "Administrator",
Permissions = new[] { ManageImpersonationSettings, StandardPermissions.SiteOwner }
},
};
}
Name = "Administrator",
Permissions = _allPermissions
},
];
}
}
3 changes: 2 additions & 1 deletion src/OrchardCoreContrib.Users/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This module provides features for users management.

## Version

1.5.1
1.7.0

## Category

Expand Down Expand Up @@ -34,6 +34,7 @@ Security

| Name | Version |
|---------------------------------------------------------------------------------------------|---------|
| [`OrchardCoreContrib.Users`](https://www.nuget.org/packages/OrchardCoreContrib.Users/1.7.0) | 1.7.0 |
| [`OrchardCoreContrib.Users`](https://www.nuget.org/packages/OrchardCoreContrib.Users/1.6.0) | 1.6.0 |
| [`OrchardCoreContrib.Users`](https://www.nuget.org/packages/OrchardCoreContrib.Users/1.5.1) | 1.5.1 |
| [`OrchardCoreContrib.Users`](https://www.nuget.org/packages/OrchardCoreContrib.Users/1.5.0) | 1.5.0 |
Expand Down
Loading
Loading