Dissallow users to save languages they do not have access to

This commit is contained in:
Nikolaj Geisle
2022-05-10 14:57:58 +02:00
parent 23bb172be0
commit eda9328f99
5 changed files with 73 additions and 4 deletions

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Cms.Core;
public static partial class Constants
{
public class ModelStateErrorKeys
{
public const string PermissionError = "PermissionError";
}
}

View File

@@ -111,8 +111,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
private static IReadOnlyUserGroup ToReadOnlyGroup(UserGroupDto group)
{
return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon,
group.StartContentId, group.StartMediaId, group.Alias,
Enumerable.Empty<int>(), // TODO: Need to find the real languages when the dto model is updated
group.StartContentId, group.StartMediaId, group.Alias, group.UserGroup2LanguageDtos.Select(x => x.LanguageId),
group.UserGroup2AppDtos.Select(x => x.AppAlias).WhereNotNull().ToArray(),
group.DefaultPermissions == null ? Enumerable.Empty<string>() : group.DefaultPermissions.ToCharArray().Select(x => x.ToString()));
}

View File

@@ -393,6 +393,17 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
var startNodes = Database.Fetch<UserStartNodeDto>(sql);
// get groups2languages
sql = SqlContext.Sql()
.Select<UserGroup2LanguageDto>()
.From<UserGroup2LanguageDto>()
.WhereIn<UserGroup2LanguageDto>(x => x.UserGroupId, groupIds);
var groups2languages = Database.Fetch<UserGroup2LanguageDto>(sql)
.GroupBy(x => x.UserGroupId)
.ToDictionary(x => x.Key, x => x);
// map groups
foreach (var user2group in users2groups)
@@ -419,6 +430,16 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
if (groups2apps.TryGetValue(group.Id, out var list))
group.UserGroup2AppDtos = list.ToList(); // groups2apps is distinct
}
// map languages
foreach (var group in groups.Values)
{
if (groups2languages.TryGetValue(group.Id, out var list))
{
group.UserGroup2LanguageDtos = list.ToList(); // groups2apps is distinct
}
}
}
#endregion

View File

@@ -796,6 +796,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// we will continue to save if model state is invalid, however we cannot save if critical data is missing.
if (!ModelState.IsValid)
{
// Don't try and save if we do not have access
if (ModelState.Keys.Contains(Constants.ModelStateErrorKeys.PermissionError))
{
var forDisplay = mapToDisplay(contentItem.PersistedContent);
return ValidationProblem(forDisplay, ModelState);
}
// check for critical data validation issues, we can't continue saving if this data is invalid
if (!passesCriticalValidationRules)
{

View File

@@ -10,6 +10,8 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
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.Web.BackOffice.Authorization;
using Umbraco.Cms.Web.Common.Authorization;
@@ -33,6 +35,8 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
private readonly IContentService _contentService;
private readonly IPropertyValidationService _propertyValidationService;
private readonly IAuthorizationService _authorizationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly ILocalizationService _localizationService;
private readonly ILoggerFactory _loggerFactory;
@@ -40,12 +44,16 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
ILoggerFactory loggerFactory,
IContentService contentService,
IPropertyValidationService propertyValidationService,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
ILocalizationService localizationService)
{
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
_propertyValidationService = propertyValidationService ?? throw new ArgumentNullException(nameof(propertyValidationService));
_authorizationService = authorizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_localizationService = localizationService;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
@@ -76,7 +84,7 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
if (!ValidateAtLeastOneVariantIsBeingSaved(model, context)) return;
if (!contentItemValidator.ValidateExistingContent(model, context)) return;
if (!await ValidateUserAccessAsync(model, context)) return;
if (!await ValidateUserAccessAsync(model, context))
if (model is not null)
{
@@ -129,6 +137,31 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
var permissionToCheck = new List<char>();
IContent? contentToCheck = null;
int contentIdToCheck;
// First check if user has Access to that language
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
bool hasAccess = false;
if (currentUser is null)
{
return false;
}
foreach (IReadOnlyUserGroup group in currentUser.Groups)
{
IEnumerable<ILanguage> languages = _localizationService.GetAllLanguages().Where(x => group.AllowedLanguages.Contains(x.Id));
if (group.AllowedLanguages.Count() is 0 ||
languages.Select(x => x.IsoCode).Intersect(contentItem?.Variants.Where(x => x.Save || x.Publish).Select(x => x.Culture) ?? Enumerable.Empty<string>()).Count() is not 0)
{
hasAccess = true;
}
}
if (!hasAccess && contentItem?.Variants.First().Culture is not null)
{
actionContext.ModelState.AddModelError(Constants.ModelStateErrorKeys.PermissionError, "User does not have access to save language");
return false;
}
switch (contentItem?.Action)
{
case ContentSaveAction.Save: