diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d0778279dc..35f794f895 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,7 +15,7 @@ permissions: contents: read env: - dotnetVersion: 7.x + dotnetVersion: 8.x dotnetIncludePreviewVersions: true solution: umbraco.sln buildConfiguration: SkipTests diff --git a/Directory.Build.props b/Directory.Build.props index 7d9f7ed111..087cea7abc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - net7.0 - latest + net8.0 + 11.0 Umbraco HQ Umbraco Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy')) @@ -30,8 +30,8 @@ true - true - 11.0.0 + false + 12.0.0-rc1 true true diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index b33e4a6cc0..24c62b90aa 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -24,7 +24,7 @@ parameters: variables: nodeVersion: 18.16.0 - dotnetVersion: 7.x + dotnetVersion: 8.x dotnetIncludePreviewVersions: true solution: umbraco.sln buildConfiguration: Release diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj index 946c08556f..cc1571540e 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj index f95f1cd1e1..d3274a5005 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj index f8e3851ccd..8d7a835ab1 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj @@ -6,8 +6,9 @@ - - + + + diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj index 45b6b01052..92d929ce12 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 049d7477e7..f621d27fa6 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -719,6 +719,7 @@ af Fortryd Celle margen + Skift Vælg Ryd Luk diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index c7ad610ef0..38c4916e31 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -759,6 +759,7 @@ by Cancel Cell margin + Change Choose Clear Close diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs index 6418a7bca7..0d359b640f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs @@ -14,6 +14,7 @@ public class ContentVariantDisplay : ITabbedContent, ICo Tabs = new List>(); Notifications = new List(); AllowedActions = Enumerable.Empty(); + AdditionalPreviewUrls = Enumerable.Empty(); } [DataMember(Name = "allowedActions", IsRequired = true)] @@ -72,6 +73,9 @@ public class ContentVariantDisplay : ITabbedContent, ICo /// [DataMember(Name = "tabs")] public IEnumerable> Tabs { get; set; } + + [DataMember(Name = "additionalPreviewUrls")] + public IEnumerable AdditionalPreviewUrls { get; set; } } public class ContentVariantScheduleDisplay : ContentVariantDisplay diff --git a/src/Umbraco.Core/Models/ContentEditing/NamedUrl.cs b/src/Umbraco.Core/Models/ContentEditing/NamedUrl.cs new file mode 100644 index 0000000000..8c71f86cbf --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/NamedUrl.cs @@ -0,0 +1,13 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "namedUrl", Namespace = "")] +public class NamedUrl +{ + [DataMember(Name = "name")] + public required string Name { get; set; } + + [DataMember(Name = "url")] + public required string Url { get; set; } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 30d7b8ce7f..6a385aeba6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -7,18 +7,18 @@ - - - - - - - - - + + + + + + + + + - + diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index a683fdae9c..c527c9efa2 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -13,15 +13,14 @@ - - - - - - + + + + + @@ -40,8 +39,8 @@ - - + + diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index e91384f86a..21961acb45 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -127,6 +127,7 @@ internal class ContentMapDefinition : IMapDefinition target.Tabs = source.Tabs; target.UpdateDate = source.UpdateDate; target.AllowedActions = source.AllowedActions; + target.AdditionalPreviewUrls = source.AdditionalPreviewUrls; } // Umbraco.Code.MapAll @@ -189,6 +190,7 @@ internal class ContentMapDefinition : IMapDefinition target.Tabs = source.Tabs; target.UpdateDate = source.UpdateDate; target.AllowedActions = source.AllowedActions; + target.AdditionalPreviewUrls = source.AdditionalPreviewUrls; // We'll only try and map the ReleaseDate/ExpireDate if the "old" ContentVariantScheduleDisplay is in the context, otherwise we'll just skip it quietly. _ = context.Items.TryGetValue(nameof(ContentItemDisplayWithSchedule.Variants), out var variants); @@ -352,7 +354,7 @@ internal class ContentMapDefinition : IMapDefinition return result; } - // Umbraco.Code.MapAll -Segment -Language -DisplayName + // Umbraco.Code.MapAll -Segment -Language -DisplayName -AdditionalPreviewUrls private void Map(IContent source, ContentVariantDisplay target, MapperContext context) { target.CreateDate = source.CreateDate; diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 00e6c46273..90104b30a4 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -7,10 +7,6 @@ Umbraco.Cms.Web.BackOffice - - - - @@ -33,4 +29,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs b/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs index 95c4ae5cec..2f56cdb51f 100644 --- a/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs +++ b/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs @@ -60,14 +60,14 @@ public class UmbracoMemberAuthorizeFilter : IAsyncAuthorizationFilter { context.HttpContext.SetReasonPhrase( "Resource restricted: the member is not of a permitted type or group."); + context.HttpContext.Response.StatusCode = 403; context.Result = new ForbidResult(); } } else { - context.HttpContext.SetReasonPhrase( - "Resource restricted: the member is not logged in."); - context.Result = new UnauthorizedResult(); + context.HttpContext.Response.StatusCode = 401; + context.Result = new ForbidResult(); } } diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs index 0dac2084bd..b2c02e54b2 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs @@ -193,18 +193,7 @@ internal class CompilationOptionsProvider var parseOptions = new CSharpParseOptions(preprocessorSymbols: (IEnumerable)defines); - if (string.IsNullOrEmpty(dependencyContextOptions.LanguageVersion)) - { - parseOptions = parseOptions.WithLanguageVersion(LanguageVersion.Latest); - } - else if (LanguageVersionFacts.TryParse(dependencyContextOptions.LanguageVersion, out var languageVersion)) - { - parseOptions = parseOptions.WithLanguageVersion(languageVersion); - } - else - { - Debug.Fail($"LanguageVersion {languageVersion} specified in the deps file could not be parsed."); - } + parseOptions = parseOptions.WithLanguageVersion(LanguageVersion.Latest); return parseOptions; } diff --git a/src/Umbraco.Web.Common/Mvc/IpAddressUtilities.cs b/src/Umbraco.Web.Common/Mvc/IpAddressUtilities.cs index 876c1fdd3f..319669571c 100644 --- a/src/Umbraco.Web.Common/Mvc/IpAddressUtilities.cs +++ b/src/Umbraco.Web.Common/Mvc/IpAddressUtilities.cs @@ -7,11 +7,25 @@ public class IpAddressUtilities : IIpAddressUtilities { public bool IsAllowListed(IPAddress clientIpAddress, string allowedIpString) { - if (IPNetwork.TryParse(allowedIpString, out IPNetwork allowedIp) && allowedIp.Contains(clientIpAddress)) + var subnetmaskIndex = allowedIpString.LastIndexOf('/'); + if (subnetmaskIndex >= 0) // It's a network + { + if (IPNetwork.TryParse(allowedIpString, out IPNetwork allowedIp) && allowedIp.Contains(clientIpAddress)) + { + return true; + } + + return false; + } + + // Assume ip address + if (IPAddress.TryParse(allowedIpString, out IPAddress? allowedIpAddress) && allowedIpAddress.Equals(clientIpAddress)) { return true; } return false; + + } } diff --git a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs index b8c2874641..1ba9a52526 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security; @@ -58,7 +60,16 @@ public sealed class ConfigureMemberCookieOptions : IConfigureNamedOptions { - ctx.Response.StatusCode = StatusCodes.Status403Forbidden; + // When the controller is an UmbracoAPIController, we want to return a StatusCode instead of a redirect. + // All other cases should use the default Redirect of the CookieAuthenticationEvent. + var controllerDescriptor = ctx.HttpContext.GetEndpoint()?.Metadata + .OfType() + .FirstOrDefault(); + + if (!controllerDescriptor?.ControllerTypeInfo.IsSubclassOf(typeof(UmbracoApiController)) ?? false) + { + new CookieAuthenticationEvents().OnRedirectToAccessDenied(ctx); + } return Task.CompletedTask; }, diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index faea7d7e48..dc5fdde359 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -6,16 +6,12 @@ Umbraco.Cms.Web.Common - - - - - - + + diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/login.svg b/src/Umbraco.Web.UI.Client/src/assets/img/login.svg index 3bd280b4af..37499a996c 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/img/login.svg +++ b/src/Umbraco.Web.UI.Client/src/assets/img/login.svg @@ -1 +1,996 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index abf173b129..238d9a8ee6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -344,10 +344,14 @@ function clipboardService($window, notificationsService, eventsService, localSto // Clean up each entry var copiedDatas = datas.map(data => prepareEntryForStorage(type, data, firstLevelClearupMethod)); - // remove previous copies of this entry: + // remove previous copies of this entry (Make sure to not remove copies from unsaved content): storage.entries = storage.entries.filter( (entry) => { - return entry.unique !== uniqueKey; + if (entry.unique === 0) { + return displayLabel !== entry.label; + } else { + return entry.unique !== uniqueKey; + } } ); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html index 56c7a9cf48..be6f21ed96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html @@ -5,7 +5,7 @@ ng-repeat="subView in subViews track by subView.alias" ng-class="'sub-view-' + subView.name" val-sub-view="subView" - ng-if="subView.active" + ng-show="subView.active" >
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e0db466443..44a72b7380 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -25,6 +25,7 @@ + all
diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index 0d4a775a95..f9f3779b47 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -7,10 +7,6 @@ Umbraco.Cms.Web.Website - - - - diff --git a/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json index 768a7a4bee..33ec0699b1 100644 --- a/templates/UmbracoPackage/.template.config/template.json +++ b/templates/UmbracoPackage/.template.config/template.json @@ -28,13 +28,13 @@ "datatype": "choice", "choices": [ { - "displayName": ".NET 7.0", - "description": "Target net7.0", - "choice": "net7.0" + "displayName": ".NET 8.0", + "description": "Target net8.0", + "choice": "net8.0" } ], - "defaultValue": "net7.0", - "replaces": "net7.0" + "defaultValue": "net8.0", + "replaces": "net8.0" }, "UmbracoVersion": { "displayName": "Umbraco version", diff --git a/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj index 6da6cf2a79..268611b9ae 100644 --- a/templates/UmbracoPackage/UmbracoPackage.csproj +++ b/templates/UmbracoPackage/UmbracoPackage.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 . UmbracoPackage UmbracoPackage diff --git a/templates/UmbracoPackageRcl/UmbracoPackage.csproj b/templates/UmbracoPackageRcl/UmbracoPackage.csproj index 5c980684ce..1cbdd209e5 100644 --- a/templates/UmbracoPackageRcl/UmbracoPackage.csproj +++ b/templates/UmbracoPackageRcl/UmbracoPackage.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 enable enable true diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index d88b23c07d..b17352476e 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -38,13 +38,13 @@ "datatype": "choice", "choices": [ { - "displayName": ".NET 7.0", - "description": "Target net7.0", - "choice": "net7.0" + "displayName": ".NET 8.0", + "description": "Target net8.0", + "choice": "net8.0" } ], - "defaultValue": "net7.0", - "replaces": "net7.0" + "defaultValue": "net8.0", + "replaces": "net8.0" }, "UmbracoVersion": { "displayName": "Umbraco version", diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index d50f95a907..fcd25050f6 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 enable enable Umbraco.Cms.Web.UI diff --git a/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker b/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker index e19483cfa1..d1ca7fb4a4 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker +++ b/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker @@ -2,7 +2,7 @@ ## Build ############################################ -FROM mcr.microsoft.com/dotnet/nightly/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0.100-preview.6-jammy AS build COPY nuget.config . @@ -22,7 +22,7 @@ RUN dotnet publish --configuration Release --no-build --output /dist ## Run ############################################ -FROM mcr.microsoft.com/dotnet/nightly/aspnet:7.0 AS run +FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0.0-preview.6-jammy AS run WORKDIR /app COPY --from=build dist . diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index f338b443f3..e24c03c2c3 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 5ea936bc58..145300ef37 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs new file mode 100644 index 0000000000..0fc1dfa85d --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs @@ -0,0 +1,126 @@ +using System.Net; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Integration.TestServerTest; +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.Filters; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Cms.Web.Website.Controllers; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Security +{ + public class MemberAuthorizeTests : UmbracoTestServerTestBase + { + private Mock _memberManagerMock = new(); + + protected override void ConfigureTestServices(IServiceCollection services) + { + _memberManagerMock = new Mock(); + services.Remove(new ServiceDescriptor(typeof(IMemberManager), typeof(MemberManager), ServiceLifetime.Scoped)); + services.Remove(new ServiceDescriptor(typeof(MemberManager), ServiceLifetime.Scoped)); + services.AddScoped(_ => _memberManagerMock.Object); + } + + [Test] + public async Task Secure_SurfaceController_Should_Return_Redirect_WhenNotLoggedIn() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(false); + + var url = PrepareSurfaceControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + var cookieAuthenticationOptions = Services.GetService>(); + Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode); + Assert.AreEqual(cookieAuthenticationOptions.Value.AccessDeniedPath.ToString(), response.Headers.Location?.AbsolutePath); + } + + [Test] + public async Task Secure_SurfaceController_Should_Return_Redirect_WhenNotAuthorized() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(true); + _memberManagerMock.Setup(x => x.IsMemberAuthorizedAsync( + It.IsAny>(), + It.IsAny>(), + It.IsAny>())) + .ReturnsAsync(false); + + var url = PrepareSurfaceControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + var cookieAuthenticationOptions = Services.GetService>(); + Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode); + Assert.AreEqual(cookieAuthenticationOptions.Value.AccessDeniedPath.ToString(), response.Headers.Location?.AbsolutePath); + } + + + [Test] + public async Task Secure_ApiController_Should_Return_Unauthorized_WhenNotLoggedIn() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(false); + var url = PrepareApiControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Test] + public async Task Secure_ApiController_Should_Return_Forbidden_WhenNotAuthorized() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(true); + _memberManagerMock.Setup(x => x.IsMemberAuthorizedAsync( + It.IsAny>(), + It.IsAny>(), + It.IsAny>())) + .ReturnsAsync(false); + + var url = PrepareApiControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode); + } + } + + public class TestSurfaceController : SurfaceController + { + public TestSurfaceController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base( + umbracoContextAccessor, + databaseFactory, + services, + appCaches, + profilingLogger, + publishedUrlProvider) + { + } + + [UmbracoMemberAuthorize] + public IActionResult Secure() => NoContent(); + } + + public class TestApiController : UmbracoApiController + { + [UmbracoMemberAuthorize] + public IActionResult Secure() => NoContent(); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index d312428734..ca47bfbd97 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -153,11 +153,20 @@ public class ComponentTests return new ProfilingLogger(Mock.Of>(), Mock.Of()); } + if (type == typeof(ILogger)) + { + return Mock.Of>(); + } + if (type == typeof(ILogger)) { return Mock.Of>(); } + if (type == typeof(IServiceProviderIsService)) + { + return Mock.Of(); + } throw new NotSupportedException(type.FullName); }); }); @@ -315,6 +324,11 @@ public class ComponentTests { return Mock.Of>(); } + + if (type == typeof(IServiceProviderIsService)) + { + return Mock.Of(); + } throw new NotSupportedException(type.FullName); }); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs index eaab03c9b8..c2cbc1e416 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; +using System.Collections; using NUnit.Framework; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Tests.Common.Published; @@ -45,7 +45,7 @@ public class ModelTypeTests // Note the inner assembly qualified name Assert.AreEqual( - "System.Collections.Generic.IEnumerable`1[[System.Int32[], System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + $"System.Collections.Generic.IEnumerable`1[[System.Int32[], System.Private.CoreLib, Version={typeof(IEnumerable).Assembly.GetName().Version}, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", typeof(IEnumerable<>).MakeGenericType(type.MakeArrayType()).FullName); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index 1a8cecee6b..afda7c5cbf 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -64,8 +64,8 @@ public class BackOfficeClaimsPrincipalFactoryTests new BackOfficeClaimsPrincipalFactory(GetMockedUserManager().Object, null)); [Test] - public void Ctor_When_Options_Value_Is_Null_Expect_ArgumentNullException() - => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + public void Ctor_When_Options_Value_Is_Null_Expect_ArgumentException() + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( GetMockedUserManager().Object, new OptionsWrapper(null))); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 5efd49eedf..7d37843ce0 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -7,8 +7,8 @@ + -