Roles management (#8535)

* Started adding roles that will be used to to allow users the ability to assign
specific roles to others.

* small refactor of the method to return dynamic AssignRole permissions

* Implemented permissions to limit the ability of a user to assign specific
roles

* controller action to assign roles

* Refactored Assign action in roles admin Controller
Fixed a bug in figuring out the id of roles to assign
Added provider to add action links to list of users in BO
Added "cancel" button to page to assign roles, based on returnUrl
This commit is contained in:
Matteo Piovanelli
2022-02-09 10:23:34 +01:00
committed by GitHub
parent 0d410d5ec6
commit f2a8450d90
16 changed files with 464 additions and 64 deletions

View File

@@ -2,10 +2,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.DisplayManagement;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Logging; using Orchard.Logging;
using Orchard.Mvc; using Orchard.Mvc;
using Orchard.Mvc.Extensions; using Orchard.Mvc.Extensions;
using Orchard.Roles.Events;
using Orchard.Roles.Models; using Orchard.Roles.Models;
using Orchard.Roles.Services; using Orchard.Roles.Services;
using Orchard.Roles.ViewModels; using Orchard.Roles.ViewModels;
@@ -17,20 +21,38 @@ namespace Orchard.Roles.Controllers {
public class AdminController : Controller { public class AdminController : Controller {
private readonly IRoleService _roleService; private readonly IRoleService _roleService;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IContentManager _contentManager;
private readonly IRepository<UserRolesPartRecord> _userRolesRepository;
private readonly INotifier _notifier;
private readonly IRoleEventHandler _roleEventHandlers;
public AdminController( public AdminController(
IOrchardServices services, IOrchardServices services,
IRoleService roleService, IRoleService roleService,
INotifier notifier, INotifier notifier,
IAuthorizationService authorizationService) { IAuthorizationService authorizationService,
IWorkContextAccessor workContextAccessor,
IContentManager contentManager,
IShapeFactory shapeFactory,
IRepository<UserRolesPartRecord> userRolesRepository,
IRoleEventHandler roleEventHandlers) {
Services = services; Services = services;
_roleService = roleService; _roleService = roleService;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_workContextAccessor = workContextAccessor;
_contentManager = contentManager;
_userRolesRepository = userRolesRepository;
_notifier = notifier;
_roleEventHandlers = roleEventHandlers;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
Shape = shapeFactory;
} }
dynamic Shape { get; set; }
public IOrchardServices Services { get; set; } public IOrchardServices Services { get; set; }
public Localizer T { get; set; } public Localizer T { get; set; }
public ILogger Logger { get; set; } public ILogger Logger { get; set; }
@@ -175,5 +197,118 @@ namespace Orchard.Roles.Controllers {
return this.RedirectLocal(returnUrl, () => RedirectToAction("Index")); return this.RedirectLocal(returnUrl, () => RedirectToAction("Index"));
} }
[Authorize]
public ActionResult Assign(int id) {
// CurrentUser is trying to access a page to assign roles to the User
// with Id id.
var currentUser = _workContextAccessor.GetContext().CurrentUser;
// Get the user whose roles we want to assign
var userRolesPart = _contentManager.Get<UserRolesPart>(id);
if (userRolesPart == null) {
return HttpNotFound();
}
// Check whether the current user has any of the required permissions
var allRoles = _roleService.GetRoles();
var authorizedRoleIds = allRoles
.Where(rr => _authorizationService.TryCheckAccess(
Permissions.CreatePermissionForAssignRole(rr.Name),
currentUser,
userRolesPart))
.Select(rr => rr.Id).ToList();
if (!authorizedRoleIds.Any()) {
return new HttpUnauthorizedResult();
}
// create the ViewModel used to manage a user's roles
var model = new UserRolesViewModel {
User = userRolesPart.As<IUser>(),
UserRoles = userRolesPart,
Roles = allRoles.Select(x => new UserRoleEntry {
RoleId = x.Id,
Name = x.Name,
Granted = userRolesPart.Roles.Contains(x.Name)
}).ToList(),
AuthorizedRoleIds = authorizedRoleIds
};
// this calls the same view used by the driver that lets users with higher
// permissions do the same.
return AssignView(model);
}
[HttpPost, ActionName("Assign"), Authorize]
public ActionResult AssignPOST(int id, string returnUrl) {
var currentUser = _workContextAccessor.GetContext().CurrentUser;
// Get the user whose roles we want to assign
var userRolesPart = _contentManager.Get<UserRolesPart>(id);
if (userRolesPart == null) {
return HttpNotFound();
}
// Check whether the current user has any of the required permissions
var allRoles = _roleService.GetRoles();
var authorizedRoleIds = allRoles
.Where(rr => _authorizationService.TryCheckAccess(
Permissions.CreatePermissionForAssignRole(rr.Name),
currentUser,
userRolesPart))
.Select(rr => rr.Id).ToList();
if (!authorizedRoleIds.Any()) {
return new HttpUnauthorizedResult();
}
// Start trying to update
var editModel = new UserRolesViewModel {
User = userRolesPart.As<IUser>(),
UserRoles = userRolesPart
};
if (TryUpdateModel(editModel)) {
// same logic that is used in the UserRolesPartDriver:
var currentUserRoleRecords = _userRolesRepository.Fetch(x => x.UserId == editModel.User.Id).ToArray();
var currentRoleRecords = currentUserRoleRecords.Select(x => x.Role);
// The roles the user should have after the update (pending a verification that
// the currentUser is allowed to assign them)
var targetRoleRecords = editModel.Roles.Where(x => x.Granted).Select(x => _roleService.GetRole(x.RoleId)).ToArray();
foreach (var addingRole in targetRoleRecords
.Where(x =>
// user doesn't have the role yet
!currentRoleRecords.Contains(x)
// && we are authorized to assign this role
&& authorizedRoleIds.Contains(x.Id))) {
_notifier.Warning(T("Adding role {0} to user {1}", addingRole.Name, userRolesPart.As<IUser>().UserName));
_userRolesRepository.Create(new UserRolesPartRecord { UserId = editModel.User.Id, Role = addingRole });
_roleEventHandlers.UserAdded(new UserAddedContext { Role = addingRole, User = editModel.User });
}
foreach (var removingRole in currentUserRoleRecords
.Where(x =>
// user has this role that they shouldn't
!targetRoleRecords.Contains(x.Role)
// && we are authorized to assign this role
&& authorizedRoleIds.Contains(x.Role.Id))) {
_notifier.Warning(T("Removing role {0} from user {1}", removingRole.Role.Name, userRolesPart.As<IUser>().UserName));
_userRolesRepository.Delete(removingRole);
_roleEventHandlers.UserRemoved(new UserRemovedContext { Role = removingRole.Role, User = editModel.User });
}
}
if (!ModelState.IsValid) {
editModel.AuthorizedRoleIds = authorizedRoleIds;
// Something went wrong in the update
Services.TransactionManager.Cancel();
return AssignView(editModel);
}
return this.RedirectLocal(returnUrl, () => RedirectToAction("Assign", new { id = id }));
}
private ActionResult AssignView(UserRolesViewModel editModel) {
var editor = Shape.EditorTemplate(
TemplateName: "Parts/Roles.UserRoles",
Model: editModel,
Prefix: null);
return View(editor
.UserName(editModel.User?.UserName));
}
} }
} }

