Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7888b9a4ce | ||
|
|
e31582b297 | ||
|
|
d60137e6da | ||
|
|
9f9c88781a |
@@ -48,7 +48,7 @@
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.9" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.10" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
|
||||
<PackageVersion Include="Smidge.InMemory" Version="4.3.0" />
|
||||
<PackageVersion Include="Smidge.Nuglify" Version="4.2.1" />
|
||||
|
||||
@@ -19,6 +19,8 @@ public class
|
||||
{
|
||||
private readonly ContentPermissions _contentPermissions;
|
||||
|
||||
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Document;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringHandler" /> class.
|
||||
/// </summary>
|
||||
@@ -47,7 +49,11 @@ public class
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var argument = routeVal.ToString();
|
||||
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
|
||||
// It's the first one that'll be processed by the protected method so we should verify that.
|
||||
var argument = routeVal.Count == 1
|
||||
? routeVal.ToString()
|
||||
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
|
||||
|
||||
if (!TryParseNodeId(argument, out nodeId))
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
|
||||
{
|
||||
private readonly MediaPermissions _mediaPermissions;
|
||||
|
||||
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Media;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaPermissionsQueryStringHandler" /> class.
|
||||
/// </summary>
|
||||
@@ -44,7 +46,11 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var argument = routeVal.ToString();
|
||||
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
|
||||
// It's the first one that'll be processed by the protected method so we should verify that.
|
||||
var argument = routeVal.Count == 1
|
||||
? routeVal.ToString()
|
||||
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
|
||||
|
||||
if (!TryParseNodeId(argument, out var nodeId))
|
||||
{
|
||||
|
||||
@@ -49,12 +49,18 @@ public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementA
|
||||
/// </summary>
|
||||
protected IEntityService EntityService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to Unknown so all types are allowed, since Keys are unique across all node types this works,
|
||||
/// but it if you are certain you are looking for a specific type this should be overwritten for DB query performance.
|
||||
/// </summary>
|
||||
protected virtual UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse a node ID from a string representation found in a querystring value.
|
||||
/// </summary>
|
||||
/// <param name="argument">Querystring value.</param>
|
||||
/// <param name="nodeId">Output parsed Id.</param>
|
||||
/// <returns>True of node ID could be parased, false it not.</returns>
|
||||
/// <returns>True of node ID could be parsed, false it not.</returns>
|
||||
protected bool TryParseNodeId(string argument, out int nodeId)
|
||||
{
|
||||
// If the argument is an int, it will parse and can be assigned to nodeId.
|
||||
@@ -75,7 +81,7 @@ public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementA
|
||||
|
||||
if (Guid.TryParse(argument, out Guid key))
|
||||
{
|
||||
nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
|
||||
nodeId = EntityService.GetId(key, KeyParsingFilterType).Result;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,7 @@ public class ContentController : ContentControllerBase
|
||||
/// Permission check is done for letter 'R' which is for <see cref="ActionRights" /> which the user must have access to
|
||||
/// update
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IEnumerable<AssignedUserGroupPermissions?>?>> PostSaveUserGroupPermissions(
|
||||
UserGroupPermissionsSave saveModel)
|
||||
{
|
||||
@@ -842,6 +843,7 @@ public class ContentController : ContentControllerBase
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
|
||||
[FileUploadCleanupFilter]
|
||||
[ContentSaveValidation(skipUserAccessValidation:true)] // skip user access validation because we "only" require Settings access to create new blueprints from scratch
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ContentItemDisplay<ContentVariantDisplay>?>?> PostSaveBlueprint(
|
||||
[ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem)
|
||||
{
|
||||
@@ -879,6 +881,7 @@ public class ContentController : ContentControllerBase
|
||||
[FileUploadCleanupFilter]
|
||||
[ContentSaveValidation]
|
||||
[OutgoingEditorModelEvent]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ContentItemDisplay<ContentVariantScheduleDisplay>?>> PostSave(
|
||||
[ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem)
|
||||
{
|
||||
@@ -1960,6 +1963,7 @@ public class ContentController : ContentControllerBase
|
||||
/// does not have Publish access to this node.
|
||||
/// </remarks>
|
||||
[Authorize(Policy = AuthorizationPolicies.ContentPermissionPublishById)]
|
||||
[HttpPost]
|
||||
public IActionResult PostPublishById(int id)
|
||||
{
|
||||
IContent? foundContent = GetObjectFromRequest(() => _contentService.GetById(id));
|
||||
@@ -1991,6 +1995,7 @@ public class ContentController : ContentControllerBase
|
||||
/// does not have Publish access to this node.
|
||||
/// </remarks>
|
||||
[Authorize(Policy = AuthorizationPolicies.ContentPermissionPublishById)]
|
||||
[HttpPost]
|
||||
public IActionResult PostPublishByIdAndCulture(PublishContent model)
|
||||
{
|
||||
var languageCount = _allLangs.Value.Count();
|
||||
@@ -2114,6 +2119,7 @@ public class ContentController : ContentControllerBase
|
||||
/// </summary>
|
||||
/// <param name="sorted"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostSort(ContentSortOrder sorted)
|
||||
{
|
||||
if (sorted == null)
|
||||
@@ -2165,6 +2171,7 @@ public class ContentController : ContentControllerBase
|
||||
/// </summary>
|
||||
/// <param name="move"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult?> PostMove(MoveOrCopy move)
|
||||
{
|
||||
// Authorize...
|
||||
@@ -2199,6 +2206,7 @@ public class ContentController : ContentControllerBase
|
||||
/// </summary>
|
||||
/// <param name="copy"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IContent>?> PostCopy(MoveOrCopy copy)
|
||||
{
|
||||
// Authorize...
|
||||
@@ -2238,6 +2246,7 @@ public class ContentController : ContentControllerBase
|
||||
/// <param name="model">The content and variants to unpublish</param>
|
||||
/// <returns></returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ContentItemDisplayWithSchedule?>> PostUnpublish(UnpublishContent model)
|
||||
{
|
||||
IContent? foundContent = _contentService.GetById(model.Id);
|
||||
@@ -2960,6 +2969,7 @@ public class ContentController : ContentControllerBase
|
||||
return notifications;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult PostNotificationOptions(
|
||||
int contentId,
|
||||
[FromQuery(Name = "notifyOptions[]")] string[] notifyOptions)
|
||||
|
||||
@@ -385,6 +385,7 @@ public class MediaController : ContentControllerBase
|
||||
/// </summary>
|
||||
/// <param name="move"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostMove(MoveOrCopy move)
|
||||
{
|
||||
// Authorize...
|
||||
@@ -432,6 +433,7 @@ public class MediaController : ContentControllerBase
|
||||
[FileUploadCleanupFilter]
|
||||
[MediaItemSaveValidation]
|
||||
[OutgoingEditorModelEvent]
|
||||
[HttpPost]
|
||||
public ActionResult<MediaItemDisplay?>? PostSave(
|
||||
[ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem)
|
||||
{
|
||||
@@ -547,6 +549,7 @@ public class MediaController : ContentControllerBase
|
||||
/// </summary>
|
||||
/// <param name="sorted"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostSort(ContentSortOrder sorted)
|
||||
{
|
||||
if (sorted == null)
|
||||
@@ -592,6 +595,7 @@ public class MediaController : ContentControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<MediaItemDisplay?>> PostAddFolder(PostedFolder folder)
|
||||
{
|
||||
ActionResult<int?>? parentIdResult = await GetParentIdAsIntAsync(folder.ParentId, true);
|
||||
@@ -625,6 +629,7 @@ public class MediaController : ContentControllerBase
|
||||
/// <remarks>
|
||||
/// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data.
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostAddFile([FromForm] string path, [FromForm] string currentFolder,
|
||||
[FromForm] string contentTypeAlias, List<IFormFile> file)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
@@ -130,6 +131,11 @@ public class PreviewController : Controller
|
||||
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
|
||||
public ActionResult Frame(int id, string culture)
|
||||
{
|
||||
if (ValidateProvidedCulture(culture) is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not recognise the provided culture: {culture}");
|
||||
}
|
||||
|
||||
EnterPreview(id);
|
||||
|
||||
// use a numeric URL because content may not be in cache and so .Url would fail
|
||||
@@ -138,6 +144,28 @@ public class PreviewController : Controller
|
||||
return RedirectPermanent($"../../{id}{query}");
|
||||
}
|
||||
|
||||
private static bool ValidateProvidedCulture(string culture)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can be confident the backoffice will have provided a valid culture in linking to the
|
||||
// preview, so we don't need to check that the culture matches an Umbraco language.
|
||||
// We are only concerned here with protecting against XSS attacks from a fiddled preview
|
||||
// URL, so we can just confirm we have a valid culture.
|
||||
try
|
||||
{
|
||||
CultureInfo.GetCultureInfo(culture, true);
|
||||
return true;
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ActionResult? EnterPreview(int id)
|
||||
{
|
||||
IUser? user = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
||||
|
||||
@@ -141,7 +141,10 @@ public abstract class UmbracoViewPage<TModel> : RazorPage<TModel>
|
||||
string.Format(
|
||||
ContentSettings.PreviewBadge,
|
||||
HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoPath),
|
||||
Context.Request.GetEncodedUrl(),
|
||||
System.Web.HttpUtility.HtmlEncode(Context.Request.GetEncodedUrl()), // Belt and braces - via a browser at least it doesn't seem possible to have anything other than
|
||||
// a valid culture code provided in the querystring of this URL.
|
||||
// But just to be sure of prevention of an XSS vulnterablity we'll HTML encode here too.
|
||||
// An expected URL is untouched by this encoding.
|
||||
UmbracoContext.PublishedRequest?.PublishedContent?.Id);
|
||||
}
|
||||
else
|
||||
|
||||
+32
-14
@@ -2,9 +2,7 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
@@ -35,7 +33,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Id_From_Requirement_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext(NodeId);
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue();
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "A" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -47,7 +45,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Id_From_Requirement_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext(NodeId);
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue();
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "B" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -60,7 +58,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Id_Missing_From_Requirement_And_QueryString_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor("xxx");
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue("xxx");
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "A" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -72,7 +70,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Integer_Id_From_QueryString_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: NodeId.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "A" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -85,7 +83,21 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: NodeId.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "B" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
|
||||
Assert.IsFalse(authHandlerContext.HasSucceeded);
|
||||
AssertContentCached(mockHttpContextAccessor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized_Even_When_Additional_Parameter_For_Id_With_Permission_Is_Provided()
|
||||
{
|
||||
// Provides initially failing test and verifies fix for advisory https://github.com/umbraco/Umbraco-CMS/security/advisories/GHSA-wx5h-wqfq-v698
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValues(queryStringValues: new[] { NodeId.ToString(), 1001.ToString() });
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "B" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -98,7 +110,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Udi_Id_From_QueryString_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeUdi.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "A" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -111,7 +123,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Udi_Id_From_QueryString_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeUdi.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "B" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -124,7 +136,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Guid_Id_From_QueryString_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeGuid.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "A" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -137,7 +149,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Guid_Id_From_QueryString_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeGuid.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "B" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -150,7 +162,7 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Invalid_Id_From_QueryString_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid");
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: "invalid");
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new[] { "A" });
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -169,14 +181,20 @@ public class ContentPermissionsQueryStringHandlerTests
|
||||
return new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, user, resource);
|
||||
}
|
||||
|
||||
private static Mock<IHttpContextAccessor> CreateMockHttpContextAccessor(
|
||||
private static Mock<IHttpContextAccessor> CreateMockHttpContextAccessorWithQueryStringValue(
|
||||
string queryStringName = QueryStringName,
|
||||
string queryStringValue = "")
|
||||
=> CreateMockHttpContextAccessorWithQueryStringValues(queryStringName, new[] { queryStringValue });
|
||||
|
||||
private static Mock<IHttpContextAccessor> CreateMockHttpContextAccessorWithQueryStringValues(
|
||||
string queryStringName = QueryStringName,
|
||||
string[]? queryStringValues = null)
|
||||
{
|
||||
queryStringValues ??= Array.Empty<string>();
|
||||
var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
|
||||
var mockHttpContext = new Mock<HttpContext>();
|
||||
var mockHttpRequest = new Mock<HttpRequest>();
|
||||
var queryParams = new Dictionary<string, StringValues> { { queryStringName, queryStringValue } };
|
||||
var queryParams = new Dictionary<string, StringValues> { { queryStringName, new StringValues(queryStringValues) } };
|
||||
mockHttpRequest.SetupGet(x => x.Query).Returns(new QueryCollection(queryParams));
|
||||
mockHttpContext.SetupGet(x => x.Request).Returns(mockHttpRequest.Object);
|
||||
mockHttpContext.SetupGet(x => x.Items).Returns(new Dictionary<object, object>());
|
||||
|
||||
+39
-13
@@ -2,9 +2,7 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
@@ -34,7 +32,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Id_Missing_From_QueryString_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor("xxx");
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue("xxx");
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -46,7 +44,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Integer_Id_From_QueryString_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: NodeId.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -59,7 +57,21 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: NodeId.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, 1001);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
|
||||
Assert.IsFalse(authHandlerContext.HasSucceeded);
|
||||
AssertMediaCached(mockHttpContextAccessor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized_Even_When_Additional_Parameter_For_Id_With_Permission_Is_Provided()
|
||||
{
|
||||
// Provides initially failing test and verifies fix for advisory https://github.com/umbraco/Umbraco-CMS/security/advisories/GHSA-wx5h-wqfq-v698
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValues(queryStringValues: new[] { NodeId.ToString(), 1001.ToString() });
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, 1001);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -72,7 +84,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Udi_Id_From_QueryString_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeUdi.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -85,7 +97,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Udi_Id_From_QueryString_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeUdi.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, 1001);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -98,7 +110,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Guid_Id_From_QueryString_With_Permission_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeGuid.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -111,7 +123,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Guid_Id_From_QueryString_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString());
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: s_nodeGuid.ToString());
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, 1001);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -124,7 +136,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
public async Task Node_Invalid_Id_From_QueryString_Is_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext();
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid");
|
||||
var mockHttpContextAccessor = CreateMockHttpContextAccessorWithQueryStringValue(queryStringValue: "invalid");
|
||||
var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId);
|
||||
|
||||
await sut.HandleAsync(authHandlerContext);
|
||||
@@ -140,14 +152,21 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
return new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, user, resource);
|
||||
}
|
||||
|
||||
private static Mock<IHttpContextAccessor> CreateMockHttpContextAccessor(
|
||||
private static Mock<IHttpContextAccessor> CreateMockHttpContextAccessorWithQueryStringValue(
|
||||
string queryStringName = QueryStringName,
|
||||
string queryStringValue = "")
|
||||
=> CreateMockHttpContextAccessorWithQueryStringValues(queryStringName, new[] { queryStringValue });
|
||||
|
||||
private static Mock<IHttpContextAccessor> CreateMockHttpContextAccessorWithQueryStringValues(
|
||||
string queryStringName = QueryStringName,
|
||||
string[]? queryStringValues = null)
|
||||
{
|
||||
queryStringValues ??= Array.Empty<string>();
|
||||
|
||||
var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
|
||||
var mockHttpContext = new Mock<HttpContext>();
|
||||
var mockHttpRequest = new Mock<HttpRequest>();
|
||||
var queryParams = new Dictionary<string, StringValues> { { queryStringName, queryStringValue } };
|
||||
var queryParams = new Dictionary<string, StringValues> { { queryStringName, new StringValues(queryStringValues) } };
|
||||
mockHttpRequest.SetupGet(x => x.Query).Returns(new QueryCollection(queryParams));
|
||||
mockHttpContext.SetupGet(x => x.Request).Returns(mockHttpRequest.Object);
|
||||
mockHttpContext.SetupGet(x => x.Items).Returns(new Dictionary<object, object>());
|
||||
@@ -155,6 +174,13 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
return mockHttpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <param name="nodeId"></param>
|
||||
/// <param name="startMediaId">the startMediaId of the user being setup</param>
|
||||
/// <returns></returns>
|
||||
private MediaPermissionsQueryStringHandler CreateHandler(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
int nodeId,
|
||||
@@ -179,7 +205,7 @@ public class MediaPermissionsQueryStringHandlerTests
|
||||
mockEntityService
|
||||
.Setup(x => x.GetId(
|
||||
It.Is<Guid>(y => y == s_nodeGuid),
|
||||
It.Is<UmbracoObjectTypes>(y => y == UmbracoObjectTypes.Document)))
|
||||
It.Is<UmbracoObjectTypes>(y => y == UmbracoObjectTypes.Media)))
|
||||
.Returns(Attempt<int>.Succeed(NodeId));
|
||||
return mockEntityService;
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public class MediaPermissionsResourceHandlerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Resource_With_Node_Id_Withou_Permission_Is_Not_Authorized()
|
||||
public async Task Resource_With_Node_Id_Without_Permission_Is_Not_Authorized()
|
||||
{
|
||||
var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, true);
|
||||
var sut = CreateHandler(NodeId, 1001);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
||||
"version": "10.8.7",
|
||||
"version": "10.8.9",
|
||||
"assemblyVersion": {
|
||||
"precision": "build"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user