Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend user permissions #12407

Merged
merged 35 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b5391e0
Add flexible roles to users module
MikeAlhayek Sep 12, 2022
2cdf40a
Clean up permissions
MikeAlhayek Sep 13, 2022
ee1b428
Fix issues when Role module is disabled
MikeAlhayek Sep 13, 2022
b1ca65f
Add SimplifiedTypeName to RolesDocument since the assumbly name changed
MikeAlhayek Sep 13, 2022
ea0e953
Simplify AssignUserToRole and ManageUsersInRole_
MikeAlhayek Sep 13, 2022
2311bac
Unfixing #12406
MikeAlhayek Sep 14, 2022
473d76b
Merge branch 'main' into ExtendUsers
MikeAlhayek Sep 17, 2022
e3d0e50
Address some feedback around names
MikeAlhayek Sep 17, 2022
0a252f2
Merge branch 'ExtendUsers' of https://github.com/CrestApps/OrchardCor…
MikeAlhayek Sep 17, 2022
7567174
Merge branch 'main' into ExtendUsers
MikeAlhayek Dec 22, 2022
369f955
Cleanup
MikeAlhayek Dec 22, 2022
8006a36
Fix permission naming
MikeAlhayek Dec 22, 2022
ee4e7bc
Remove UserRoleIdex and use UserByRoleNameIndex instead
MikeAlhayek Dec 22, 2022
06c2243
Remove ManageUserProfileSettings permission since it is no longer needed
MikeAlhayek Dec 22, 2022
586f38e
Merge branch 'OrchardCMS:main' into ExtendUsers
MikeAlhayek Jan 4, 2023
3bf2b90
Clean up and add documenations
MikeAlhayek Jan 5, 2023
a8ae911
Merge branch 'main' into ExtendUsers
MikeAlhayek Jan 9, 2023
918b1e1
UI Fix
MikeAlhayek Jan 10, 2023
1058d08
Merge branch 'OrchardCMS:main' into ExtendUsers
MikeAlhayek Jan 10, 2023
9ab646f
Merge branch 'OrchardCMS:main' into ExtendUsers
MikeAlhayek Jan 12, 2023
b5bc917
Minor adjustments Fix #12749
MikeAlhayek Jan 12, 2023
bda7efd
Remove unused usings
MikeAlhayek Jan 12, 2023
3eccb5f
Merge branch 'main' into ExtendUsers
MikeAlhayek Jan 13, 2023
306a9c9
Adding RoleHelper for system roles
MikeAlhayek Jan 13, 2023
0589f60
Cleanup and add display action for user.
MikeAlhayek Jan 15, 2023
a36081c
use normalized name is the views
MikeAlhayek Jan 15, 2023
4e994a2
Remove ununsed class
MikeAlhayek Jan 15, 2023
72dda08
Update userid check
MikeAlhayek Jan 15, 2023
ff61b36
Normalize user "Administrator" role on setup.
jtkech Jan 16, 2023
2410929
Update permissions
MikeAlhayek Jan 17, 2023
b3d3919
Merge branch 'ExtendUsers' of https://github.com/MikeAlhayek/OrchardC…
MikeAlhayek Jan 17, 2023
d01a2f1
Cleanup
MikeAlhayek Jan 17, 2023
f6917f4
Merge remote-tracking branch 'origin/main' into ExtendUsers
jtkech Jan 18, 2023
f46c1e3
Minor change
jtkech Jan 18, 2023
cbbf241
Minor UI changes
MikeAlhayek Jan 18, 2023
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
2 changes: 1 addition & 1 deletion src/OrchardCore.Modules/OrchardCore.Users/AdminMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder)
.Add(S["Users"], S["Users"].PrefixPosition(), users => users
.AddClass("users").Id("users")
.Action("Index", "Admin", "OrchardCore.Users")
.Permission(Permissions.ViewUsers)
.Permission(CommonPermissions.ListUsers)
.Resource(new User())
.LocalNav()
)
Expand Down
189 changes: 87 additions & 102 deletions src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ public override async Task<IDisplayResult> EditAsync(LoginSettings settings, Bui
model.DisableLocalLogin = settings.DisableLocalLogin;
model.UseScriptToSyncRoles = settings.UseScriptToSyncRoles;
model.SyncRolesScript = settings.SyncRolesScript;
model.AllowChangingEmail = settings.AllowChangingEmail;
model.AllowChangingUsername = settings.AllowChangingUsername;
}).Location("Content:5").OnGroup(GroupId);
}