View File

@@ -10,6 +10,7 @@ using Orchard.Roles.Services;
using Orchard.Roles.ViewModels; using Orchard.Roles.ViewModels;
using Orchard.Security; using Orchard.Security;
using Orchard.UI.Notify; using Orchard.UI.Notify;
using System.Collections.Generic;
namespace Orchard.Roles.Drivers { namespace Orchard.Roles.Drivers {
public class UserRolesPartDriver : ContentPartDriver<UserRolesPart> { public class UserRolesPartDriver : ContentPartDriver<UserRolesPart> {
@@ -22,11 +23,11 @@ namespace Orchard.Roles.Drivers {
private const string TemplateName = "Parts/Roles.UserRoles"; private const string TemplateName = "Parts/Roles.UserRoles";
public UserRolesPartDriver( public UserRolesPartDriver(
IRepository<UserRolesPartRecord> userRolesRepository, IRepository<UserRolesPartRecord> userRolesRepository,
IRoleService roleService, IRoleService roleService,
INotifier notifier, INotifier notifier,
IAuthenticationService authenticationService, IAuthenticationService authenticationService,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
IRoleEventHandler roleEventHandlers) { IRoleEventHandler roleEventHandlers) {
_userRolesRepository = userRolesRepository; _userRolesRepository = userRolesRepository;
@@ -36,6 +37,8 @@ namespace Orchard.Roles.Drivers {
_authorizationService = authorizationService; _authorizationService = authorizationService;
_roleEventHandlers = roleEventHandlers; _roleEventHandlers = roleEventHandlers;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
_allRoles = new Lazy<IEnumerable<RoleRecord>>(() => _roleService.GetRoles());
} }
protected override string Prefix { protected override string Prefix {
@@ -46,49 +49,90 @@ namespace Orchard.Roles.Drivers {
public Localizer T { get; set; } public Localizer T { get; set; }
private Lazy<IEnumerable<RoleRecord>> _allRoles;
protected override DriverResult Editor(UserRolesPart userRolesPart, dynamic shapeHelper) { protected override DriverResult Editor(UserRolesPart userRolesPart, dynamic shapeHelper) {
// don't show editor without apply roles permission
if (!_authorizationService.TryCheckAccess(Permissions.AssignRoles, _authenticationService.GetAuthenticatedUser(), userRolesPart))
return null;
return ContentShape("Parts_Roles_UserRoles_Edit", return ContentShape("Parts_Roles_UserRoles_Edit",
() => { () => {
var roles =_roleService.GetRoles().Select(x => new UserRoleEntry { var currentUser = _authenticationService.GetAuthenticatedUser();
RoleId = x.Id, // Get the roles we are authorized to assign
Name = x.Name, var authorizedRoleIds = _allRoles.Value
Granted = userRolesPart.Roles.Contains(x.Name)}); .Where(rr => _authorizationService.TryCheckAccess(
var model = new UserRolesViewModel { Permissions.CreatePermissionForAssignRole(rr.Name),
User = userRolesPart.As<IUser>(), currentUser,
UserRoles = userRolesPart, userRolesPart))
Roles = roles.ToList(), .Select(rr => rr.Id).ToList();
}; // If the user has no roles they can assign, we will show nothing
return shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix); if (!authorizedRoleIds.Any()) {
}); return null;
}
var allRoles = _allRoles.Value
.Select(x => new UserRoleEntry {
RoleId = x.Id,
Name = x.Name,
Granted = userRolesPart.Roles.Contains(x.Name)
});
var model = new UserRolesViewModel {
User = userRolesPart.As<IUser>(),
UserRoles = userRolesPart,
Roles = allRoles.ToList(),
AuthorizedRoleIds = authorizedRoleIds
};
return shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix);
});
} }
protected override DriverResult Editor(UserRolesPart userRolesPart, IUpdateModel updater, dynamic shapeHelper) { protected override DriverResult Editor(UserRolesPart userRolesPart, IUpdateModel updater, dynamic shapeHelper) {
// don't apply editor without apply roles permission
if (!_authorizationService.TryCheckAccess(Permissions.AssignRoles, _authenticationService.GetAuthenticatedUser(), userRolesPart))
return null;
var currentUser = _authenticationService.GetAuthenticatedUser();
// Get the roles we are authorized to assign
var authorizedRoleIds = _allRoles.Value
.Where(rr => _authorizationService.TryCheckAccess(
Permissions.CreatePermissionForAssignRole(rr.Name),
currentUser,
userRolesPart))
.Select(rr => rr.Id).ToList();
var model = BuildEditorViewModel(userRolesPart); var model = BuildEditorViewModel(userRolesPart);
if (updater.TryUpdateModel(model, Prefix, null, null)) { if (updater.TryUpdateModel(model, Prefix, null, null)) {
var currentUserRoleRecords = _userRolesRepository.Fetch(x => x.UserId == model.User.Id).ToArray(); // We only have something to do for the roles the user is allowed to assign. We do this check
var currentRoleRecords = currentUserRoleRecords.Select(x => x.Role); // after the TryUpdateModel so that even if we do nothing, we'll display things as the user
var targetRoleRecords = model.Roles.Where(x => x.Granted).Select(x => _roleService.GetRole(x.RoleId)).ToArray(); // changed them.
foreach (var addingRole in targetRoleRecords.Where(x => !currentRoleRecords.Contains(x))) { if (authorizedRoleIds.Any()) {
_notifier.Warning(T("Adding role {0} to user {1}", addingRole.Name, userRolesPart.As<IUser>().UserName)); // Find all RoleRecord objects for the user: these are roles that are already
_userRolesRepository.Create(new UserRolesPartRecord { UserId = model.User.Id, Role = addingRole }); // assigned to them.
_roleEventHandlers.UserAdded(new UserAddedContext {Role = addingRole, User = model.User}); var currentUserRoleRecords = _userRolesRepository.Fetch(x => x.UserId == model.User.Id).ToArray();
} var currentRoleRecords = currentUserRoleRecords.Select(x => x.Role);
foreach (var removingRole in currentUserRoleRecords.Where(x => !targetRoleRecords.Contains(x.Role))) { // The roles the user should have after the update (pending a verification that
_notifier.Warning(T("Removing role {0} from user {1}", removingRole.Role.Name, userRolesPart.As<IUser>().UserName)); // the currentUser is allowed to assign them)
_userRolesRepository.Delete(removingRole); var targetRoleRecords = model.Roles.Where(x => x.Granted).Select(x => _roleService.GetRole(x.RoleId)).ToArray();
_roleEventHandlers.UserRemoved(new UserRemovedContext { Role = removingRole.Role, User = model.User }); foreach (var addingRole in targetRoleRecords
.Where(x =>
// user doesn't have the role yet
!currentRoleRecords.Contains(x)
// && we are authorized to assign this role
&& authorizedRoleIds.Contains(x.Id))) {
_notifier.Warning(T("Adding role {0} to user {1}", addingRole.Name, userRolesPart.As<IUser>().UserName));
_userRolesRepository.Create(new UserRolesPartRecord { UserId = model.User.Id, Role = addingRole });
_roleEventHandlers.UserAdded(new UserAddedContext { Role = addingRole, User = model.User });
}
foreach (var removingRole in currentUserRoleRecords
.Where(x =>
// user has this role that they shouldn't
!targetRoleRecords.Contains(x.Role)
// && we are authorized to assign this role
&& authorizedRoleIds.Contains(x.Role.Id))) {
_notifier.Warning(T("Removing role {0} from user {1}", removingRole.Role.Name, userRolesPart.As<IUser>().UserName));
_userRolesRepository.Delete(removingRole);
_roleEventHandlers.UserRemoved(new UserRemovedContext { Role = removingRole.Role, User = model.User });
}
} }
} }
model.AuthorizedRoleIds = authorizedRoleIds;
return ContentShape("Parts_Roles_UserRoles_Edit", return ContentShape("Parts_Roles_UserRoles_Edit",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix)); () => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix));
} }
private static UserRolesViewModel BuildEditorViewModel(UserRolesPart userRolesPart) { private static UserRolesViewModel BuildEditorViewModel(UserRolesPart userRolesPart) {

View File

@@ -137,6 +137,7 @@
<Compile Include="Permissions.cs" /> <Compile Include="Permissions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Conditions\RoleCondition.cs" /> <Compile Include="Conditions\RoleCondition.cs" />
<Compile Include="Services\AssignRoleUserManagementActionsProvider.cs" />
<Compile Include="Services\IRoleService.cs" /> <Compile Include="Services\IRoleService.cs" />
<Compile Include="Services\RolesBasedAuthorizationService.cs" /> <Compile Include="Services\RolesBasedAuthorizationService.cs" />
<Compile Include="Services\RoleService.cs" /> <Compile Include="Services\RoleService.cs" />
@@ -216,6 +217,7 @@
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
<Content Include="Views\EditorTemplates\Parts\Roles.UserSuspensionSettings.cshtml" /> <Content Include="Views\EditorTemplates\Parts\Roles.UserSuspensionSettings.cshtml" />
<Content Include="Views\Admin\Assign.cshtml" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>

View File

@@ -1,18 +1,63 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Orchard.Data;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.Roles.Constants;
using Orchard.Roles.Models;
using Orchard.Roles.Services;
using Orchard.Security.Permissions; using Orchard.Security.Permissions;
namespace Orchard.Roles { namespace Orchard.Roles {
public class Permissions : IPermissionProvider { public class Permissions : IPermissionProvider {
private readonly IRepository<RoleRecord> _roleRepository;
public static readonly Permission ManageRoles = new Permission { Description = "Managing Roles", Name = "ManageRoles" }; public static readonly Permission ManageRoles = new Permission { Description = "Managing Roles", Name = "ManageRoles" };
public static readonly Permission AssignRoles = new Permission { Description = "Assign Roles", Name = "AssignRoles", ImpliedBy = new [] { ManageRoles } }; public static readonly Permission AssignRoles = new Permission { Description = "Assign Roles", Name = "AssignRoles", ImpliedBy = new[] { ManageRoles } };
public virtual Feature Feature { get; set; } public virtual Feature Feature { get; set; }
public IEnumerable<Permission> GetPermissions() { private static readonly Permission AssignRoleTemplate =
return new[] { new Permission {
ManageRoles, AssignRoles Description = "Assign Role - {0}",
Name = "AssignRole_{0}",
ImpliedBy = new[] { ManageRoles, AssignRoles }
}; };
public Permissions(
// A dependency on IRoleService to get the list of roles would lead to a
// circular dependency, because that service has methods to handle the
// permissions for each specific role.
IRepository<RoleRecord> roleRepository) {
_roleRepository = roleRepository;
}
public static Permission CreatePermissionForAssignRole(string roleName) {
return new Permission {
Description = string.Format(AssignRoleTemplate.Description, roleName),
Name = string.Format(AssignRoleTemplate.Name, roleName),
ImpliedBy = AssignRoleTemplate.ImpliedBy
};
}
private IEnumerable<Permission> GetAssignRolePermissions() {
var allRoleNames = _roleRepository.Table
.Select(r => r.Name)
.ToList()
// Never have to assign Anonymous or Authenticated roles
.Except(SystemRoles.GetSystemRoles());
foreach (var roleName in allRoleNames) {
yield return CreatePermissionForAssignRole(roleName);
}
}
public IEnumerable<Permission> GetPermissions() {
yield return ManageRoles;
yield return AssignRoles;
foreach (var permission in GetAssignRolePermissions()) {
yield return permission;
}
yield break;
} }
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() { public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Roles.Models;
using Orchard.Security;
using Orchard.Users.Services;
namespace Orchard.Roles.Services {
public class AssignRoleUserManagementActionsProvider : IUserManagementActionsProvider {
private readonly IRoleService _roleService;
private readonly IAuthorizationService _authorizationService;
private readonly IAuthenticationService _authenticationService;
private readonly IWorkContextAccessor _workContextAccessor;
private Lazy<IEnumerable<RoleRecord>> _allRoles;
public AssignRoleUserManagementActionsProvider(
IRoleService roleService,
IAuthorizationService authorizationService,
IAuthenticationService authenticationService,
IWorkContextAccessor workContextAccessor) {
_roleService = roleService;
_authorizationService = authorizationService;
_authenticationService = authenticationService;
_workContextAccessor = workContextAccessor;
T = NullLocalizer.Instance;
_allRoles = new Lazy<IEnumerable<RoleRecord>>(() => _roleService.GetRoles());
}
public Localizer T { get; set; }
public IEnumerable<Func<HtmlHelper, MvcHtmlString>> UserActionLinks(IUser user) {
// Get the user whose roles we want to assign
var userRolesPart = user.As<UserRolesPart>();
if (userRolesPart == null) {
yield break;
}
var currentUser = _authenticationService.GetAuthenticatedUser();
// Get the roles we are authorized to assign
var authorizedRoleIds = _allRoles.Value
.Where(rr => _authorizationService.TryCheckAccess(
Permissions.CreatePermissionForAssignRole(rr.Name),
currentUser,
userRolesPart))
.Select(rr => rr.Id).ToList();
// If the user has no roles they can assign, we will show nothing
if (!authorizedRoleIds.Any()) {
yield break;
}
yield return (Func<HtmlHelper, MvcHtmlString>)
(Html => Html.ActionLink(
T("Roles").ToString(),
"Assign",
new {
Area = "Orchard.Roles",
Controller = "Admin",
id = user.Id,
returnUrl = Html.ViewContext.RequestContext.HttpContext.Request.RawUrl
}));
}
}
}

View File

@@ -6,11 +6,13 @@ namespace Orchard.Roles.ViewModels {
public class UserRolesViewModel { public class UserRolesViewModel {
public UserRolesViewModel() { public UserRolesViewModel() {
Roles = new List<UserRoleEntry>(); Roles = new List<UserRoleEntry>();
AuthorizedRoleIds = new List<int>();
} }
public IUser User { get; set; } public IUser User { get; set; }
public IUserRoles UserRoles { get; set; } public IUserRoles UserRoles { get; set; }
public IList<UserRoleEntry> Roles { get; set; } public IList<UserRoleEntry> Roles { get; set; }
public IList<int> AuthorizedRoleIds { get; set; }
} }
public class UserRoleEntry { public class UserRoleEntry {

View File

@@ -0,0 +1,35 @@
@using Orchard.Utility.Extensions
@{
Layout.Title = T("Assign Roles").ToString();
var userName = (string)Model.UserName ?? string.Empty;
var returnUrl = Request.QueryString["returnUrl"];
}
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<div class="edit-item">
<div class="edit-item-primary">
<div class="edit-item-content">
<fieldset>
<legend>@T("Assign roles for user {0}", userName)</legend>
@* Model is a Shape, calling Display() so that it is rendered using the most specific template for its Shape type *@
@Display(Model)
</fieldset>
</div>
</div>
<div class="edit-item-secondary group">
<div class="edit-item-sidebar group">
<fieldset class="save-button">
<button class="primaryAction" type="submit">@T("Save")</button>
</fieldset>
@if (!String.IsNullOrWhiteSpace(returnUrl) && Request.IsLocalUrl(returnUrl)) {
<fieldset class="cancel-button">
<a id="button-cancel" href="@returnUrl" class="button">@T("Cancel")</a>
</fieldset>
}
</div>
</div>
</div>
}

View File

@@ -18,14 +18,17 @@
@Html.Hidden("Roles[" + index + "].Name", entry.Name) @Html.Hidden("Roles[" + index + "].Name", entry.Name)
<div> <div>
@Html.CheckBox("Roles[" + index + "].Granted", entry.Granted) @if (Model.AuthorizedRoleIds.Contains(entry.RoleId)) {
@Html.CheckBox("Roles[" + index + "].Granted", entry.Granted)
} else {
@Html.CheckBox("Roles[" + index + "].Granted", entry.Granted, new { disabled = true })
}
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Roles[index].Granted)">@entry.Name</label> <label class="forcheckbox" for="@Html.FieldIdFor(m => m.Roles[index].Granted)">@entry.Name</label>
</div> </div>
index++; index++;
} }
} } else {
else {
<p>@T("There are no roles.")</p> <p>@T("There are no roles.")</p>
} }
</fieldset> </fieldset>

View File

@@ -12,7 +12,7 @@ namespace Orchard.Users {
.Add(T("Users"), "11", .Add(T("Users"), "11",
menu => menu.Action("Index", "Admin", new { area = "Orchard.Users" }) menu => menu.Action("Index", "Admin", new { area = "Orchard.Users" })
.Add(T("Users"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Users" }) .Add(T("Users"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Users" })
.LocalNav().Permission(Permissions.ManageUsers))); .LocalNav().Permission(Permissions.ViewUsers)));
} }
} }
} }

View File

@@ -27,6 +27,7 @@ namespace Orchard.Users.Controllers {
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IUserEventHandler _userEventHandlers; private readonly IUserEventHandler _userEventHandlers;
private readonly ISiteService _siteService; private readonly ISiteService _siteService;
private readonly IEnumerable<IUserManagementActionsProvider> _userManagementActionsProviders;
public AdminController( public AdminController(
IOrchardServices services, IOrchardServices services,
@@ -34,13 +35,15 @@ namespace Orchard.Users.Controllers {
IUserService userService, IUserService userService,
IShapeFactory shapeFactory, IShapeFactory shapeFactory,
IUserEventHandler userEventHandlers, IUserEventHandler userEventHandlers,
ISiteService siteService) { ISiteService siteService,
IEnumerable<IUserManagementActionsProvider> userManagementActionsProviders) {
Services = services; Services = services;
_membershipService = membershipService; _membershipService = membershipService;
_userService = userService; _userService = userService;
_userEventHandlers = userEventHandlers; _userEventHandlers = userEventHandlers;
_siteService = siteService; _siteService = siteService;
_userManagementActionsProviders = userManagementActionsProviders;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
Shape = shapeFactory; Shape = shapeFactory;
@@ -51,7 +54,7 @@ namespace Orchard.Users.Controllers {
public Localizer T { get; set; } public Localizer T { get; set; }
public ActionResult Index(UserIndexOptions options, PagerParameters pagerParameters) { public ActionResult Index(UserIndexOptions options, PagerParameters pagerParameters) {
if (!Services.Authorizer.Authorize(Permissions.ManageUsers, T("Not authorized to list users"))) if (!Services.Authorizer.Authorize(Permissions.ViewUsers, T("Not authorized to list users")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters); var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
@@ -102,7 +105,12 @@ namespace Orchard.Users.Controllers {
var model = new UsersIndexViewModel { var model = new UsersIndexViewModel {
Users = results Users = results
.Select(x => new UserEntry { User = x.Record }) .Select(x => new UserEntry {
UserPart = x,
User = x.Record,
AdditionalActionLinks = _userManagementActionsProviders
.SelectMany(p => p.UserActionLinks(x)).ToList()
})
.ToList(), .ToList(),
Options = options, Options = options,
Pager = pagerShape Pager = pagerShape

View File

@@ -140,7 +140,9 @@
<Compile Include="Services\ApproveUserService.cs" /> <Compile Include="Services\ApproveUserService.cs" />
<Compile Include="Services\AccountValidationService.cs" /> <Compile Include="Services\AccountValidationService.cs" />
<Compile Include="Services\AuthenticationRedirectionFilter.cs" /> <Compile Include="Services\AuthenticationRedirectionFilter.cs" />
<Compile Include="Services\DefaultUserManagementActionsProvider.cs" />
<Compile Include="Services\InactiveUserSuspensionBackgroundTask.cs" /> <Compile Include="Services\InactiveUserSuspensionBackgroundTask.cs" />
<Compile Include="Services\IUserManagementActionsProvider.cs" />
<Compile Include="Services\IUserService.cs" /> <Compile Include="Services\IUserService.cs" />
<Compile Include="Services\IUserSuspensionConditionProvider.cs" /> <Compile Include="Services\IUserSuspensionConditionProvider.cs" />
<Compile Include="Services\MembershipValidationService.cs" /> <Compile Include="Services\MembershipValidationService.cs" />

View File

@@ -4,13 +4,16 @@ using Orchard.Security.Permissions;
namespace Orchard.Users { namespace Orchard.Users {
public class Permissions : IPermissionProvider { public class Permissions : IPermissionProvider {
public static readonly Permission ManageUsers = new Permission { Description = "Managing Users", Name = "ManageUsers" }; public static readonly Permission ManageUsers =
new Permission { Description = "Managing Users", Name = "ManageUsers" };
public static readonly Permission ViewUsers =
new Permission { Description = "View List of Users", Name = "ViewUsers", ImpliedBy = new[] { ManageUsers } };
public virtual Feature Feature { get; set; } public virtual Feature Feature { get; set; }
public IEnumerable<Permission> GetPermissions() { public IEnumerable<Permission> GetPermissions() {
return new[] { return new[] {
ManageUsers, ManageUsers, ViewUsers
}; };
} }
@@ -18,7 +21,7 @@ namespace Orchard.Users {
return new[] { return new[] {
new PermissionStereotype { new PermissionStereotype {
Name = "Administrator", Name = "Administrator",
Permissions = new[] {ManageUsers} Permissions = new[] { ManageUsers, ViewUsers }
}, },
}; };
} }

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Orchard.Security;
namespace Orchard.Users.Services {
public class DefaultUserManagementActionsProvider : IUserManagementActionsProvider {
public IEnumerable<Func<HtmlHelper, MvcHtmlString>> UserActionLinks(IUser user) {
yield break;
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Orchard.Security;
namespace Orchard.Users.Services {
public interface IUserManagementActionsProvider : IDependency {
// Using a delegate so implementations don't have to build/figure out
// their own HtmlHelper
IEnumerable<Func<HtmlHelper, MvcHtmlString>> UserActionLinks(IUser user);
}
}

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Orchard.Users.Models; using Orchard.Users.Models;
namespace Orchard.Users.ViewModels { namespace Orchard.Users.ViewModels {
@@ -10,8 +12,14 @@ namespace Orchard.Users.ViewModels {
} }
public class UserEntry { public class UserEntry {
public UserEntry() {
AdditionalActionLinks = new List<Func<HtmlHelper, MvcHtmlString>>();
}
public UserPart UserPart { get; set; }
public UserPartRecord User { get; set; } public UserPartRecord User { get; set; }
public bool IsChecked { get; set; } public bool IsChecked { get; set; }
public List<Func<HtmlHelper, MvcHtmlString>> AdditionalActionLinks { get; set; }
} }
public class UserIndexOptions { public class UserIndexOptions {

View File

@@ -1,4 +1,5 @@
@model Orchard.Users.ViewModels.UsersIndexViewModel @model Orchard.Users.ViewModels.UsersIndexViewModel
@using Orchard.Users;
@using Orchard.Users.Models; @using Orchard.Users.Models;
@using Orchard.Users.ViewModels; @using Orchard.Users.ViewModels;
@{ @{
@@ -53,6 +54,7 @@
</tr> </tr>
</thead> </thead>
@foreach (var entry in Model.Users) { @foreach (var entry in Model.Users) {
var canManageUser = Authorizer.Authorize(Permissions.ManageUsers, entry.UserPart);
<tr> <tr>
<td> <td>
<input type="hidden" value="@Model.Users[userIndex].User.Id" name="@Html.NameOf(m => m.Users[userIndex].User.Id)" /> <input type="hidden" value="@Model.Users[userIndex].User.Id" name="@Html.NameOf(m => m.Users[userIndex].User.Id)" />
@@ -65,32 +67,43 @@
else { else {
<img class="icon" src="@Href("~/Modules/Orchard.Users/Content/Admin/images/offline.gif") " alt="@T("Moderated") " title="@if (entry.User.EmailStatus == UserStatus.Approved) { @T("User is moderated") } else { @T("E-mail validation is pending") }" /> <img class="icon" src="@Href("~/Modules/Orchard.Users/Content/Admin/images/offline.gif") " alt="@T("Moderated") " title="@if (entry.User.EmailStatus == UserStatus.Approved) { @T("User is moderated") } else { @T("E-mail validation is pending") }" />
} }
@Html.ActionLink(entry.User.UserName, "Edit", new { entry.User.Id }) @if (canManageUser) {
@Html.ActionLink(entry.User.UserName, "Edit", new { entry.User.Id })
} else {
@entry.User.UserName
}
</td> </td>
<td> <td>
@entry.User.Email @entry.User.Email
</td> </td>
<td> <td>
<ul class="action-links"> <ul class="action-links">
<li class="action-link"> @if (canManageUser) {
@Html.ActionLink(T("Edit").ToString(), "Edit", new { entry.User.Id }) @* TODO: what permission should we check for the "Edit" Action *@
</li>
<li class="action-link">
@Html.ActionLink(T("Delete").ToString(), "Delete", new { entry.User.Id }, new { itemprop = "RemoveUrl UnsafeUrl" })
</li>
@if (entry.User.RegistrationStatus == UserStatus.Pending) {
<li class="action-link"> <li class="action-link">
@Html.ActionLink(T("Approve").ToString(), "Approve", new { entry.User.Id }, new { itemprop = "UnsafeUrl" }) @Html.ActionLink(T("Edit").ToString(), "Edit", new { entry.User.Id })
</li> </li>
}
else {
<li class="action-link"> <li class="action-link">
@Html.ActionLink(T("Disable").ToString(), "Moderate", new { entry.User.Id }, new { itemprop = "UnsafeUrl" }) @Html.ActionLink(T("Delete").ToString(), "Delete", new { entry.User.Id }, new { itemprop = "RemoveUrl UnsafeUrl" })
</li> </li>
if (entry.User.RegistrationStatus == UserStatus.Pending) {
<li class="action-link">
@Html.ActionLink(T("Approve").ToString(), "Approve", new { entry.User.Id }, new { itemprop = "UnsafeUrl" })
</li>
} else {
<li class="action-link">
@Html.ActionLink(T("Disable").ToString(), "Moderate", new { entry.User.Id }, new { itemprop = "UnsafeUrl" })
</li>
}
if (entry.User.EmailStatus == UserStatus.Pending) {
<li class="action-link">
@Html.ActionLink(T("Send challenge E-mail").ToString(), "SendChallengeEmail", new { entry.User.Id }, new { itemprop = "UnsafeUrl" })
</li>
}
} }
@if (entry.User.EmailStatus == UserStatus.Pending) { @foreach(var actionLink in entry.AdditionalActionLinks) {
<li class="action-link"> <li class="action-link">
@Html.ActionLink(T("Send challenge E-mail").ToString(), "SendChallengeEmail", new { entry.User.Id }, new { itemprop = "UnsafeUrl" }) @actionLink(Html)
</li> </li>
} }
</ul> </ul>