Only apply validation on content update to variant cultures where the editor has permission for the culture (#18778)
* Only apply validation on content update to variant cultures where the editor has permission for the culture. * Remove inadvertent comment updates. * Fixed failing integration test.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
@@ -16,15 +19,32 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
|
||||
{
|
||||
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ValidateCreateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IContentEditingService contentEditingService)
|
||||
: this(
|
||||
authorizationService,
|
||||
documentEditingPresentationFactory,
|
||||
contentEditingService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ValidateCreateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IContentEditingService contentEditingService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
: base(authorizationService)
|
||||
{
|
||||
_documentEditingPresentationFactory = documentEditingPresentationFactory;
|
||||
_contentEditingService = contentEditingService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPost("validate")]
|
||||
@@ -36,7 +56,10 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
|
||||
=> await HandleRequest(requestModel, async () =>
|
||||
{
|
||||
ContentCreateModel model = _documentEditingPresentationFactory.MapCreateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateCreateAsync(model);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
|
||||
await _contentEditingService.ValidateCreateAsync(
|
||||
model,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
@@ -17,15 +20,32 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
{
|
||||
private readonly IContentEditingService _contentEditingService;
|
||||
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public ValidateUpdateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory)
|
||||
: this(
|
||||
authorizationService,
|
||||
contentEditingService,
|
||||
documentEditingPresentationFactory,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ValidateUpdateDocumentController(
|
||||
IAuthorizationService authorizationService,
|
||||
IContentEditingService contentEditingService,
|
||||
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
: base(authorizationService)
|
||||
{
|
||||
_contentEditingService = contentEditingService;
|
||||
_documentEditingPresentationFactory = documentEditingPresentationFactory;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/validate")]
|
||||
@@ -62,7 +82,11 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
|
||||
=> await HandleRequest(id, requestModel, async () =>
|
||||
{
|
||||
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
|
||||
await _contentEditingService.ValidateUpdateAsync(
|
||||
id,
|
||||
model,
|
||||
CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -64,7 +64,7 @@ internal sealed class ContentEditingService
|
||||
return await Task.FromResult(content);
|
||||
}
|
||||
|
||||
[Obsolete("Please use the validate update method that is not obsoleted. Will be removed in V16.")]
|
||||
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in V16.")]
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ContentUpdateModel updateModel)
|
||||
{
|
||||
IContent? content = ContentService.GetById(key);
|
||||
@@ -73,16 +73,50 @@ internal sealed class ContentEditingService
|
||||
: Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
|
||||
}
|
||||
|
||||
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in V17.")]
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel)
|
||||
=> await ValidateUpdateAsync(key, updateModel, Guid.Empty);
|
||||
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel, Guid userKey)
|
||||
{
|
||||
IContent? content = ContentService.GetById(key);
|
||||
return content is not null
|
||||
? await ValidateCulturesAndPropertiesAsync(updateModel, content.ContentType.Key, updateModel.Cultures)
|
||||
? await ValidateCulturesAndPropertiesAsync(updateModel, content.ContentType.Key, await GetCulturesToValidate(updateModel.Cultures, userKey))
|
||||
: Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
|
||||
}
|
||||
|
||||
[Obsolete("Please use the validate create method that is not obsoleted. Scheduled for removal in V17.")]
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel)
|
||||
=> await ValidateCulturesAndPropertiesAsync(createModel, createModel.ContentTypeKey, createModel.Variants.Select(variant => variant.Culture));
|
||||
=> await ValidateCreateAsync(createModel, Guid.Empty);
|
||||
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel, Guid userKey)
|
||||
=> await ValidateCulturesAndPropertiesAsync(createModel, createModel.ContentTypeKey, await GetCulturesToValidate(createModel.Variants.Select(variant => variant.Culture), userKey));
|
||||
|
||||
private async Task<IEnumerable<string?>?> GetCulturesToValidate(IEnumerable<string?>? cultures, Guid userKey)
|
||||
{
|
||||
// Cultures to validate can be provided by the calling code, but if the editor is restricted to only have
|
||||
// access to certain languages, we don't want to validate by any they aren't allowed to edit.
|
||||
|
||||
// TODO: Remove this check once the obsolete overloads to ValidateCreateAsync and ValidateUpdateAsync that don't provide a user key are removed.
|
||||
// We only have this to ensure backwards compatibility with the obsolete overloads.
|
||||
if (userKey == Guid.Empty)
|
||||
{
|
||||
return cultures;
|
||||
}
|
||||
|
||||
HashSet<string>? allowedCultures = await GetAllowedCulturesForEditingUser(userKey);
|
||||
|
||||
if (cultures == null)
|
||||
{
|
||||
// If no cultures are provided, we are asking to validate all cultures. But if the user doesn't have access to all, we
|
||||
// should only validate the ones they do.
|
||||
var allCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToList();
|
||||
return allowedCultures.Count == allCultures.Count ? null : allowedCultures;
|
||||
}
|
||||
|
||||
// If explicit cultures are provided, we should only validate the ones the user has access to.
|
||||
return cultures.Where(x => !string.IsNullOrEmpty(x) && allowedCultures.Contains(x)).ToList();
|
||||
}
|
||||
|
||||
public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateAsync(ContentCreateModel createModel, Guid userKey)
|
||||
{
|
||||
@@ -127,16 +161,7 @@ internal sealed class ContentEditingService
|
||||
|
||||
IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key);
|
||||
|
||||
IUser? user = await _userService.GetAsync(userKey);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
}
|
||||
|
||||
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
|
||||
|
||||
var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
|
||||
HashSet<string>? allowedCultures = await GetAllowedCulturesForEditingUser(userKey);
|
||||
|
||||
ILanguage? defaultLanguage = await _languageService.GetDefaultLanguageAsync();
|
||||
|
||||
@@ -211,6 +236,16 @@ internal sealed class ContentEditingService
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
}
|
||||
|
||||
private async Task<HashSet<string>> GetAllowedCulturesForEditingUser(Guid userKey)
|
||||
{
|
||||
IUser? user = await _userService.GetAsync(userKey)
|
||||
?? throw new InvalidOperationException($"Could not find user by key {userKey} when editing or validating content.");
|
||||
|
||||
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
|
||||
|
||||
return (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
|
||||
}
|
||||
|
||||
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey)
|
||||
{
|
||||
IContent? content = ContentService.GetById(key);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
@@ -8,13 +8,25 @@ public interface IContentEditingService
|
||||
{
|
||||
Task<IContent?> GetAsync(Guid key);
|
||||
|
||||
[Obsolete("Please use the validate create method that is not obsoleted. Scheduled for removal in Umbraco 17.")]
|
||||
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel);
|
||||
|
||||
[Obsolete("Please use the validate update method that is not obsoleted. Will be removed in V16.")]
|
||||
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel, Guid userKey)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> ValidateCreateAsync(createModel);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in Umbraco 16.")]
|
||||
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ContentUpdateModel updateModel);
|
||||
|
||||
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in Umbraco 17.")]
|
||||
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel);
|
||||
|
||||
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel, Guid userKey)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> ValidateUpdateAsync(key, updateModel);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateAsync(ContentCreateModel createModel, Guid userKey);
|
||||
|
||||
Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Models.Membership.Permissions;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
|
||||
@@ -27,6 +28,8 @@ public class UserGroupBuilder<TParent>
|
||||
{
|
||||
private string _alias;
|
||||
private IEnumerable<string> _allowedSections = Enumerable.Empty<string>();
|
||||
private IEnumerable<int> _allowedLanguages = Enumerable.Empty<int>();
|
||||
private IEnumerable<IGranularPermission> _granularPermissions = Enumerable.Empty<IGranularPermission>();
|
||||
private string _icon;
|
||||
private int? _id;
|
||||
private Guid? _key;
|
||||
@@ -95,13 +98,24 @@ public class UserGroupBuilder<TParent>
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public UserGroupBuilder<TParent> WithAllowedSections(IList<string> allowedSections)
|
||||
{
|
||||
_allowedSections = allowedSections;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserGroupBuilder<TParent> WithAllowedLanguages(IList<int> allowedLanguages)
|
||||
{
|
||||
_allowedLanguages = allowedLanguages;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserGroupBuilder<TParent> WithGranularPermissions(IList<IGranularPermission> granularPermissions)
|
||||
{
|
||||
_granularPermissions = granularPermissions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserGroupBuilder<TParent> WithStartContentId(int startContentId)
|
||||
{
|
||||
_startContentId = startContentId;
|
||||
@@ -144,17 +158,40 @@ public class UserGroupBuilder<TParent>
|
||||
Id = id,
|
||||
Key = key,
|
||||
StartContentId = startContentId,
|
||||
StartMediaId = startMediaId
|
||||
StartMediaId = startMediaId,
|
||||
Permissions = _permissions
|
||||
};
|
||||
|
||||
userGroup.Permissions = _permissions;
|
||||
BuildAllowedSections(userGroup);
|
||||
BuildAllowedLanguages(userGroup);
|
||||
BuildGranularPermissions(userGroup);
|
||||
|
||||
return userGroup;
|
||||
}
|
||||
|
||||
|
||||
private void BuildAllowedSections(UserGroup userGroup)
|
||||
{
|
||||
foreach (var section in _allowedSections)
|
||||
{
|
||||
userGroup.AddAllowedSection(section);
|
||||
}
|
||||
}
|
||||
|
||||
return userGroup;
|
||||
private void BuildAllowedLanguages(UserGroup userGroup)
|
||||
{
|
||||
foreach (var language in _allowedLanguages)
|
||||
{
|
||||
userGroup.AddAllowedLanguage(language);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildGranularPermissions(UserGroup userGroup)
|
||||
{
|
||||
foreach (var permission in _granularPermissions)
|
||||
{
|
||||
userGroup.GranularPermissions.Add(permission);
|
||||
}
|
||||
}
|
||||
|
||||
public static UserGroup CreateUserGroup(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
@@ -346,6 +346,7 @@ public partial class ContentEditingServiceTests
|
||||
InvariantName = "Updated Name",
|
||||
InvariantProperties = new[]
|
||||
{
|
||||
new PropertyValueModel { Alias = "title", Value = "The initial title" },
|
||||
new PropertyValueModel { Alias = "label", Value = "The updated label value" }
|
||||
}
|
||||
};
|
||||
@@ -390,6 +391,7 @@ public partial class ContentEditingServiceTests
|
||||
Name = "Updated English Name",
|
||||
Properties = new []
|
||||
{
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = "The initial English title" },
|
||||
new PropertyValueModel { Alias = "variantLabel", Value = "The updated English label value" }
|
||||
}
|
||||
},
|
||||
@@ -399,6 +401,7 @@ public partial class ContentEditingServiceTests
|
||||
Name = "Updated Danish Name",
|
||||
Properties = new []
|
||||
{
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = "The initial Danish title" },
|
||||
new PropertyValueModel { Alias = "variantLabel", Value = "The updated Danish label value" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
|
||||
public partial class ContentEditingServiceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Can_Validate_Valid_Invariant_Content()
|
||||
{
|
||||
var content = await CreateInvariantContent();
|
||||
|
||||
var validateContentUpdateModel = new ValidateContentUpdateModel
|
||||
{
|
||||
InvariantName = "Updated Name",
|
||||
InvariantProperties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "title", Value = "The updated title" },
|
||||
new PropertyValueModel { Alias = "text", Value = "The updated text" }
|
||||
]
|
||||
};
|
||||
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Will_Fail_Invalid_Invariant_Content()
|
||||
{
|
||||
var content = await CreateInvariantContent();
|
||||
|
||||
var validateContentUpdateModel = new ValidateContentUpdateModel
|
||||
{
|
||||
InvariantName = "Updated Name",
|
||||
InvariantProperties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "title", Value = null },
|
||||
new PropertyValueModel { Alias = "text", Value = "The updated text" }
|
||||
]
|
||||
};
|
||||
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(ContentEditingOperationStatus.PropertyValidationError, result.Status);
|
||||
Assert.AreEqual(1, result.Result.ValidationErrors.Count());
|
||||
Assert.AreEqual("#validation_invalidNull", result.Result.ValidationErrors.Single(x => x.Alias == "title").ErrorMessages[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Validate_Valid_Variant_Content()
|
||||
{
|
||||
var content = await CreateVariantContent();
|
||||
|
||||
var validateContentUpdateModel = new ValidateContentUpdateModel
|
||||
{
|
||||
InvariantProperties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }
|
||||
],
|
||||
Variants =
|
||||
[
|
||||
new VariantModel
|
||||
{
|
||||
Culture = "en-US",
|
||||
Name = "Updated English Name",
|
||||
Properties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" }
|
||||
]
|
||||
},
|
||||
new VariantModel
|
||||
{
|
||||
Culture = "da-DK",
|
||||
Name = "Updated Danish Name",
|
||||
Properties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = "The updated Danish title" }
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Will_Fail_Invalid_Variant_Content()
|
||||
{
|
||||
var content = await CreateVariantContent();
|
||||
|
||||
var validateContentUpdateModel = new ValidateContentUpdateModel
|
||||
{
|
||||
InvariantProperties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }
|
||||
],
|
||||
Variants =
|
||||
[
|
||||
new VariantModel
|
||||
{
|
||||
Culture = "en-US",
|
||||
Name = "Updated English Name",
|
||||
Properties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" }
|
||||
]
|
||||
},
|
||||
new VariantModel
|
||||
{
|
||||
Culture = "da-DK",
|
||||
Name = "Updated Danish Name",
|
||||
Properties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = null }
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(ContentEditingOperationStatus.PropertyValidationError, result.Status);
|
||||
Assert.AreEqual(1, result.Result.ValidationErrors.Count());
|
||||
Assert.AreEqual("#validation_invalidNull", result.Result.ValidationErrors.Single(x => x.Alias == "variantTitle" && x.Culture == "da-DK").ErrorMessages[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Will_Succeed_For_Invalid_Variant_Content_Without_Access_To_Edited_Culture()
|
||||
{
|
||||
var content = await CreateVariantContent();
|
||||
|
||||
IUser englishEditor = await CreateEnglishLanguageOnlyEditor();
|
||||
|
||||
var validateContentUpdateModel = new ValidateContentUpdateModel
|
||||
{
|
||||
InvariantProperties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }
|
||||
],
|
||||
Variants =
|
||||
[
|
||||
new VariantModel
|
||||
{
|
||||
Culture = "en-US",
|
||||
Name = "Updated English Name",
|
||||
Properties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" }
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, englishEditor.Key);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
|
||||
}
|
||||
|
||||
private async Task<IUser> CreateEnglishLanguageOnlyEditor()
|
||||
{
|
||||
var enUSLanguage = await LanguageService.GetAsync("en-US");
|
||||
var userGroup = new UserGroupBuilder()
|
||||
.WithName("English Editors")
|
||||
.WithAlias("englishEditors")
|
||||
.WithAllowedLanguages([enUSLanguage.Id])
|
||||
.Build();
|
||||
|
||||
var createUserGroupResult = await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(createUserGroupResult.Success);
|
||||
|
||||
var createUserAttempt = await UserService.CreateAsync(Constants.Security.SuperUserKey, new UserCreateModel
|
||||
{
|
||||
Email = "english-editor@test.com",
|
||||
Name = "Test English Editor",
|
||||
UserName = "english-editor@test.com",
|
||||
UserGroupKeys = new[] { userGroup.Key }.ToHashSet(),
|
||||
});
|
||||
Assert.IsTrue(createUserAttempt.Success);
|
||||
|
||||
return await UserService.GetAsync(createUserAttempt.Result.CreatedUser.Key);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,11 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
|
||||
|
||||
protected IContentBlueprintEditingService ContentBlueprintEditingService => GetRequiredService<IContentBlueprintEditingService>();
|
||||
|
||||
private ILanguageService LanguageService => GetRequiredService<ILanguageService>();
|
||||
protected ILanguageService LanguageService => GetRequiredService<ILanguageService>();
|
||||
|
||||
protected IUserService UserService => GetRequiredService<IUserService>();
|
||||
|
||||
protected IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
|
||||
|
||||
protected IContentType CreateInvariantContentType(params ITemplate[] templates)
|
||||
{
|
||||
@@ -30,22 +34,23 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
|
||||
.WithName("Invariant Test")
|
||||
.WithContentVariation(ContentVariation.Nothing)
|
||||
.AddPropertyType()
|
||||
.WithAlias("title")
|
||||
.WithName("Title")
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.WithAlias("title")
|
||||
.WithName("Title")
|
||||
.WithMandatory(true)
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithAlias("text")
|
||||
.WithName("Text")
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.WithAlias("text")
|
||||
.WithName("Text")
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithAlias("label")
|
||||
.WithName("Label")
|
||||
.WithDataTypeId(Constants.DataTypes.LabelString)
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done();
|
||||
.WithAlias("label")
|
||||
.WithName("Label")
|
||||
.WithDataTypeId(Constants.DataTypes.LabelString)
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done();
|
||||
|
||||
foreach (var template in templates)
|
||||
{
|
||||
@@ -81,22 +86,23 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
|
||||
.WithName("Culture Variation Test")
|
||||
.WithContentVariation(ContentVariation.Culture)
|
||||
.AddPropertyType()
|
||||
.WithAlias("variantTitle")
|
||||
.WithName("Variant Title")
|
||||
.WithVariations(ContentVariation.Culture)
|
||||
.Done()
|
||||
.WithAlias("variantTitle")
|
||||
.WithName("Variant Title")
|
||||
.WithMandatory(true)
|
||||
.WithVariations(ContentVariation.Culture)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithAlias("invariantTitle")
|
||||
.WithName("Invariant Title")
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.WithAlias("invariantTitle")
|
||||
.WithName("Invariant Title")
|
||||
.WithVariations(ContentVariation.Nothing)
|
||||
.Done()
|
||||
.AddPropertyType()
|
||||
.WithAlias("variantLabel")
|
||||
.WithName("Variant Label")
|
||||
.WithDataTypeId(Constants.DataTypes.LabelString)
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithVariations(ContentVariation.Culture)
|
||||
.Done()
|
||||
.WithAlias("variantLabel")
|
||||
.WithName("Variant Label")
|
||||
.WithDataTypeId(Constants.DataTypes.LabelString)
|
||||
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
|
||||
.WithVariations(ContentVariation.Culture)
|
||||
.Done()
|
||||
.Build();
|
||||
contentType.AllowedAsRoot = true;
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<PackageId>Umbraco.Cms.Tests.Integration</PackageId>
|
||||
@@ -97,6 +97,9 @@
|
||||
<Compile Update="Umbraco.Infrastructure\Services\ContentEditingServiceTests.Update.cs">
|
||||
<DependentUpon>ContentEditingServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Infrastructure\Services\ContentEditingServiceTests.Validate.cs">
|
||||
<DependentUpon>ContentEditingServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Infrastructure\Services\ContentPublishingServiceTests.Publish.cs">
|
||||
<DependentUpon>ContentPublishingServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
Reference in New Issue
Block a user