public override async Task<IDisplayResult> UpdateAsync(LoginSettings section, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageUsers))
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext?.User, Permissions.ManageUsers))
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Modules;
using OrchardCore.Mvc.ModelBinding;
using OrchardCore.Users.Handlers;
using OrchardCore.Users.Models;
using OrchardCore.Users.ViewModels;
using OrchardCore.Mvc.ModelBinding;
using Microsoft.Extensions.Localization;

namespace OrchardCore.Users.Drivers
{
Expand All @@ -27,7 +27,7 @@ public class UserDisplayDriver : DisplayDriver<User>
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly INotifier _notifier;
private readonly IAuthorizationService _authorizationService;
private IEnumerable<IUserEventHandler> _userEventHandlers;
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
private readonly ILogger _logger;
private readonly IHtmlLocalizer H;
private readonly IStringLocalizer S;
Expand Down Expand Up @@ -56,32 +56,36 @@ public override IDisplayResult Display(User user)
{
return Combine(
Initialize<SummaryAdminUserViewModel>("UserFields", model => model.User = user).Location("SummaryAdmin", "Header:1"),
Initialize<SummaryAdminUserViewModel>("UserInfo", model => model.User = user).Location("DetailAdmin", "Content:5"),
Initialize<SummaryAdminUserViewModel>("UserButtons", model => model.User = user).Location("SummaryAdmin", "Actions:1")
);
}

public override Task<IDisplayResult> EditAsync(User user, BuildEditorContext context)
public override async Task<IDisplayResult> EditAsync(User user, BuildEditorContext context)
{
return Task.FromResult<IDisplayResult>(Initialize<EditUserViewModel>("UserFields_Edit", async model =>
{
model.EmailConfirmed = user.EmailConfirmed;
model.IsEnabled = user.IsEnabled;
model.IsNewRequest = context.IsNew;
// The current user cannot disable themselves, nor can a user without permission to manage this user disable them.
model.IsEditingDisabled = !await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageUsers, user) ||
String.Equals(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier), user.UserId, StringComparison.OrdinalIgnoreCase);
})
.Location("Content:1.5")
.RenderWhen(() => _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ViewUsers, user)));
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, CommonPermissions.EditUsers, user))
{
return null;
}

return Initialize<EditUserViewModel>("UserFields_Edit", model =>
{
model.EmailConfirmed = user.EmailConfirmed;
model.IsEnabled = user.IsEnabled;
model.IsNewRequest = context.IsNew;
// The current user cannot disable themselves, nor can a user without permission to manage this user disable them.
model.IsEditingDisabled = IsCurrentUser(user);
})
.Location("Content:1.5");
}

public override async Task<IDisplayResult> UpdateAsync(User user, UpdateEditorContext context)
{
// To prevent html injection when updating the user must meet all authorization requirements.
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageUsers, user))
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, CommonPermissions.EditUsers, user))
{
// When the user is only editing their profile never update this part of the user.
return Edit(user);
return await EditAsync(user, context);
}

var model = new EditUserViewModel();
Expand All @@ -90,7 +94,7 @@ public override async Task<IDisplayResult> UpdateAsync(User user, UpdateEditorCo

if (context.IsNew)
{
if (string.IsNullOrWhiteSpace(model.Password))
if (String.IsNullOrWhiteSpace(model.Password))
{
context.Updater.ModelState.AddModelError(Prefix, nameof(model.Password), S["A password is required"]);
}
Expand All @@ -106,15 +110,18 @@ public override async Task<IDisplayResult> UpdateAsync(User user, UpdateEditorCo
return await EditAsync(user, context);
}

var isEditingDisabled = !await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageUsers, user) ||
String.Equals(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier), user.UserId, StringComparison.OrdinalIgnoreCase);
var isEditingDisabled = IsCurrentUser(user);

if (!isEditingDisabled && !model.IsEnabled && user.IsEnabled)
{
var usersOfAdminRole = (await _userManager.GetUsersInRoleAsync(AdministratorRole)).Cast<User>();
if (usersOfAdminRole.Count() == 1 && String.Equals(user.UserId, usersOfAdminRole.First().UserId, StringComparison.OrdinalIgnoreCase))
var enabledUsersOfAdminRole = (await _userManager.GetUsersInRoleAsync(AdministratorRole))
.Cast<User>()
.Where(user => user.IsEnabled)
.ToList();

if (enabledUsersOfAdminRole.Count == 1 && user.UserId == enabledUsersOfAdminRole.First().UserId)
{
await _notifier.WarningAsync(H["Cannot disable the only administrator."]);
await _notifier.WarningAsync(H["Cannot disable the only enabled administrator."]);
}
else
{
Expand Down Expand Up @@ -145,5 +152,10 @@ public override async Task<IDisplayResult> UpdateAsync(User user, UpdateEditorCo

return await EditAsync(user, context);
}

private bool IsCurrentUser(User user)
{
return _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) == user.UserId;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Microsoft.AspNetCore.Http;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Entities;
using OrchardCore.Settings;
using OrchardCore.Users.Models;
using OrchardCore.Users.ViewModels;

Expand All @@ -14,73 +16,103 @@ public class UserInformationDisplayDriver : DisplayDriver<User>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly ISiteService _siteService;

public UserInformationDisplayDriver(
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
ISiteService siteService)
{
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
_siteService = siteService;
}

public override IDisplayResult Edit(User user)
public override async Task<IDisplayResult> EditAsync(User user, BuildEditorContext context)
{
return Initialize<EditUserInformationViewModel>("UserInformationFields_Edit", async model =>
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, CommonPermissions.EditUsers, user))
{
model.UserName = user.UserName;
model.Email = user.Email;
model.IsEditingDisabled = !await AuthorizeUpdateAsync(user);
})
.Location("Content:1")
.RenderWhen(() => AuthorizeEditAsync(user));
return null;
}

var site = await _siteService.GetSiteSettingsAsync();
var settings = site.As<LoginSettings>();

return Combine(
Initialize<EditUserNameViewModel>("UserName_Edit", async model =>
{
model.UserName = user.UserName;

model.AllowEditing = context.IsNew || (settings.AllowChangingUsername && await CanEditUserInfoAsync(user));

}).Location("Content:1"),

Initialize<EditUserEmailViewModel>("UserEmail_Edit", async model =>
{
model.Email = user.Email;

model.AllowEditing = context.IsNew || (settings.AllowChangingEmail && await CanEditUserInfoAsync(user));

}).Location("Content:1.3")
);
}


public override async Task<IDisplayResult> UpdateAsync(User user, UpdateEditorContext context)
{
if (!await AuthorizeUpdateAsync(user))
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, CommonPermissions.EditUsers, user))
{
return Edit(user);
return null;
}

var model = new EditUserInformationViewModel();
var userNameModel = new EditUserNameViewModel();
var emailModel = new EditUserEmailViewModel();

if (await context.Updater.TryUpdateModelAsync(model, Prefix))
{
// Do not use the user manager to set these values, or validate them here, as they will validate at the incorrect time.
// After this driver runs the IUserService.UpdateAsync or IUserService.CreateAsync method will
// validate the user and provide the correct error messages based on the entire user objects values.
// Do not use the user manager to set these values, or validate them here, as they will validate at the incorrect time.
// After this driver runs the IUserService.UpdateAsync or IUserService.CreateAsync method will
// validate the user and provide the correct error messages based on the entire user objects values.

// Custom properties should still be validated in the driver.
// Custom properties should still be validated in the driver.

user.UserName = model.UserName;
user.Email = model.Email;
if (context.IsNew)
{
if (await context.Updater.TryUpdateModelAsync(userNameModel, Prefix))
{
user.UserName = userNameModel.UserName;
}

if (await context.Updater.TryUpdateModelAsync(emailModel, Prefix))
{
user.Email = emailModel.Email;
}
}
else
{
var site = await _siteService.GetSiteSettingsAsync();
var settings = site.As<LoginSettings>();

return Edit(user);
}
if (settings.AllowChangingUsername && await CanEditUserInfoAsync(user) && await context.Updater.TryUpdateModelAsync(userNameModel, Prefix))
{
user.UserName = userNameModel.UserName;
}

private Task<bool> AuthorizeUpdateAsync(User user)
{
// When the current user matches this user we can ask for ManageOwnUserInformation
if (String.Equals(user.UserId, _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier), StringComparison.OrdinalIgnoreCase))
{
return _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageOwnUserInformation);
if (settings.AllowChangingEmail && await CanEditUserInfoAsync(user) && await context.Updater.TryUpdateModelAsync(emailModel, Prefix))
{
user.Email = emailModel.Email;
}
}

// Otherwise we require permission to manage this users information.
return _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageUsers, user);
return await EditAsync(user, context);
}

private Task<bool> AuthorizeEditAsync(User user)
private async Task<bool> CanEditUserInfoAsync(User user)
{
// When the current user matches this user we can ask for ManageOwnUserInformation
if (String.Equals(user.UserId, _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier), StringComparison.OrdinalIgnoreCase))
{
return _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageOwnUserInformation);
}
return !IsCurrentUser(user) || await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ManageOwnUserInformation);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
jtkech marked this conversation as resolved.
Show resolved Hide resolved
}

// Otherwise we require permission to manage this users information.
return _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Permissions.ViewUsers, user);
private bool IsCurrentUser(User user)
{
return String.Equals(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier), user.UserId, StringComparison.OrdinalIgnoreCase);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading