Files
Umbraco-CMS/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs
Bjarke Berg 23293b77f6 Merge remote-tracking branch 'origin/v8/dev' into v9/feature/merge-v8-05072021
# Conflicts:
#	build/NuSpecs/UmbracoCms.Web.nuspec
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Compose/RelateOnTrashComponent.cs
#	src/Umbraco.Core/Composing/Current.cs
#	src/Umbraco.Core/Constants-AppSettings.cs
#	src/Umbraco.Core/Constants-SqlTemplates.cs
#	src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs
#	src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs
#	src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs
#	src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs
#	src/Umbraco.Core/Models/IReadOnlyContentBase.cs
#	src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs
#	src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
#	src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs
#	src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
#	src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs
#	src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs
#	src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs
#	src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs
#	src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs
#	src/Umbraco.Core/Routing/UrlProviderExtensions.cs
#	src/Umbraco.Core/Runtime/CoreRuntime.cs
#	src/Umbraco.Core/Services/ILocalizedTextService.cs
#	src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs
#	src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs
#	src/Umbraco.Examine/UmbracoContentIndex.cs
#	src/Umbraco.Infrastructure/Examine/GenericIndexDiagnostics.cs
#	src/Umbraco.Infrastructure/IPublishedContentQuery.cs
#	src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
#	src/Umbraco.Infrastructure/Models/MediaWithCrops.cs
#	src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs
#	src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs
#	src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs
#	src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs
#	src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs
#	src/Umbraco.Infrastructure/PublishedContentQuery.cs
#	src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs
#	src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs
#	src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/ContentSourceDto.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs
#	src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs
#	src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs
#	src/Umbraco.Tests/App.config
#	src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs
#	src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
#	src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
#	src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
#	src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
#	src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
#	src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs
#	src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs
#	src/Umbraco.Web.BackOffice/Controllers/MediaController.cs
#	src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs
#	src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs
#	src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs
#	src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs
#	src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs
#	src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs
#	src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs
#	src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs
#	src/Umbraco.Web.Common/Macros/MacroRenderer.cs
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml
#	src/Umbraco.Web.UI/Umbraco/config/lang/it.xml
#	src/Umbraco.Web.UI/web.Template.Debug.config
#	src/Umbraco.Web.UI/web.Template.config
#	src/Umbraco.Web/Compose/NotificationsComponent.cs
#	src/Umbraco.Web/Composing/ModuleInjector.cs
#	src/Umbraco.Web/Editors/AuthenticationController.cs
#	src/Umbraco.Web/Editors/BackOfficeController.cs
#	src/Umbraco.Web/Editors/ContentTypeController.cs
#	src/Umbraco.Web/Editors/CurrentUserController.cs
#	src/Umbraco.Web/Editors/DictionaryController.cs
#	src/Umbraco.Web/Editors/MediaTypeController.cs
#	src/Umbraco.Web/Editors/MemberController.cs
#	src/Umbraco.Web/Editors/MemberGroupController.cs
#	src/Umbraco.Web/Editors/MemberTypeController.cs
#	src/Umbraco.Web/Editors/NuCacheStatusController.cs
#	src/Umbraco.Web/Editors/UserGroupsController.cs
#	src/Umbraco.Web/Editors/UsersController.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs
#	src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs
#	src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
#	src/Umbraco.Web/Models/Trees/MenuItemList.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentCacheDataModel.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/SerializerBase.cs
#	src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs
#	src/Umbraco.Web/Runtime/WebRuntime.cs
#	src/Umbraco.Web/Search/ExamineComponent.cs
#	src/Umbraco.Web/Trees/ApplicationTreeController.cs
#	src/Umbraco.Web/Trees/MemberTreeController.cs
#	src/Umbraco.Web/UrlHelperRenderExtensions.cs
2021-07-05 20:58:04 +02:00

212 lines
9.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)]
[PrefixlessBodyModelValidator]
public class UserGroupsController : BackOfficeNotificationsController
{
private readonly IUserService _userService;
private readonly IContentService _contentService;
private readonly IEntityService _entityService;
private readonly IMediaService _mediaService;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IUmbracoMapper _umbracoMapper;
private readonly ILocalizedTextService _localizedTextService;
private readonly IShortStringHelper _shortStringHelper;
private readonly AppCaches _appCaches;
public UserGroupsController(
IUserService userService,
IContentService contentService,
IEntityService entityService,
IMediaService mediaService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IUmbracoMapper umbracoMapper,
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
AppCaches appCaches)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
_mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService));
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
_localizedTextService =
localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
_appCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
}
[UserGroupValidate]
public ActionResult<UserGroupDisplay> PostSaveUserGroup(UserGroupSave userGroupSave)
{
if (userGroupSave == null) throw new ArgumentNullException(nameof(userGroupSave));
//authorize that the user has access to save this user group
var authHelper = new UserGroupEditorAuthorizationHelper(
_userService, _contentService, _mediaService, _entityService, _appCaches);
var isAuthorized = authHelper.AuthorizeGroupAccess(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, userGroupSave.Alias);
if (isAuthorized == false)
return Unauthorized(isAuthorized.Result);
//if sections were added we need to check that the current user has access to that section
isAuthorized = authHelper.AuthorizeSectionChanges(
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
userGroupSave.PersistedUserGroup.AllowedSections,
userGroupSave.Sections);
if (isAuthorized == false)
return Unauthorized(isAuthorized.Result);
//if start nodes were changed we need to check that the current user has access to them
isAuthorized = authHelper.AuthorizeStartNodeChanges(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
userGroupSave.PersistedUserGroup.StartContentId,
userGroupSave.StartContentId,
userGroupSave.PersistedUserGroup.StartMediaId,
userGroupSave.StartMediaId);
if (isAuthorized == false)
return Unauthorized(isAuthorized.Result);
//need to ensure current user is in a group if not an admin to avoid a 401
EnsureNonAdminUserIsInSavedUserGroup(userGroupSave);
//map the model to the persisted instance
_umbracoMapper.Map(userGroupSave, userGroupSave.PersistedUserGroup);
//save the group
_userService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray());
//deal with permissions
//remove ones that have been removed
var existing = _userService.GetPermissions(userGroupSave.PersistedUserGroup, true)
.ToDictionary(x => x.EntityId, x => x);
var toRemove = existing.Keys.Except(userGroupSave.AssignedPermissions.Select(x => x.Key));
foreach (var contentId in toRemove)
{
_userService.RemoveUserGroupPermissions(userGroupSave.PersistedUserGroup.Id, contentId);
}
//update existing
foreach (var assignedPermission in userGroupSave.AssignedPermissions)
{
_userService.ReplaceUserGroupPermissions(
userGroupSave.PersistedUserGroup.Id,
assignedPermission.Value.Select(x => x[0]),
assignedPermission.Key);
}
var display = _umbracoMapper.Map<UserGroupDisplay>(userGroupSave.PersistedUserGroup);
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","operationSavedHeader"), _localizedTextService.Localize("speechBubbles","editUserGroupSaved"));
return display;
}
private void EnsureNonAdminUserIsInSavedUserGroup(UserGroupSave userGroupSave)
{
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin())
{
return;
}
var userIds = userGroupSave.Users.ToList();
if (userIds.Contains(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id))
{
return;
}
userIds.Add(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
userGroupSave.Users = userIds;
}
/// <summary>
/// Returns the scaffold for creating a new user group
/// </summary>
/// <returns></returns>
public UserGroupDisplay GetEmptyUserGroup()
{
return _umbracoMapper.Map<UserGroupDisplay>(new UserGroup(_shortStringHelper));
}
/// <summary>
/// Returns all user groups
/// </summary>
/// <returns></returns>
public IEnumerable<UserGroupBasic> GetUserGroups(bool onlyCurrentUserGroups = true)
{
var allGroups = _umbracoMapper.MapEnumerable<IUserGroup, UserGroupBasic>(_userService.GetAllUserGroups())
.ToList();
var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin();
if (isAdmin) return allGroups;
if (onlyCurrentUserGroups == false)
{
//this user is not an admin so in that case we need to exclude all admin users
allGroups.RemoveAt(allGroups.IndexOf(allGroups.Find(basic => basic.Alias == Constants.Security.AdminGroupAlias)));
return allGroups;
}
//we cannot return user groups that this user does not have access to
var currentUserGroups = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Groups.Select(x => x.Alias).ToArray();
return allGroups.Where(x => currentUserGroups.Contains(x.Alias)).ToArray();
}
/// <summary>
/// Return a user group
/// </summary>
/// <returns></returns>
[Authorize(Policy = AuthorizationPolicies.UserBelongsToUserGroupInRequest)]
public ActionResult<UserGroupDisplay> GetUserGroup(int id)
{
var found = _userService.GetUserGroupById(id);
if (found == null)
return NotFound();
var display = _umbracoMapper.Map<UserGroupDisplay>(found);
return display;
}
[HttpPost]
[HttpDelete]
[Authorize(Policy = AuthorizationPolicies.UserBelongsToUserGroupInRequest)]
public IActionResult PostDeleteUserGroups([FromQuery] int[] userGroupIds)
{
var userGroups = _userService.GetAllUserGroups(userGroupIds)
//never delete the admin group, sensitive data or translators group
.Where(x => !x.IsSystemUserGroup())
.ToArray();
foreach (var userGroup in userGroups)
{
_userService.DeleteUserGroup(userGroup);
}
if (userGroups.Length > 1)
{
return Ok(_localizedTextService.Localize("speechBubbles","deleteUserGroupsSuccess", new[] {userGroups.Length.ToString()}));
}
return Ok(_localizedTextService.Localize("speechBubbles","deleteUserGroupSuccess", new[] {userGroups[0].Name}));
}
}
}