V14: Remove old backoffice project. (#15752)

* Move magical route to management api

* Move auth around

* Remove "New" cookies, as they are no longer needed

* Move all installer related

* Remove BackOfficeServerVariables.cs and trees

* Move webhooks to management api

* Remove remainting controllers

* Remove last services

* Move preview to management api

* Remove mroe extensions

* Remove tours

* Remove old Auth handlers

* Remove server variables entirely

* Remove old backoffice controller

* Remove controllers namespace entirely

* Move rest of preview

* move last services

* Move language file extension

* Remove old backoffice entirely (Backoffice and Web.UI projects)

* Clean up unused security classes

* Fix up installer route

* Remove obsolete tests

* Fix up DI in integration test

* Add missing property mapping

* Move core mapping into core

* Add composers to integration test

* remove identity

* Fix up DI

* Outcomment failing test :)

* Fix up remaining test

* Update mapper

* Remove the actual project files

* Remove backoffice cs proj

* Remove old backoffice from yml

* Run belissima before login

* Remove caching

* Refactor file paths

* Remove belle from static assets

* Dont refer to old project in templates

* update gitignore

* Add missing files

* Remove install view as its no longer used

* Fix up failing test

* Remove outcommented code

* Update submodule to latest

* fix build

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Nikolaj Geisle
2024-02-27 12:40:30 +01:00
committed by GitHub
parent 593f1eea6c
commit 595ee242aa
2606 changed files with 655 additions and 273115 deletions

View File

@@ -85,23 +85,6 @@ stages:
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: Cache@2
displayName: Cache node_modules
inputs:
key: '"npm_client" | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/package-lock.json'
restoreKeys: |
"npm_client" | "$(Agent.OS)"
"npm_client"
path: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: src/Umbraco.Web.UI.Client
displayName: Run npm ci (Backoffice)
- task: gulp@0
displayName: Run gulp build (Backoffice)
inputs:
gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
targets: coreBuild
workingDirectory: src/Umbraco.Web.UI.Client
- script: npm ci --no-fund --no-audit --prefer-offline
displayName: Run npm ci (Login)
workingDirectory: src/Umbraco.Web.UI.Login
@@ -241,7 +224,7 @@ stages:
- task: Cache@2
displayName: Cache node_modules
inputs:
key: '"npm_client" | "$(Agent.OS)"| $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/package-lock.json | $(Build.SourcesDirectory)/src/Umbraco.Web.UI.New.Client/package-lock.json'
key: '"npm_client" | "$(Agent.OS)"| $(Build.SourcesDirectory)/src/Umbraco.Web.UI.New.Client/package-lock.json'
restoreKeys: |
"npm_client" | "$(Agent.OS)"
"npm_client"

View File

@@ -2,6 +2,7 @@
using Asp.Versioning;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@@ -14,11 +15,11 @@ using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.BackOffice.Security;
using Umbraco.Extensions;
using IdentitySignInResult = Microsoft.AspNetCore.Identity.SignInResult;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.Security;
namespace Umbraco.Cms.Api.Management.Controllers.Security;
@@ -174,7 +175,7 @@ public class BackOfficeController : SecurityControllerBase
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application.
return SignOut(Constants.Security.NewBackOfficeAuthenticationType, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return SignOut(Constants.Security.BackOfficeAuthenticationType, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
@@ -182,7 +183,7 @@ public class BackOfficeController : SecurityControllerBase
/// </summary>
private async Task<string?> GetUserNameFromAuthCookie()
{
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.NewBackOfficeAuthenticationType);
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
return cookieAuthResult.Succeeded
? cookieAuthResult.Principal?.Identity?.Name
: null;
@@ -263,5 +264,5 @@ public class BackOfficeController : SecurityControllerBase
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, backOfficePrincipal);
}
private static IActionResult DefaultChallengeResult() => new ChallengeResult(Constants.Security.NewBackOfficeAuthenticationType);
private static IActionResult DefaultChallengeResult() => new ChallengeResult(Constants.Security.BackOfficeAuthenticationType);
}

View File

@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Security;
public class BackOfficeDefaultController : Controller
{
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Index()
{
// force authentication to occur since this is not an authorized endpoint
AuthenticateResult result = await this.AuthenticateBackOfficeAsync();
// if we are not authenticated then we need to redirect to the login page
if (!result.Succeeded)
{
RedirectToAction("Login", "Backoffice");
}
ViewResult defaultView = DefaultView();
return defaultView;
}
/// <summary>
/// Returns the default view for the BackOffice
/// </summary>
/// <returns>The default view currently /umbraco/UmbracoBackOffice/Default.cshtml</returns>
public ViewResult DefaultView()
{
var viewPath = Path.Combine(Constants.SystemDirectories.Umbraco, Constants.Web.Mvc.BackOfficeArea, nameof(Index) + ".cshtml")
.Replace("\\", "/"); // convert to forward slashes since it's a virtual path
return View(viewPath);
}
}

View File

@@ -19,13 +19,12 @@ public static class BackOfficeAuthBuilderExtensions
builder
.AddAuthentication()
.AddUmbracoOpenIddict()
.AddBackOfficeLogin()
.AddTokenRevocation();
.AddBackOfficeLogin();
return builder;
}
private static IUmbracoBuilder AddTokenRevocation(this IUmbracoBuilder builder)
public static IUmbracoBuilder AddTokenRevocation(this IUmbracoBuilder builder)
{
builder.AddNotificationAsyncHandler<UserSavingNotification, RevokeUserAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<UserSavedNotification, RevokeUserAuthenticationTokensNotificationHandler>();
@@ -52,28 +51,29 @@ public static class BackOfficeAuthBuilderExtensions
{
builder.Services
.AddAuthentication()
.AddCookie(Constants.Security.NewBackOfficeAuthenticationType, options =>
// Add our custom schemes which are cookie handlers
.AddCookie(Constants.Security.BackOfficeAuthenticationType, options =>
{
options.LoginPath = "/umbraco/login";
options.Cookie.Name = Constants.Security.NewBackOfficeAuthenticationType;
options.Cookie.Name = Constants.Security.BackOfficeAuthenticationType;
})
.AddCookie(Constants.Security.NewBackOfficeExternalAuthenticationType, options =>
.AddCookie(Constants.Security.BackOfficeExternalAuthenticationType, o =>
{
options.Cookie.Name = Constants.Security.NewBackOfficeExternalAuthenticationType;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
o.Cookie.Name = Constants.Security.BackOfficeExternalAuthenticationType;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
// Although we don't natively support this, we add it anyways so that if end-users implement the required logic
// they don't have to worry about manually adding this scheme or modifying the sign in manager
.AddCookie(Constants.Security.NewBackOfficeTwoFactorAuthenticationType, options =>
.AddCookie(Constants.Security.BackOfficeTwoFactorAuthenticationType, options =>
{
options.Cookie.Name = Constants.Security.NewBackOfficeTwoFactorAuthenticationType;
options.Cookie.Name = Constants.Security.BackOfficeTwoFactorAuthenticationType;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(Constants.Security.NewBackOfficeTwoFactorRememberMeAuthenticationType, options =>
.AddCookie(Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType, o =>
{
options.Cookie.Name = Constants.Security.NewBackOfficeTwoFactorRememberMeAuthenticationType;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
o.Cookie.Name = Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
return builder;

View File

@@ -1,10 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Install;
using Umbraco.Cms.Api.Management.Mapping.Installer;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Factories;
using Umbraco.Cms.Core.Installer;
using Umbraco.Cms.Core.Installer.Steps;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services.Installer;
using Umbraco.Cms.Infrastructure.Factories.Installer;
using Umbraco.Cms.Infrastructure.Installer.Steps;
@@ -13,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.DependencyInjection;
public static class InstallerBuilderExtensions
{
internal static IUmbracoBuilder AddNewInstaller(this IUmbracoBuilder builder)
internal static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder)
{
IServiceCollection services = builder.Services;
@@ -23,7 +25,7 @@ public static class InstallerBuilderExtensions
builder.AddInstallSteps();
services.AddTransient<IInstallService, InstallService>();
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, CreateUnattendedUserNotificationHandler>();
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<InstallerViewModelsMapDefinition>();
return builder;

View File

@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Preview;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class PreviewBuilderExtensions
{
internal static IUmbracoBuilder AddPreview(this IUmbracoBuilder builder)
{
builder.Services.AddSignalR();
builder.Services.AddSingleton<PreviewRoutes>();
builder.AddNotificationAsyncHandler<ContentCacheRefresherNotification, PreviewHubUpdater>();
return builder;
}
}

View File

@@ -0,0 +1,61 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Infrastructure.DependencyInjection;
using Umbraco.Cms.Infrastructure.Examine.DependencyInjection;
using Umbraco.Cms.Web.Common.Hosting;
namespace Umbraco.Extensions;
/// <summary>
/// Extension methods for <see cref="IUmbracoBuilder"/> for the Umbraco back office
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Adds all required components to run the Umbraco back office
/// </summary>
public static IUmbracoBuilder
AddBackOffice(this IUmbracoBuilder builder, Action<IMvcBuilder>? configureMvc = null) => builder
.AddConfiguration()
.AddUmbracoCore()
.AddWebComponents()
.AddRuntimeMinifier()
.AddBackOfficeCore()
.AddBackOfficeIdentity()
.AddBackOfficeAuthentication()
.AddTokenRevocation()
.AddMembersIdentity()
.AddUmbracoProfiler()
.AddMvcAndRazor(configureMvc)
.AddWebServer()
.AddRecurringBackgroundJobs()
.AddNuCache()
.AddDistributedCache()
.AddCoreNotifications()
.AddExamine()
.AddExamineIndexes();
public static IUmbracoBuilder AddBackOfficeCore(this IUmbracoBuilder builder)
{
builder.Services.AddUnique<IBackOfficePathGenerator, UmbracoBackOfficePathGenerator>();
builder.Services.AddUnique<IPhysicalFileSystem>(factory =>
{
var path = "~/";
IHostingEnvironment hostingEnvironment = factory.GetRequiredService<IHostingEnvironment>();
return new PhysicalFileSystem(
factory.GetRequiredService<IIOHelper>(),
hostingEnvironment,
factory.GetRequiredService<ILogger<PhysicalFileSystem>>(),
hostingEnvironment.MapPathContentRoot(path),
hostingEnvironment.ToAbsolute(path)
);
});
return builder;
}
}

View File

@@ -3,6 +3,8 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Api.Management.Telemetry;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
@@ -15,8 +17,6 @@ using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Security;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
using Umbraco.Cms.Web.BackOffice.Security;
using Umbraco.Cms.Web.BackOffice.Telemetry;
using Umbraco.Cms.Web.Common.AspNetCore;
using Umbraco.Cms.Web.Common.Security;
@@ -63,18 +63,17 @@ public static partial class UmbracoBuilderExtensions
services.AddScoped<ICoreBackOfficeUserManager, BackOfficeUserManager>();
services.AddScoped<IInviteUriProvider, InviteUriProvider>();
services.AddScoped<IForgotPasswordUriProvider, ForgotPasswordUriProvider>();
services.AddScoped<IBackOfficePasswordChanger, BackOfficePasswordChanger>();
services.AddSingleton<IBackOfficeUserPasswordChecker, NoopBackOfficeUserPasswordChecker>(); ;
services.AddSingleton<IBackOfficeUserPasswordChecker, NoopBackOfficeUserPasswordChecker>();
// Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance
services.ConfigureOptions<ConfigureBackOfficeIdentityOptions>();
services.ConfigureOptions<ConfigureBackOfficeSecurityStampValidatorOptions>();
return builder;
}
//TODO change this to private when the legacy backoffice is removed
public static BackOfficeIdentityBuilder BuildUmbracoBackOfficeIdentity(this IUmbracoBuilder builder)
private static BackOfficeIdentityBuilder BuildUmbracoBackOfficeIdentity(this IUmbracoBuilder builder)
{
IServiceCollection services = builder.Services;

View File

@@ -3,25 +3,32 @@ using Umbraco.Cms.Api.Common.Configuration;
using Umbraco.Cms.Api.Common.DependencyInjection;
using Umbraco.Cms.Api.Management.Configuration;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.Middleware;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Serialization;
using Umbraco.Cms.Api.Management.Services;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Services;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions;
public static class UmbracoBuilderExtensions
public static partial class UmbracoBuilderExtensions
{
public static IUmbracoBuilder AddUmbracoManagementApi(this IUmbracoBuilder builder)
{
IServiceCollection services = builder.Services;
builder.Services.AddSingleton<BackOfficeAreaRoutes>();
builder.Services.AddSingleton<BackOfficeExternalLoginProviderErrorMiddleware>();
builder.Services.AddUnique<IConflictingRouteService, ConflictingRouteService>();
if (!services.Any(x => x.ImplementationType == typeof(JsonPatchService)))
{
ModelsBuilderBuilderExtensions.AddModelsBuilder(builder)
.AddJson()
.AddNewInstaller()
.AddInstaller()
.AddUpgrader()
.AddSearchManagement()
.AddTrees()
@@ -55,8 +62,10 @@ public static class UmbracoBuilderExtensions
.AddWebhooks()
.AddServer()
.AddCorsPolicy()
.AddBackOfficeAuthentication()
.AddPasswordConfiguration();
.AddWebhooks()
.AddPreview()
.AddPasswordConfiguration()
.AddSupplemenataryLocalizedTextFileSources();
services
.ConfigureOptions<ConfigureApiBehaviorOptions>()

View File

@@ -1,16 +1,13 @@
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Web.BackOffice.Mapping;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class WebhookBuilderExtensions
internal static class WebhooksBuilderExtensions
{
internal static IUmbracoBuilder AddWebhooks(this IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<WebhookMapDefinition>();
builder.Services.AddUnique<IWebhookPresentationFactory, WebhookPresentationFactory>();
return builder;

View File

@@ -0,0 +1,52 @@
using System.Text;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Web.Common.Hosting;
namespace Umbraco.Cms.Api.Management.Extensions;
public static class HtmlHelperBackOfficeExtensions
{
/// <summary>
/// Outputs a script tag containing the import map for the BackOffice.
/// </summary>
/// <remarks>
/// It will replace the token %CACHE_BUSTER% with the cache buster hash.
/// It will also replace the /umbraco/backoffice path with the correct path for the BackOffice assets.
/// </remarks>
/// <returns>A <see cref="Task"/> containing the html content for the BackOffice import map.</returns>
public static async Task<IHtmlContent> BackOfficeImportMapScriptAsync(
this IHtmlHelper html,
IJsonSerializer jsonSerializer,
IBackOfficePathGenerator backOfficePathGenerator,
IPackageManifestService packageManifestService)
{
try
{
PackageManifestImportmap packageImports = await packageManifestService.GetPackageManifestImportmapAsync();
var sb = new StringBuilder();
sb.AppendLine("""<script type="importmap">""");
sb.AppendLine(jsonSerializer.Serialize(packageImports));
sb.AppendLine("</script>");
// Inject the BackOffice cache buster into the import string to handle BackOffice assets
var importmapScript = sb.ToString()
.Replace(backOfficePathGenerator.BackOfficeVirtualDirectory, backOfficePathGenerator.BackOfficeAssetsPath)
.Replace(Constants.Web.CacheBusterToken, backOfficePathGenerator.BackOfficeCacheBustHash);
return html.Raw(importmapScript);
}
catch (NotSupportedException ex)
{
throw new NotSupportedException("Failed to serialize the BackOffice import map", ex);
}
catch (Exception ex)
{
throw new Exception("Failed to generate the BackOffice import map", ex);
}
}
}

View File

@@ -1,10 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Web.BackOffice.Middleware;
using Umbraco.Cms.Web.BackOffice.Routing;
using Umbraco.Cms.Api.Management.Middleware;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions;
@@ -21,13 +18,6 @@ public static partial class UmbracoApplicationBuilderExtensions
/// <returns></returns>
public static IUmbracoApplicationBuilderContext UseBackOffice(this IUmbracoApplicationBuilderContext builder)
{
KeepAliveSettings keepAliveSettings =
builder.ApplicationServices.GetRequiredService<IOptions<KeepAliveSettings>>().Value;
IHostingEnvironment hostingEnvironment = builder.ApplicationServices.GetRequiredService<IHostingEnvironment>();
builder.AppBuilder.Map(
hostingEnvironment.ToAbsolute(keepAliveSettings.KeepAlivePingUrl),
a => a.UseMiddleware<KeepAliveMiddleware>());
builder.AppBuilder.UseMiddleware<BackOfficeExternalLoginProviderErrorMiddleware>();
return builder;
}

View File

@@ -1,5 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Web.BackOffice.Routing;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions;

View File

@@ -1,5 +1,6 @@
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.Models;
namespace Umbraco.Cms.Api.Management.Factories;

View File

@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
@@ -10,7 +10,7 @@ using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Install;
namespace Umbraco.Cms.Api.Management.Install;
public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler<UnattendedInstallNotification>
{
@@ -18,8 +18,7 @@ public class CreateUnattendedUserNotificationHandler : INotificationAsyncHandler
private readonly IOptions<UnattendedSettings> _unattendedSettings;
private readonly IUserService _userService;
public CreateUnattendedUserNotificationHandler(IOptions<UnattendedSettings> unattendedSettings,
IUserService userService, IServiceScopeFactory serviceScopeFactory)
public CreateUnattendedUserNotificationHandler(IOptions<UnattendedSettings> unattendedSettings, IUserService userService, IServiceScopeFactory serviceScopeFactory)
{
_unattendedSettings = unattendedSettings;
_userService = userService;

View File

@@ -6,6 +6,7 @@ namespace Umbraco.Cms.Api.Management;
public class ManagementApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder) => builder.AddUmbracoManagementApi();
public void Compose(IUmbracoBuilder builder) =>
builder.AddUmbracoManagementApi();
}

View File

@@ -1,84 +0,0 @@
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Webhooks;
using Umbraco.Cms.Web.Common.Models;
namespace Umbraco.Cms.Web.BackOffice.Mapping;
public class WebhookMapDefinition : IMapDefinition
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILocalizedTextService _localizedTextService;
public WebhookMapDefinition(IHostingEnvironment hostingEnvironment, ILocalizedTextService localizedTextService)
{
_hostingEnvironment = hostingEnvironment;
_localizedTextService = localizedTextService;
}
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<IWebhookEvent, WebhookEventResponseModel>((_, _) => new WebhookEventResponseModel(), Map);
mapper.Define<WebhookLog, WebhookLogViewModel>((_, _) => new WebhookLogViewModel(), Map);
mapper.Define<CreateWebhookRequestModel, IWebhook>((_, _) => new Webhook(string.Empty), Map);
mapper.Define<UpdateWebhookRequestModel, IWebhook>((_, _) => new Webhook(string.Empty), Map);
}
// Umbraco.Code.MapAll
private void Map(IWebhookEvent source, WebhookEventResponseModel target, MapperContext context)
{
target.EventName = source.EventName;
target.EventType = source.EventType;
target.Alias = source.Alias;
}
// Umbraco.Code.MapAll -CreateDate -DeleteDate -Id -UpdateDate
private void Map(CreateWebhookRequestModel source, IWebhook target, MapperContext context)
{
target.Url = source.Url;
target.Enabled = source.Enabled;
target.ContentTypeKeys = source.ContentTypeKeys;
target.Events = source.Events;
target.Headers = source.Headers;
target.Key = source.Id ?? Guid.NewGuid();
}
// Umbraco.Code.MapAll -CreateDate -DeleteDate -Id -UpdateDate -Key
private void Map(UpdateWebhookRequestModel source, IWebhook target, MapperContext context)
{
target.Url = source.Url;
target.Enabled = source.Enabled;
target.ContentTypeKeys = source.ContentTypeKeys;
target.Events = source.Events;
target.Headers = source.Headers;
}
// Umbraco.Code.MapAll
private void Map(WebhookLog source, WebhookLogViewModel target, MapperContext context)
{
target.Date = source.Date;
target.EventAlias = source.EventAlias;
target.Key = source.Key;
target.RequestBody = source.RequestBody ?? string.Empty;
target.RetryCount = source.RetryCount;
target.Url = source.Url;
target.RequestHeaders = source.RequestHeaders;
target.WebhookKey = source.WebhookKey;
if (_hostingEnvironment.IsDebugMode)
{
target.ExceptionOccured = source.ExceptionOccured;
target.ResponseBody = source.ResponseBody;
target.ResponseHeaders = source.ResponseHeaders;
target.StatusCode = source.StatusCode;
}
else
{
target.ResponseBody = _localizedTextService.Localize("webhooks", "toggleDebug", Thread.CurrentThread.CurrentUICulture);
target.StatusCode = source.StatusCode is "OK (200)" ? source.StatusCode : _localizedTextService.Localize("webhooks", "statusNotOk", Thread.CurrentThread.CurrentUICulture);
}
}
}

View File

@@ -5,7 +5,7 @@ using Newtonsoft.Json;
using Umbraco.Cms.Core.Security;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Middleware;
namespace Umbraco.Cms.Api.Management.Middleware;
/// <summary>
/// Used to handle errors registered by external login providers

View File

@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Filters;
namespace Umbraco.Cms.Api.Management.Middleware;
/// <summary>
/// Logs any unhandled exception.

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Web.BackOffice.SignalR;
namespace Umbraco.Cms.Api.Management.Preview;
public interface IPreviewHub
{

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.SignalR;
namespace Umbraco.Cms.Web.BackOffice.SignalR;
namespace Umbraco.Cms.Api.Management.Preview;
public class PreviewHub : Hub<IPreviewHub>
{

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Sync;
namespace Umbraco.Cms.Web.BackOffice.SignalR;
namespace Umbraco.Cms.Api.Management.Preview;
public class PreviewHubUpdater : INotificationAsyncHandler<ContentCacheRefresherNotification>
{

View File

@@ -1,19 +1,18 @@
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Controllers.Security;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web.Mvc;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Routing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Routing;
namespace Umbraco.Cms.Api.Management.Routing;
/// <summary>
/// Creates routes for the back office area
@@ -68,27 +67,24 @@ public sealed class BackOfficeAreaRoutes : IAreaRoutes
/// </summary>
private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints)
{
endpoints.MapUmbracoRoute<BackOfficeController>(
endpoints.MapUmbracoRoute<BackOfficeDefaultController>(
_umbracoPathSegment,
Constants.Web.Mvc.BackOfficeArea,
null!,
string.Empty,
"Default",
"Index",
false,
// Limit the action/id to only allow characters - this is so this route doesn't hog all other
// routes like: /umbraco/channels/word.aspx, etc...
// (Not that we have to worry about too many of those these days, there still might be a need for these constraints).
new { action = @"[a-zA-Z]*", id = @"[a-zA-Z]*" });
endpoints.MapUmbracoApiRoute<AuthenticationController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeApiArea, true, string.Empty);
endpoints.MapAreaControllerRoute(
endpoints.MapControllerRoute(
"catch-all-sections-to-client",
Constants.Web.Mvc.BackOfficeArea,
new StringBuilder(_umbracoPathSegment).Append("/section/{**slug}").ToString(),
new
{
Controller = ControllerExtensions.GetControllerName<BackOfficeController>(),
Action = nameof(BackOfficeController.Default)
Controller = ControllerExtensions.GetControllerName<BackOfficeDefaultController>(),
Action = nameof(BackOfficeDefaultController.Index)
});
}

View File

@@ -1,16 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Preview;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Cms.Web.BackOffice.SignalR;
using Umbraco.Cms.Web.Common.Routing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Routing;
namespace Umbraco.Cms.Api.Management.Routing;
/// <summary>
/// Creates routes for the preview hub
@@ -37,8 +36,6 @@ public sealed class PreviewRoutes : IAreaRoutes
case RuntimeLevel.Upgrade:
case RuntimeLevel.Run:
endpoints.MapHub<PreviewHub>(GetPreviewHubRoute());
endpoints.MapUmbracoRoute<PreviewController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea,
null);
break;
case RuntimeLevel.BootFailed:
case RuntimeLevel.Unknown:

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Identity;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Result returned from signing in when auto-linking takes place

View File

@@ -5,7 +5,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Custom <see cref="AuthenticationBuilder" /> used to associate external logins with umbraco external login options

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Authentication;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
public class BackOfficeExternaLoginProviderScheme
{

View File

@@ -1,6 +1,6 @@
using Microsoft.Extensions.Options;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// An external login (OAuth) provider for the back office

View File

@@ -1,6 +1,6 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Options used to configure back office external login providers

View File

@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Authentication;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <inheritdoc />
public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders, ILocalLoginSettingProvider

View File

@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Used to add back office login providers

View File

@@ -2,9 +2,8 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Security;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
public class BackOfficePasswordChanger : IBackOfficePasswordChanger
{

View File

@@ -2,7 +2,7 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Custom secure format that ensures the Identity in the ticket is verified <see cref="ClaimsIdentity" />

View File

@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
@@ -15,7 +14,7 @@ using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// The sign in manager for back office users

View File

@@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Used to configure <see cref="BackOfficeIdentityOptions" /> for the Umbraco Back office
@@ -17,12 +17,6 @@ public sealed class ConfigureBackOfficeIdentityOptions : IConfigureOptions<BackO
private readonly UserPasswordConfigurationSettings _userPasswordConfiguration;
private readonly SecuritySettings _securitySettings;
[Obsolete("Use the constructor that accepts SecuritySettings. Will be removed in V13.")]
public ConfigureBackOfficeIdentityOptions(IOptions<UserPasswordConfigurationSettings> userPasswordConfiguration)
: this(userPasswordConfiguration, StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}
public ConfigureBackOfficeIdentityOptions(
IOptions<UserPasswordConfigurationSettings> userPasswordConfiguration,
IOptions<SecuritySettings> securitySettings)

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <inheritdoc />
public class DefaultBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Identity;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Result returned from signing in when external logins are used.

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Security;
using SecurityConstants = Umbraco.Cms.Core.Constants.Security;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Options used to configure auto-linking external OAuth providers

View File

@@ -7,10 +7,9 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
public class ForgotPasswordUriProvider : IForgotPasswordUriProvider
{
@@ -43,10 +42,11 @@ public class ForgotPasswordUriProvider : IForgotPasswordUriProvider
string forgotPasswordToken = $"{user.Key}{WebUtility.UrlEncode("|")}{tokenAttempt.Result.ToUrlBase64()}";
// FIXME: This will need to change.
string? action = _linkGenerator.GetPathByAction(
nameof(BackOfficeController.ValidatePasswordResetCode),
ControllerExtensions.GetControllerName<BackOfficeController>(),
new { area = Constants.Web.Mvc.BackOfficeArea, invite = forgotPasswordToken });
// string? action = _linkGenerator.GetPathByAction(
// nameof(BackOfficeController.ValidatePasswordResetCode),
// ControllerExtensions.GetControllerName<BackOfficeController>(),
// new { area = Constants.Web.Mvc.BackOfficeArea, invite = forgotPasswordToken });
string action = string.Empty;
Uri applicationUri = _httpContextAccessor
.GetRequiredHttpContext()

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Service to return <see cref="BackOfficeExternalLoginProvider" /> instances

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Options used to control 2FA for the Umbraco back office.

View File

@@ -7,10 +7,9 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
public class InviteUriProvider : IInviteUriProvider
{
@@ -43,10 +42,11 @@ public class InviteUriProvider : IInviteUriProvider
string inviteToken = $"{invitee.Key}{WebUtility.UrlEncode("|")}{tokenAttempt.Result.ToUrlBase64()}";
// FIXME: This will need to change.
string? action = _linkGenerator.GetPathByAction(
nameof(BackOfficeController.VerifyInvite),
ControllerExtensions.GetControllerName<BackOfficeController>(),
new { area = Constants.Web.Mvc.BackOfficeArea, invite = inviteToken });
// string? action = _linkGenerator.GetPathByAction(
// nameof(BackOfficeController.VerifyInvite),
// ControllerExtensions.GetControllerName<BackOfficeController>(),
// new { area = Constants.Web.Mvc.BackOfficeArea, invite = inviteToken });
string action = string.Empty;
Uri applicationUri = _httpContextAccessor
.GetRequiredHttpContext()

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Api.Management.Security;
/// <summary>
/// Options used as named options for 2fa providers

View File

@@ -1,8 +1,8 @@
using System.Reflection;
using Umbraco.Cms.Api.Management.Controllers;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Controllers;
namespace Umbraco.Cms.Web.BackOffice.Services;
@@ -18,7 +18,7 @@ public class ConflictingRouteService : IConflictingRouteService
/// <inheritdoc />
public bool HasConflictingRoutes(out string controllerName)
{
var controllers = _typeLoader.GetTypes<UmbracoApiControllerBase>().ToList();
var controllers = _typeLoader.GetTypes<ManagementApiControllerBase>().ToList();
foreach (Type controller in controllers)
{
Type[] potentialConflicting = controllers.Where(x => x.Name == controller.Name).ToArray();

View File

@@ -1,13 +1,14 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
using Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Web.BackOffice.Telemetry;
namespace Umbraco.Cms.Api.Management.Telemetry;
public class ExternalLoginTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IBackOfficeExternalLoginProviders _externalLoginProviders;
private readonly
IBackOfficeExternalLoginProviders _externalLoginProviders;
public ExternalLoginTelemetryProvider(IBackOfficeExternalLoginProviders externalLoginProviders)
{

View File

@@ -9,23 +9,16 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<ProjectReference Include="..\Umbraco.Cms.Api.Management\Umbraco.Cms.Api.Management.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<PropertyGroup>
<BasePath>$(ProjectDir)wwwroot\umbraco</BasePath>
<BellePath>$(BasePath)\lib</BellePath>
<BellissimaPath>$(BasePath)\backoffice</BellissimaPath>
<LoginPath>$(BasePath)\login</LoginPath>
</PropertyGroup>
<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="Build">
<Message Text="Skip BuildBelle target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
<Message Text="Skip BuildBelle target because '$(BellePath)' already exists" Importance="high" Condition="Exists('$(BellePath)')" />
<Message Text="Call BuildBelle target because UmbracoBuild is empty (this is Visual Studio) and '$(BellePath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BellePath)')" />
<CallTarget Targets="BuildBelle" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BellePath)')" />
<Message Text="Skip BuildBellissima target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
<Message Text="Skip BuildBellissima target because '$(BellissimaPath)' already exists" Importance="high" Condition="Exists('$(BellissimaPath)')" />
<Message Text="Call BuildBellissima target because UmbracoBuild is empty (this is Visual Studio) and '$(BellissimaPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BellissimaPath)')" />
@@ -37,11 +30,6 @@
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and (!Exists('$(LoginPath)') or !Exists('$(BasePath)/auth'))" />
</Target>
<Target Name="BuildBelle">
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm ci --no-fund --no-audit --prefer-offline" />
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm run build:skip-tests" />
</Target>
<Target Name="BuildBellissima">
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.New.Client\" Command="npm ci --no-fund --no-audit --prefer-offline" />
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.New.Client\" Command="npm run build:for:cms" />
@@ -54,10 +42,6 @@
</Target>
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
<Message Text="Skip CleanBelle target because '$(BellePath)' doesn't exist" Importance="high" Condition="!Exists('$(BellePath)')" />
<Message Text="Skip CleanBelle target because preserve.belle marker file exists" Importance="high" Condition="Exists('$(BellePath)') and Exists('$(SolutionDir)preserve.belle')" />
<Message Text="Call CleanBelle target because '$(BellePath)' exists and preserve.belle marker file doesn't exist" Importance="high" Condition="Exists('$(BellePath)') and !Exists('$(SolutionDir)preserve.belle')" />
<CallTarget Targets="CleanBelle" Condition="Exists('$(BellePath)') and !Exists('$(SolutionDir)preserve.belle')" />
<Message Text="Skip CleanBellissima target because '$(BellissimaPath)' doesn't exist" Importance="high" Condition="!Exists('$(BellissimaPath)')" />
<Message Text="Skip CleanBellissima target because preserve.bellissima marker file exists" Importance="high" Condition="Exists('$(BellissimaPath)') and Exists('$(SolutionDir)preserve.bellissima')" />
@@ -70,13 +54,6 @@
<CallTarget Targets="CleanLogin" Condition="(Exists('$(LoginPath)') or Exists('$(BasePath)/auth')) and !Exists('$(SolutionDir)preserve.login')" />
</Target>
<Target Name="CleanBelle">
<ItemGroup>
<BelleDirectories Include="$(BasePath)/js;$(BasePath)/lib;$(BasePath)/assets;$(BasePath)/views;" />
</ItemGroup>
<RemoveDir Directories="@(BelleDirectories)" />
</Target>
<Target Name="CleanBellissima">
<ItemGroup>
<BellissimaDirectories Include="$(BellissimaPath);" />

View File

@@ -1,73 +0,0 @@
@using Microsoft.Extensions.Options;
@using Umbraco.Cms.Core
@using Umbraco.Cms.Core.Configuration
@using Umbraco.Cms.Core.Configuration.Models
@using Umbraco.Cms.Core.Hosting
@using Umbraco.Cms.Core.WebAssets
@using Umbraco.Cms.Infrastructure.WebAssets
@using Umbraco.Cms.Web.BackOffice.Controllers
@using Umbraco.Cms.Web.BackOffice.Security
@using Umbraco.Extensions
@inject BackOfficeServerVariables backOfficeServerVariables
@inject IUmbracoVersion umbracoVersion
@inject IHostingEnvironment hostingEnvironment
@inject IOptions<GlobalSettings> globalSettings
@inject IBackOfficeExternalLoginProviders externalLogins
@inject IRuntimeMinifier runtimeMinifier
@{
var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment);
}
<!DOCTYPE html>
<html lang="en">
<head>
<base href="@backOfficePath.EnsureEndsWith('/')" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Umbraco</title>
@Html.Raw(await runtimeMinifier.RenderCssHereAsync(BackOfficeWebAssets.UmbracoUpgradeCssBundleName))
@*Because we're lazy loading angular js, the embedded cloak style will not be loaded initially, but we need it*@
<style>
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
</style>
</head>
<body id="umbracoMainPageBody" ng-controller="Umbraco.AuthorizeUpgradeController" class="login-only">
<umb-login on-login="loginAndRedirect()"></umb-login>
<umb-notifications></umb-notifications>
@{
var externalLoginUrl = Url.Action("ExternalLogin", "BackOffice", new
{
area = ViewData.GetUmbracoPath(),
//Custom redirect URL since we don't want to just redirect to the back office since this is for authing upgrades
redirectUrl = Url.Action("AuthorizeUpgrade", "BackOffice")
});
}
@await Html.BareMinimumServerVariablesScriptAsync(backOfficeServerVariables)
<script type="text/javascript">
document.angularReady = function (app) {
@await Html.AngularValueExternalLoginInfoScriptAsync(externalLogins, ViewData.GetExternalSignInProviderErrors()!)
@Html.AngularValueResetPasswordCodeInfoScript(ViewData[ViewDataExtensions.TokenPasswordResetCode]!)
}
</script>
@*And finally we can load in our angular app*@
<script type="text/javascript" src="lib/lazyload-js/LazyLoad.min.js"></script>
<script src="@Url.GetUrlWithCacheBust("Application", "BackOffice", null!, hostingEnvironment, umbracoVersion, runtimeMinifier)"></script>
</body>
</html>

View File

@@ -1,35 +0,0 @@
@using System.Globalization
@using Umbraco.Cms.Core.Manifest
@using Umbraco.Cms.Core.Serialization
@using Umbraco.Cms.Web.Common.Hosting
@using Umbraco.Extensions
@inject IBackOfficePathGenerator BackOfficePathGenerator
@inject IPackageManifestService PackageManifestService
@inject IJsonSerializer JsonSerializer
@{
var backOfficePath = BackOfficePathGenerator.BackOfficePath;
var backOfficeAssetsPath = BackOfficePathGenerator.BackOfficeAssetsPath;
}
<!DOCTYPE html>
<html lang="@CultureInfo.CurrentCulture.Name">
<head>
<base href="@backOfficePath.EnsureEndsWith('/')" />
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="@backOfficeAssetsPath/assets/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Umbraco</title>
<link rel="stylesheet" href="@backOfficeAssetsPath/css/umb-css.css" />
<link rel="stylesheet" href="@backOfficeAssetsPath/css/uui-css.css" />
@await Html.BackOfficeImportMapScriptAsync(JsonSerializer, BackOfficePathGenerator, PackageManifestService)
<script type="module" src="@backOfficeAssetsPath/apps/app/app.element.js"></script>
</head>
<body class="uui-font uui-text" style="margin: 0; padding: 0; overflow: hidden">
<umb-app culture="@CultureInfo.CurrentCulture.Name"></umb-app>
</body>
</html>

View File

@@ -1,4 +1,5 @@
@using System.Globalization
@using Umbraco.Cms.Api.Management.Extensions
@using Umbraco.Cms.Core.Manifest
@using Umbraco.Cms.Core.Serialization
@using Umbraco.Cms.Web.Common.Hosting

View File

@@ -1,114 +0,0 @@
@using Microsoft.Extensions.Options;
@using Umbraco.Cms.Core.Configuration
@using Umbraco.Cms.Core.Configuration.Models
@using Umbraco.Cms.Core.Hosting
@using Umbraco.Cms.Core.Logging
@using Umbraco.Cms.Core.Services
@using Umbraco.Cms.Core.WebAssets
@using Umbraco.Cms.Infrastructure.WebAssets
@using Umbraco.Cms.Web.BackOffice.Controllers
@using Umbraco.Cms.Web.BackOffice.Security
@using Umbraco.Extensions
@inject IBackOfficeSignInManager SignInManager
@inject BackOfficeServerVariables BackOfficeServerVariables
@inject IUmbracoVersion UmbracoVersion
@inject IHostingEnvironment HostingEnvironment
@inject IOptions<GlobalSettings> GlobalSettings
@inject IRuntimeMinifier RuntimeMinifier
@inject IProfilerHtml ProfilerHtml
@inject ILocalizedTextService LocalizedTextService
@model Umbraco.Cms.Core.Editors.BackOfficePreviewModel
@{
var disableDevicePreview = Model?.DisableDevicePreview.ToString().ToLowerInvariant();
var EndLabel = LocalizedTextService.Localize("preview", "endLabel");
var EndTitle = LocalizedTextService.Localize("preview", "endTitle");
var OpenWebsiteLabel = LocalizedTextService.Localize("preview", "openWebsiteLabel");
var OpenWebsiteTitle = LocalizedTextService.Localize("preview", "openWebsiteTitle");
var returnToPreviewHeadline = LocalizedTextService.Localize("preview", "returnToPreviewHeadline");
var returnToPreviewDescription = LocalizedTextService.Localize("preview", "returnToPreviewDescription");
var returnToPreviewAcceptButton = LocalizedTextService.Localize("preview", "returnToPreviewAcceptButton");
var returnToPreviewDeclineButton = LocalizedTextService.Localize("preview", "returnToPreviewDeclineButton");
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Umbraco Preview</title>
<meta name="robots" content="noindex, nofollow">
<meta name="pinterest" content="nopin" />
@Html.Raw(await RuntimeMinifier.RenderCssHereAsync(BackOfficeWebAssets.UmbracoPreviewCssBundleName))
<script type="text/javascript">
window.umbLocalizedVars = {
'returnToPreviewHeadline': '@returnToPreviewHeadline',
'returnToPreviewDescription':'@returnToPreviewDescription',
'returnToPreviewAcceptButton':'@returnToPreviewAcceptButton',
'returnToPreviewDeclineButton':'@returnToPreviewDeclineButton'
};
</script>
</head>
<body id="canvasdesignerPanel" ng-mouseover="outlinePositionHide()" ng-controller="previewController" ng-class="{'tabbing-active': tabbingActive === true}" ng-click="windowClickHandler($event)">
<div class="wait" ng-show="!frameLoaded"></div>
@if (!string.IsNullOrWhiteSpace(Model?.PreviewExtendedHeaderView))
{
@await Html.PartialAsync(Model.PreviewExtendedHeaderView)
}
<div id="demo-iframe-wrapper" class="{{previewDevice.css}}">
<preview-i-frame src="pageUrl" on-loaded="onFrameLoaded(iframe)"></preview-i-frame>
</div>
<div class="canvasdesigner" ng-init="showDevicesPreview = true; showDevices = !@(disableDevicePreview);" ng-mouseenter="positionSelectedHide()">
<div class="menu-bar selected">
<div class="menu-bar__title">Preview Mode</div>
<div class="menu-bar__right-part">
<div class="preview-menu-option" ng-class="{'--open': sizeOpen === true}" ng-click="$event.stopPropagation()">
<button class="menu-bar__button umb-outline" ng-click="toggleSizeOpen()"><i class="icon {{previewDevice.icon}}"></i><span>{{previewDevice.title}}</span></button>
<div class="dropdown-menu">
<button ng-repeat="device in devices" class="menu-bar__button umb-outline" ng-class="{ '--active':previewDevice === device }" ng-click="updatePreviewDevice(device)">
<i class="icon {{device.icon}}"></i><span>{{device.title}}</span>
</button>
</div>
</div>
@if (Model?.Languages.Any() == true)
{
<div class="preview-menu-option" ng-class="{'--open': cultureOpen === true}" ng-click="$event.stopPropagation()">
<button class="menu-bar__button umb-outline" ng-click="toggleCultureOpen()"><i class="icon icon-globe-europe---africa"></i><span>{{currentCulture.title}}</span></button>
<div class="dropdown-menu">
@foreach (var language in Model.Languages)
{
<button class="menu-bar__button umb-outline" ng-class="{ '--active': currentCultureIso === '@language.IsoCode' || (@language.IsDefault.ToString().ToLower() && currentCultureIso === null) }" ng-click="changeCulture('@language.IsoCode')" ng-init="registerCulture('@language.IsoCode', '@language.CultureName', @language.IsDefault.ToString().ToLower())">
<i class="icon icon-globe-europe---africa"></i><span>@language.CultureName</span>
</button>
}
</div>
</div>
}
<button ng-click="openInBrowser()" title="@OpenWebsiteTitle" class="menu-bar__button umb-outline">
<i class="icon icon-out"></i><span>@OpenWebsiteLabel</span>
</button>
<button ng-click="exitPreview()" title="@EndTitle" class="menu-bar__button umb-outline">
<i class="icon icon-power"></i><span>@EndLabel</span>
</button>
</div>
</div>
</div>
@await Html.BareMinimumServerVariablesScriptAsync(BackOfficeServerVariables)
<script src="../lib/lazyload-js/LazyLoad.min.js"></script>
<script src="@Url.GetUrlWithCacheBust("Application", "Preview", null!, HostingEnvironment, UmbracoVersion, RuntimeMinifier)"></script>
</body>
</html>

View File

@@ -152,9 +152,9 @@ public class ContentSettings
internal const string StaticMacroErrors = "Inline";
internal const string StaticDisallowedUploadFiles = "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,xamlx";
internal const bool StaticShowDeprecatedPropertyEditors = false;
internal const string StaticLoginBackgroundImage = "assets/img/login.jpg";
internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_blue.svg";
internal const string StaticLoginLogoImageAlternative = "assets/img/application/umbraco_logo_blue.svg";
internal const string StaticLoginBackgroundImage = "login/login.jpg";
internal const string StaticLoginLogoImage = "login/logo_dark.svg";
internal const string StaticLoginLogoImageAlternative = "login/logo_light.svg";
internal const bool StaticHideBackOfficeLogo = false;
internal const bool StaticDisableDeleteWhenReferenced = false;
internal const bool StaticDisableUnpublishWhenReferenced = false;

View File

@@ -77,11 +77,6 @@ public static partial class Constants
public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
public const string BackOfficeTwoFactorRememberMeAuthenticationType = "UmbracoTwoFactorRememberMeCookie";
// FIXME: remove this in favor of BackOfficeAuthenticationType when the old backoffice auth is no longer necessary
public const string NewBackOfficeAuthenticationType = "NewUmbracoBackOffice";
public const string NewBackOfficeExternalAuthenticationType = "NewUmbracoExternalCookie";
public const string NewBackOfficeTwoFactorAuthenticationType = "NewUmbracoTwoFactorCookie";
public const string NewBackOfficeTwoFactorRememberMeAuthenticationType = "NewUmbracoTwoFactorRememberMeCookie";
public const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__";
public const string DefaultMemberTypeAlias = "Member";

View File

@@ -97,7 +97,6 @@ public static partial class UmbracoBuilderExtensions
.Append<Hulu>()
.Append<Giphy>()
.Append<LottieFiles>();
builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes<ISearchableTree>());
builder.BackOfficeAssets();
builder.SelectorHandlers().Add(() => builder.TypeLoader.GetTypes<ISelectorHandler>());
builder.FilterHandlers().Add(() => builder.TypeLoader.GetTypes<IFilterHandler>());
@@ -245,12 +244,6 @@ public static partial class UmbracoBuilderExtensions
public static EmbedProvidersCollectionBuilder EmbedProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<EmbedProvidersCollectionBuilder>();
/// <summary>
/// Gets the back office searchable tree collection builder
/// </summary>
public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<SearchableTreeCollectionBuilder>();
/// <summary>
/// Gets the back office custom assets collection builder
/// </summary>

View File

@@ -217,7 +217,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddUnique<IEventMessagesFactory, DefaultEventMessagesFactory>();
Services.AddUnique<IEventMessagesAccessor, HybridEventMessagesAccessor>();
Services.AddUnique<ITreeService, TreeService>();
Services.AddUnique<ISmsSender, NotImplementedSmsSender>();
Services.AddUnique<IEmailSender, NotImplementedEmailSender>();
@@ -369,7 +369,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddUnique<IWebhookLogService, WebhookLogService>();
Services.AddUnique<IWebhookLogFactory, WebhookLogFactory>();
Services.AddUnique<IWebhookRequestService, WebhookRequestService>();
// Data type configuration cache
Services.AddUnique<IDataTypeConfigurationCache, DataTypeConfigurationCache>();
Services.AddNotificationHandler<DataTypeCacheRefresherNotification, DataTypeConfigurationCacheRefresher>();

View File

@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.Mapping;
@@ -20,6 +21,9 @@ public class ContentPropertyMapDefinition : IMapDefinition
private readonly ContentPropertyBasicMapper<ContentPropertyBasic> _contentPropertyBasicConverter;
private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper;
private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter;
private readonly CommonMapper _commonMapper;
private readonly ContentBasicSavedStateMapper<ContentPropertyBasic> _basicStateMapper;
public ContentPropertyMapDefinition(
ICultureDictionary cultureDictionary,
@@ -28,8 +32,10 @@ public class ContentPropertyMapDefinition : IMapDefinition
ILocalizedTextService textService,
ILoggerFactory loggerFactory,
PropertyEditorCollection propertyEditors,
IDataTypeConfigurationCache dataTypeConfigurationCache)
CommonMapper commonMapper)
{
_commonMapper = commonMapper;
_basicStateMapper = new ContentBasicSavedStateMapper<ContentPropertyBasic>();
_contentPropertyBasicConverter = new ContentPropertyBasicMapper<ContentPropertyBasic>(
dataTypeService,
entityService,
@@ -49,24 +55,6 @@ public class ContentPropertyMapDefinition : IMapDefinition
propertyEditors);
}
[Obsolete("Please use constructor that takes an IDataTypeConfigurationCache. Will be removed in V14.")]
public ContentPropertyMapDefinition(
ICultureDictionary cultureDictionary,
IDataTypeService dataTypeService,
IEntityService entityService,
ILocalizedTextService textService,
ILoggerFactory loggerFactory,
PropertyEditorCollection propertyEditors)
: this(
cultureDictionary,
dataTypeService,
entityService,
textService,
loggerFactory,
propertyEditors,
StaticServiceProvider.Instance.GetRequiredService<IDataTypeConfigurationCache>())
{ }
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<PropertyGroup, Tab<ContentPropertyDisplay>>(
@@ -74,6 +62,8 @@ public class ContentPropertyMapDefinition : IMapDefinition
mapper.Define<IProperty, ContentPropertyBasic>((source, context) => new ContentPropertyBasic(), Map);
mapper.Define<IProperty, ContentPropertyDto>((source, context) => new ContentPropertyDto(), Map);
mapper.Define<IProperty, ContentPropertyDisplay>((source, context) => new ContentPropertyDisplay(), Map);
mapper.Define<IContent, ContentItemBasic<ContentPropertyBasic>>((source, context) => new ContentItemBasic<ContentPropertyBasic>(), Map);
mapper.Define<IContent, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
}
// Umbraco.Code.MapAll -Properties -Alias -Expanded
@@ -101,4 +91,81 @@ public class ContentPropertyMapDefinition : IMapDefinition
// assume this is mapping everything and no MapAll is required
_contentPropertyDisplayMapper.Map(source, target, context);
// Umbraco.Code.MapAll -Alias
private void Map(IContent source, ContentItemBasic<ContentPropertyBasic> target, MapperContext context)
{
target.ContentTypeId = source.ContentType.Id;
target.ContentTypeAlias = source.ContentType.Alias;
target.CreateDate = source.CreateDate;
target.Edited = source.Edited;
target.Icon = source.ContentType.Icon;
target.Id = source.Id;
target.Key = source.Key;
target.Name = GetName(source, context);
target.Owner = _commonMapper.GetOwner(source, context);
target.ParentId = source.ParentId;
target.Path = source.Path;
target.Properties = context.MapEnumerable<IProperty, ContentPropertyBasic>(source.Properties).WhereNotNull();
target.SortOrder = source.SortOrder;
target.State = _basicStateMapper.Map(source, context);
target.Trashed = source.Trashed;
target.Udi =
Udi.Create(source.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, source.Key);
target.UpdateDate = GetUpdateDate(source, context);
target.Updater = _commonMapper.GetCreator(source, context);
target.VariesByCulture = source.ContentType.VariesByCulture();
}
// Umbraco.Code.MapAll
private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context) =>
target.Properties = context.MapEnumerable<IProperty, ContentPropertyDto>(source.Properties).WhereNotNull();
private string? GetName(IContent source, MapperContext context)
{
// invariant = only 1 name
if (!source.ContentType.VariesByCulture())
{
return source.Name;
}
// variant = depends on culture
var culture = context.GetCulture();
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
if (culture == null)
{
throw new InvalidOperationException("Missing culture in mapping options.");
}
// if we don't have a name for a culture, it means the culture is not available, and
// hey we should probably not be mapping it, but it's too late, return a fallback name
return source.CultureInfos is not null &&
source.CultureInfos.TryGetValue(culture, out ContentCultureInfos name) && !name.Name.IsNullOrWhiteSpace()
? name.Name
: $"({source.Name})";
}
private DateTime GetUpdateDate(IContent source, MapperContext context)
{
// invariant = global date
if (!source.ContentType.VariesByCulture())
{
return source.UpdateDate;
}
// variant = depends on culture
var culture = context.GetCulture();
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
if (culture == null)
{
throw new InvalidOperationException("Missing culture in mapping options.");
}
// if we don't have a date for a culture, it means the culture is not available, and
// hey we should probably not be mapping it, but it's too late, return a fallback date
DateTime? date = source.GetUpdateDate(culture);
return date ?? source.UpdateDate;
}
}

View File

@@ -0,0 +1,50 @@
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.Mapping;
/// <summary>
/// Declares model mappings for media.
/// </summary>
public class MediaMapDefinition : IMapDefinition
{
private readonly CommonMapper _commonMapper;
public MediaMapDefinition(CommonMapper commonMapper)
{
_commonMapper = commonMapper;
}
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<IMedia, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
mapper.Define<IMedia, ContentItemBasic<ContentPropertyBasic>>((source, context) => new ContentItemBasic<ContentPropertyBasic>(), Map);
}
// Umbraco.Code.MapAll
private static void Map(IMedia source, ContentPropertyCollectionDto target, MapperContext context) =>
target.Properties = context.MapEnumerable<IProperty, ContentPropertyDto>(source.Properties).WhereNotNull();
// Umbraco.Code.MapAll -Edited -Updater -Alias
private void Map(IMedia source, ContentItemBasic<ContentPropertyBasic> target, MapperContext context)
{
target.ContentTypeId = source.ContentType.Id;
target.ContentTypeAlias = source.ContentType.Alias;
target.CreateDate = source.CreateDate;
target.Icon = source.ContentType.Icon;
target.Id = source.Id;
target.Key = source.Key;
target.Name = source.Name;
target.Owner = _commonMapper.GetOwner(source, context);
target.ParentId = source.ParentId;
target.Path = source.Path;
target.Properties = context.MapEnumerable<IProperty, ContentPropertyBasic>(source.Properties).WhereNotNull();
target.SortOrder = source.SortOrder;
target.State = null;
target.Trashed = source.Trashed;
target.Udi = Udi.Create(Constants.UdiEntityType.Media, source.Key);
target.UpdateDate = source.UpdateDate;
target.VariesByCulture = source.ContentType.VariesByCulture();
}
}

View File

@@ -1,13 +1,23 @@
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.Mapping;
/// <inheritdoc />
public class MemberMapDefinition : IMapDefinition
{
private readonly CommonMapper _commonMapper;
public MemberMapDefinition(CommonMapper commonMapper) => _commonMapper = commonMapper;
/// <inheritdoc />
public void DefineMaps(IUmbracoMapper mapper) => mapper.Define<MemberSave, IMember>(Map);
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<MemberSave, IMember>(Map);
mapper.Define<IMember, MemberBasic>((source, context) => new MemberBasic(), Map);
mapper.Define<IMember, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
}
private static void Map(MemberSave source, IMember target, MapperContext context)
{
@@ -28,4 +38,36 @@ public class MemberMapDefinition : IMapDefinition
// TODO: add groups as required
}
// Umbraco.Code.MapAll -Trashed -Edited -Updater -Alias -VariesByCulture
private void Map(IMember source, MemberBasic target, MapperContext context)
{
target.ContentTypeId = source.ContentType.Id;
target.ContentTypeAlias = source.ContentType.Alias;
target.CreateDate = source.CreateDate;
target.Email = source.Email;
target.Icon = source.ContentType.Icon;
target.Id = int.MaxValue;
target.Key = source.Key;
target.Name = source.Name;
target.Owner = _commonMapper.GetOwner(source, context);
target.ParentId = source.ParentId;
target.Path = source.Path;
target.Properties = context.MapEnumerable<IProperty, ContentPropertyBasic>(source.Properties).WhereNotNull();
target.SortOrder = source.SortOrder;
target.State = null;
target.Udi = Udi.Create(Constants.UdiEntityType.Member, source.Key);
target.UpdateDate = source.UpdateDate;
target.Username = source.Username;
target.FailedPasswordAttempts = source.FailedPasswordAttempts;
target.Approved = source.IsApproved;
target.LockedOut = source.IsLockedOut;
target.LastLockoutDate = source.LastLockoutDate;
target.LastLoginDate = source.LastLoginDate;
target.LastPasswordChangeDate = source.LastPasswordChangeDate;
}
// Umbraco.Code.MapAll
private static void Map(IMember source, ContentPropertyCollectionDto target, MapperContext context) =>
target.Properties = context.MapEnumerable<IProperty, ContentPropertyDto>(source.Properties).WhereNotNull();
}

View File

@@ -4,8 +4,8 @@ namespace Umbraco.Cms.Core.Security;
public class BackOfficeAuthenticationTypeSettings
{
public string AuthenticationType { get; set; } = Constants.Security.NewBackOfficeAuthenticationType;
public string ExternalAuthenticationType { get; set; } = Constants.Security.NewBackOfficeExternalAuthenticationType;
public string TwoFactorAuthenticationType { get; set; } = Constants.Security.NewBackOfficeTwoFactorAuthenticationType;
public string TwoFactorRememberMeAuthenticationType { get; set; } = Constants.Security.NewBackOfficeTwoFactorRememberMeAuthenticationType;
public string AuthenticationType { get; set; } = Constants.Security.BackOfficeAuthenticationType;
public string ExternalAuthenticationType { get; set; } = Constants.Security.BackOfficeExternalAuthenticationType;
public string TwoFactorAuthenticationType { get; set; } = Constants.Security.BackOfficeTwoFactorAuthenticationType;
public string TwoFactorRememberMeAuthenticationType { get; set; } = Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType;
}

View File

@@ -1,19 +0,0 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services;
public interface IIconService
{
/// <summary>
/// Gets the svg string for the icon name found at the global icons path
/// </summary>
/// <param name="iconName"></param>
/// <returns></returns>
IconModel? GetIcon(string iconName);
/// <summary>
/// Gets a list of all svg icons found at at the global icons path.
/// </summary>
/// <returns></returns>
IReadOnlyDictionary<string, string>? GetIcons();
}

View File

@@ -1,30 +0,0 @@
using Umbraco.Cms.Core.Trees;
namespace Umbraco.Cms.Core.Services;
/// <summary>
/// Represents a service which manages section trees.
/// </summary>
public interface ITreeService
{
/// <summary>
/// Gets a tree.
/// </summary>
/// <param name="treeAlias">The tree alias.</param>
Tree? GetByAlias(string treeAlias);
/// <summary>
/// Gets all trees.
/// </summary>
IEnumerable<Tree> GetAll(TreeUse use = TreeUse.Main);
/// <summary>
/// Gets all trees for a section.
/// </summary>
IEnumerable<Tree> GetBySection(string sectionAlias, TreeUse use = TreeUse.Main);
/// <summary>
/// Gets all trees for a section, grouped.
/// </summary>
IDictionary<string, IEnumerable<Tree>> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main);
}

View File

@@ -1,41 +0,0 @@
using Umbraco.Cms.Core.Trees;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services;
/// <summary>
/// Implements <see cref="ITreeService" />.
/// </summary>
public class TreeService : ITreeService
{
private readonly TreeCollection _treeCollection;
/// <summary>
/// Initializes a new instance of the <see cref="TreeService" /> class.
/// </summary>
/// <param name="treeCollection"></param>
public TreeService(TreeCollection treeCollection) => _treeCollection = treeCollection;
/// <inheritdoc />
public Tree? GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(x => x.TreeAlias == treeAlias);
/// <inheritdoc />
public IEnumerable<Tree> GetAll(TreeUse use = TreeUse.Main)
// use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees
=> _treeCollection.Where(x => x.TreeUse.HasFlagAny(use));
/// <inheritdoc />
public IEnumerable<Tree> GetBySection(string sectionAlias, TreeUse use = TreeUse.Main)
// use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees
=> _treeCollection.Where(x => x.SectionAlias.InvariantEquals(sectionAlias) && x.TreeUse.HasFlagAny(use))
.OrderBy(x => x.SortOrder).ToList();
/// <inheritdoc />
public IDictionary<string, IEnumerable<Tree>>
GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main) =>
GetBySection(sectionAlias, use).GroupBy(x => x.TreeGroup).ToDictionary(
x => x.Key ?? string.Empty,
x => (IEnumerable<Tree>)x.ToArray());
}

View File

@@ -1,64 +0,0 @@
namespace Umbraco.Cms.Core.Trees;
[AttributeUsage(AttributeTargets.Class)]
public sealed class SearchableTreeAttribute : Attribute
{
public const int DefaultSortOrder = 1000;
/// <summary>
/// This constructor will assume that the method name equals `format(searchResult, appAlias, treeAlias)`.
/// </summary>
/// <param name="serviceName">Name of the service.</param>
public SearchableTreeAttribute(string serviceName)
: this(serviceName, string.Empty)
{
}
/// <summary>
/// This constructor defines both the Angular service and method name to use.
/// </summary>
/// <param name="serviceName">Name of the service.</param>
/// <param name="methodName">Name of the method.</param>
public SearchableTreeAttribute(string serviceName, string methodName)
: this(serviceName, methodName, DefaultSortOrder)
{
}
/// <summary>
/// This constructor defines both the Angular service and method name to use and explicitly defines a sort order for
/// the results
/// </summary>
/// <param name="serviceName">Name of the service.</param>
/// <param name="methodName">Name of the method.</param>
/// <param name="sortOrder">The sort order.</param>
/// <exception cref="ArgumentNullException">
/// serviceName
/// or
/// methodName
/// </exception>
/// <exception cref="ArgumentException">Value can't be empty or consist only of white-space characters. - serviceName</exception>
public SearchableTreeAttribute(string serviceName, string methodName, int sortOrder)
{
if (serviceName == null)
{
throw new ArgumentNullException(nameof(serviceName));
}
if (string.IsNullOrWhiteSpace(serviceName))
{
throw new ArgumentException(
"Value can't be empty or consist only of white-space characters.",
nameof(serviceName));
}
ServiceName = serviceName;
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
SortOrder = sortOrder;
}
public string ServiceName { get; }
public string MethodName { get; }
public int SortOrder { get; }
}

View File

@@ -1,45 +0,0 @@
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Trees;
public class SearchableTreeCollection : BuilderCollectionBase<ISearchableTree>
{
private readonly Dictionary<string, SearchableApplicationTree> _dictionary;
public SearchableTreeCollection(Func<IEnumerable<ISearchableTree>> items, ITreeService treeService)
: base(items) =>
_dictionary = CreateDictionary(treeService);
public IReadOnlyDictionary<string, SearchableApplicationTree> SearchableApplicationTrees => _dictionary;
public SearchableApplicationTree this[string key] => _dictionary[key];
private Dictionary<string, SearchableApplicationTree> CreateDictionary(ITreeService treeService)
{
Tree[] appTrees = treeService.GetAll()
.OrderBy(x => x.SortOrder)
.ToArray();
var dictionary = new Dictionary<string, SearchableApplicationTree>(StringComparer.OrdinalIgnoreCase);
ISearchableTree[] searchableTrees = this.ToArray();
foreach (Tree appTree in appTrees)
{
ISearchableTree? found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias));
if (found != null)
{
SearchableTreeAttribute? searchableTreeAttribute =
found.GetType().GetCustomAttribute<SearchableTreeAttribute>(false);
dictionary[found.TreeAlias] = new SearchableApplicationTree(
appTree.SectionAlias,
appTree.TreeAlias,
searchableTreeAttribute?.SortOrder ?? SearchableTreeAttribute.DefaultSortOrder,
searchableTreeAttribute?.ServiceName ?? string.Empty,
searchableTreeAttribute?.MethodName ?? string.Empty,
found);
}
}
return dictionary;
}
}

View File

@@ -1,13 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core.Trees;
public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase<SearchableTreeCollectionBuilder,
SearchableTreeCollection, ISearchableTree>
{
protected override SearchableTreeCollectionBuilder This => this;
// per request because generally an instance of ISearchableTree is a controller
protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Scoped;
}

View File

@@ -255,21 +255,6 @@ public static partial class UmbracoBuilderExtensions
return builder;
}
public static IUmbracoBuilder AddLogViewer(this IUmbracoBuilder builder)
{
builder.Services.AddSingleton<ILogViewerConfig, LogViewerConfig>();
builder.Services.AddSingleton<ILogLevelLoader, LogLevelLoader>();
builder.SetLogViewer<SerilogJsonLogViewer>();
builder.Services.AddSingleton<ILogViewer>(factory => new SerilogJsonLogViewer(
factory.GetRequiredService<ILogger<SerilogJsonLogViewer>>(),
factory.GetRequiredService<ILogViewerConfig>(),
factory.GetRequiredService<ILoggingConfiguration>(),
factory.GetRequiredService<ILogLevelLoader>(),
Log.Logger));
return builder;
}
/// <summary>
/// Adds logging requirements for Umbraco
/// </summary>

View File

@@ -31,6 +31,7 @@ public static partial class UmbracoBuilderExtensions
.Add<TagMapDefinition>()
.Add<TemplateMapDefinition>()
.Add<UserMapDefinition>()
.Add<MediaMapDefinition>()
.Add<MemberMapDefinition>()
.Add<LanguageMapDefinition>()
.Add<IdentityMapDefinition>();

View File

@@ -150,7 +150,7 @@ public class RichTextPropertyEditor : DataEditor
.WhereNotNull()
.Select(udi => new UmbracoEntityReference(udi)));
// references from blocks
// references from blocksIg
if (richTextEditorValue.Blocks is not null)
{
BlockEditorData<RichTextBlockValue, RichTextBlockLayoutItem>? blockEditorData = ConvertAndClean(richTextEditorValue.Blocks);

View File

@@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -62,7 +61,7 @@ namespace Umbraco.Cms.Infrastructure.WebAssets {
/// <summary>
/// Looks up a localized string similar to [
///
///
/// &apos;lib/jquery/jquery.min.js&apos;,
/// &apos;lib/jquery-ui/jquery-ui.min.js&apos;,
/// &apos;lib/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js&apos;,
@@ -78,7 +77,7 @@ namespace Umbraco.Cms.Infrastructure.WebAssets {
/// &apos;lib/angular-route/angular-route.min.js&apos;,
/// &apos;lib/angular-cookies/angular-cookies.min.js&apos;,
/// &apos;lib/angular-aria/angular-aria.min.js&apos;,
/// &apos;lib/angular-touch/angular-touch [rest of string was truncated]&quot;;.
/// &apos;lib/angular-touch/angular-touch.min [rest of string was truncated]&quot;;.
/// </summary>
internal static string JsInitialize {
get {
@@ -130,35 +129,20 @@ namespace Umbraco.Cms.Infrastructure.WebAssets {
}
}
/// <summary>
/// Looks up a localized string similar to // TODO: This would be nicer as an angular module so it can be injected into stuff... that&apos;d be heaps nicer, but
///// how to do that when this is not a regular JS file, it is a server side JS file and RequireJS seems to only want
///// to force load JS files ?
///
/////create the namespace (NOTE: This loads before any dependencies so we don&apos;t have a namespace mgr so we just create it manually)
///var Umbraco = {};
///Umbraco.Sys = {};
/////define a global static object
///Umbraco.Sys.ServerVariables = ##Variables## ;.
/// </summary>
internal static string ServerVariables {
get {
return ResourceManager.GetString("ServerVariables", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [
/// &apos;lib/tinymce/tinymce.min.js&apos;,
/// &apos;lib/tinymce/tinymce.min.js&apos;,
///
/// &apos;lib/tinymce/plugins/anchor/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/charmap/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/table/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/lists/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/advlist/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/autolink/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/directionality/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/t [rest of string was truncated]&quot;;.
/// &apos;lib/tinymce/plugins/anchor/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/charmap/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/table/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/lists/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/advlist/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/autolink/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/directionality/plugin.min.js&apos;,
/// &apos;lib/tinymce/plugins/searchreplace/plugin.min.js&apos;
///]
///.
/// </summary>
internal static string TinyMceInitialize {
get {

View File

@@ -28,9 +28,6 @@
<data name="PreviewInitialize" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>PreviewInitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="ServerVariables" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>ServerVariables.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="TinyMceInitialize" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>TinyMceInitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>

View File

@@ -1,9 +0,0 @@
// TODO: This would be nicer as an angular module so it can be injected into stuff... that'd be heaps nicer, but
// how to do that when this is not a regular JS file, it is a server side JS file and RequireJS seems to only want
// to force load JS files ?
//create the namespace (NOTE: This loads before any dependencies so we don't have a namespace mgr so we just create it manually)
var Umbraco = {};
Umbraco.Sys = {};
//define a global static object
Umbraco.Sys.ServerVariables = ##Variables## ;

View File

@@ -1,33 +0,0 @@
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
namespace Umbraco.Cms.Infrastructure.WebAssets;
/// <summary>
/// Ensures the server variables are included in the outgoing JS script
/// </summary>
public class ServerVariablesParser
{
private const string Token = "##Variables##";
private readonly IEventAggregator _eventAggregator;
/// <summary>
/// Initializes a new instance of the <see cref="ServerVariablesParser" /> class.
/// </summary>
public ServerVariablesParser(IEventAggregator eventAggregator) => _eventAggregator = eventAggregator;
/// <summary>
/// Ensures the server variables in the dictionary are included in the outgoing JS script
/// </summary>
public async Task<string> ParseAsync(Dictionary<string, object> items)
{
var vars = Resources.ServerVariables;
// Raise event for developers to add custom variables
await _eventAggregator.PublishAsync(new ServerVariablesParsingNotification(items));
var json = JObject.FromObject(items);
return vars.Replace(Token, json.ToString());
}
}

View File

@@ -1,12 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Cms.Web.BackOffice.ActionResults;
public class JavaScriptResult : ContentResult
{
public JavaScriptResult(string? script)
{
Content = script;
ContentType = "application/javascript";
}
}

View File

@@ -1,20 +0,0 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Cms.Web.BackOffice.ActionResults;
public class UmbracoErrorResult : ObjectResult
{
public UmbracoErrorResult(HttpStatusCode statusCode, string message) : this(statusCode, new MessageWrapper(message))
{
}
public UmbracoErrorResult(HttpStatusCode statusCode, object value) : base(value) => StatusCode = (int)statusCode;
private class MessageWrapper
{
public MessageWrapper(string message) => Message = message;
public string Message { get; }
}
}

View File

@@ -1,93 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Editors;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// If the users being edited is an admin then we must ensure that the current user is also an admin.
/// </summary>
public class AdminUsersHandler : MustSatisfyRequirementAuthorizationHandler<AdminUsersRequirement>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper;
private readonly IUserService _userService;
/// <summary>
/// Initializes a new instance of the <see cref="AdminUsersHandler" /> class.
/// </summary>
/// <param name="httpContextAccessor">Accessor for the HTTP context of the current request.</param>
/// <param name="userService">Service for user related operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
/// <param name="userEditorAuthorizationHelper">Helper for user authorization checks.</param>
public AdminUsersHandler(
IHttpContextAccessor httpContextAccessor,
IUserService userService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
UserEditorAuthorizationHelper userEditorAuthorizationHelper)
{
_httpContextAccessor = httpContextAccessor;
_userService = userService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_userEditorAuthorizationHelper = userEditorAuthorizationHelper;
}
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, AdminUsersRequirement requirement)
{
StringValues? queryString = _httpContextAccessor.HttpContext?.Request.Query[requirement.QueryStringName];
if (!queryString.HasValue || !queryString.Value.Any())
{
// Must succeed this requirement since we cannot process it.
return Task.FromResult(true);
}
int[]? userIds;
if (int.TryParse(queryString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var userId))
{
userIds = new[] { userId };
}
else
{
var ids = queryString.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.ToList();
if (ids?.Count == 0)
{
// Must succeed this requirement since we cannot process it.
return Task.FromResult(true);
}
userIds = ids?
.Select(x =>
int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var output)
? Attempt<int>.Succeed(output)
: Attempt<int>.Fail())
.Where(x => x.Success)
.Select(x => x.Result)
.ToArray();
}
if (userIds?.Length == 0)
{
// Must succeed this requirement since we cannot process it.
return Task.FromResult(true);
}
IEnumerable<IUser> users = _userService.GetUsersById(userIds);
var isAuth = users.All(user =>
_userEditorAuthorizationHelper.IsAuthorized(_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser,
user, null, null, null) != false);
return Task.FromResult(isAuth);
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Authorization requirement for the <see cref="AdminUsersHandler" />
/// </summary>
public class AdminUsersRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="AdminUsersRequirement" /> class.
/// </summary>
/// <param name="queryStringName">Query string name from which to authorize values.</param>
public AdminUsersRequirement(string queryStringName = "id") => QueryStringName = queryStringName;
/// <summary>
/// Gets the query string name from which to authorize values.
/// </summary>
public string QueryStringName { get; }
}

View File

@@ -1,45 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Ensures authorization is successful for a back office user.
/// </summary>
public class BackOfficeHandler : MustSatisfyRequirementAuthorizationHandler<BackOfficeRequirement>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurity;
private readonly IRuntimeState _runtimeState;
public BackOfficeHandler(IBackOfficeSecurityAccessor backOfficeSecurity, IRuntimeState runtimeState)
{
_backOfficeSecurity = backOfficeSecurity;
_runtimeState = runtimeState;
}
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, BackOfficeRequirement requirement)
{
// if not configured (install or upgrade) then we can continue
// otherwise we need to ensure that a user is logged in
switch (_runtimeState.Level)
{
case var _ when _runtimeState.EnableInstaller():
return Task.FromResult(true);
default:
if (!_backOfficeSecurity.BackOfficeSecurity?.IsAuthenticated() ?? false)
{
return Task.FromResult(false);
}
var userApprovalSucceeded = !requirement.RequireApproval ||
(_backOfficeSecurity.BackOfficeSecurity?.CurrentUser?.IsApproved ?? false);
return Task.FromResult(userApprovalSucceeded);
}
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Authorization requirement for the <see cref="BackOfficeHandler" />.
/// </summary>
public class BackOfficeRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeRequirement" /> class.
/// </summary>
/// <param name="requireApproval">Flag for whether back-office user approval is required.</param>
public BackOfficeRequirement(bool requireApproval = true) => RequireApproval = requireApproval;
/// <summary>
/// Gets a value indicating whether back-office user approval is required.
/// </summary>
public bool RequireApproval { get; }
}

View File

@@ -1,78 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// The user must have access to all descendant nodes of the content item in order to continue.
/// </summary>
public class ContentPermissionsPublishBranchHandler : MustSatisfyRequirementAuthorizationHandler<
ContentPermissionsPublishBranchRequirement, IContent>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly ContentPermissions _contentPermissions;
private readonly IEntityService _entityService;
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsPublishBranchHandler" /> class.
/// </summary>
/// <param name="entityService">Service for entity operations.</param>
/// <param name="contentPermissions">per for user content authorization checks.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
public ContentPermissionsPublishBranchHandler(
IEntityService entityService,
ContentPermissions contentPermissions,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_entityService = entityService;
_contentPermissions = contentPermissions;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
ContentPermissionsPublishBranchRequirement requirement, IContent resource)
{
IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
var denied = new List<IUmbracoEntity>();
var page = 0;
const int pageSize = 500;
var total = long.MaxValue;
while (page * pageSize < total)
{
// Order descendents by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit
// early if a permission higher up fails.
IEnumerable<IEntitySlim> descendants = _entityService.GetPagedDescendants(
resource.Id,
UmbracoObjectTypes.Document,
page++,
pageSize,
out total,
ordering: Ordering.By("path"));
foreach (IEntitySlim c in descendants)
{
// If this item's path has already been denied or if the user doesn't have access to it, add to the deny list.
if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) ||
_contentPermissions.CheckPermissions(
c,
currentUser,
requirement.Permission) == ContentPermissions.ContentAccess.Denied)
{
denied.Add(c);
}
}
}
return Task.FromResult(denied.Count == 0);
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Authorization requirement for <see cref="ContentPermissionsPublishBranchHandler" />
/// </summary>
public class ContentPermissionsPublishBranchRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsPublishBranchRequirement" /> class.
/// </summary>
/// <param name="permission">Permission to check.</param>
public ContentPermissionsPublishBranchRequirement(char permission) => Permission = permission;
/// <summary>
/// Gets a value for the permission to check.
/// </summary>
public char Permission { get; }
}

View File

@@ -1,81 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the content id specified in a
/// query string.
/// </summary>
public class
ContentPermissionsQueryStringHandler : PermissionsQueryStringHandler<ContentPermissionsQueryStringRequirement>
{
private readonly ContentPermissions _contentPermissions;
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringHandler" /> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
/// <param name="httpContextAccessor">Accessor for the HTTP context of the current request.</param>
/// <param name="entityService">Service for entity operations.</param>
/// <param name="contentPermissions">Helper for content authorization checks.</param>
public ContentPermissionsQueryStringHandler(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
IEntityService entityService,
ContentPermissions contentPermissions)
: base(backOfficeSecurityAccessor, httpContextAccessor, entityService) =>
_contentPermissions = contentPermissions;
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsQueryStringRequirement requirement)
{
int nodeId;
if (requirement.NodeId.HasValue == false)
{
if (HttpContextAccessor.HttpContext is null || requirement.QueryStringName is null ||
!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out StringValues routeVal))
{
// Must succeed this requirement since we cannot process it
return Task.FromResult(true);
}
var argument = routeVal.ToString();
if (!TryParseNodeId(argument, out nodeId))
{
// Must succeed this requirement since we cannot process it.
return Task.FromResult(true);
}
}
else
{
nodeId = requirement.NodeId.Value;
}
ContentPermissions.ContentAccess permissionResult = _contentPermissions.CheckPermissions(
nodeId,
BackOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser,
out IContent? contentItem,
new[] { requirement.PermissionToCheck });
if (HttpContextAccessor.HttpContext is not null && contentItem is not null)
{
// Store the content item in request cache so it can be resolved in the controller without re-looking it up.
HttpContextAccessor.HttpContext.Items[typeof(IContent).ToString()] = contentItem;
}
return permissionResult switch
{
ContentPermissions.ContentAccess.Denied => Task.FromResult(false),
_ => Task.FromResult(true)
};
}
}

View File

@@ -1,51 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// An authorization requirement for <see cref="ContentPermissionsQueryStringHandler" />
/// </summary>
public class ContentPermissionsQueryStringRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringRequirement" /> class for a specific node
/// id.
/// </summary>
/// <param name="nodeId">The node Id.</param>
/// <param name="permissionToCheck">The permission to authorize the current user against.</param>
public ContentPermissionsQueryStringRequirement(int nodeId, char permissionToCheck)
{
NodeId = nodeId;
PermissionToCheck = permissionToCheck;
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringRequirement" /> class for a
/// node id based on a query string parameter.
/// </summary>
/// <param name="paramName">The querystring parameter name.</param>
/// <param name="permissionToCheck">The permission to authorize the current user against.</param>
public ContentPermissionsQueryStringRequirement(char permissionToCheck, string paramName = "id")
{
QueryStringName = paramName;
PermissionToCheck = permissionToCheck;
}
/// <summary>
/// Gets the specific node Id.
/// </summary>
public int? NodeId { get; }
/// <summary>
/// Gets the querystring parameter name.
/// </summary>
public string? QueryStringName { get; }
/// <summary>
/// Gets the permission to authorize the current user against.
/// </summary>
public char PermissionToCheck { get; }
}

View File

@@ -1,62 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// The resource used for the <see cref="ContentPermissionsResourceRequirement" />
/// </summary>
public class ContentPermissionsResource
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsResource" /> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="permissionToCheck">The permission to authorize.</param>
public ContentPermissionsResource(IContent? content, char permissionToCheck)
{
PermissionsToCheck = new List<char> { permissionToCheck };
Content = content;
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsResource" /> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="permissionsToCheck">The collection of permissions to authorize.</param>
public ContentPermissionsResource(IContent content, IReadOnlyList<char> permissionsToCheck)
{
Content = content;
PermissionsToCheck = permissionsToCheck;
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsResource" /> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="nodeId">The node Id.</param>
/// <param name="permissionsToCheck">The collection of permissions to authorize.</param>
public ContentPermissionsResource(IContent? content, int nodeId, IReadOnlyList<char> permissionsToCheck)
{
Content = content;
NodeId = nodeId;
PermissionsToCheck = permissionsToCheck;
}
/// <summary>
/// Gets the node Id.
/// </summary>
public int? NodeId { get; }
/// <summary>
/// Gets the collection of permissions to authorize.
/// </summary>
public IReadOnlyList<char> PermissionsToCheck { get; }
/// <summary>
/// Gets the content.
/// </summary>
public IContent? Content { get; }
}

View File

@@ -1,50 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the <see cref="IContent" />
/// specified.
/// </summary>
public class ContentPermissionsResourceHandler : MustSatisfyRequirementAuthorizationHandler<
ContentPermissionsResourceRequirement, ContentPermissionsResource>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly ContentPermissions _contentPermissions;
/// <summary>
/// Initializes a new instance of the <see cref="ContentPermissionsResourceHandler" /> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
/// <param name="contentPermissions">Helper for content authorization checks.</param>
public ContentPermissionsResourceHandler(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
ContentPermissions contentPermissions)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_contentPermissions = contentPermissions;
}
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
ContentPermissionsResourceRequirement requirement, ContentPermissionsResource resource)
{
ContentPermissions.ContentAccess permissionResult = resource.NodeId.HasValue
? _contentPermissions.CheckPermissions(
resource.NodeId.Value,
_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser,
out IContent? _,
resource.PermissionsToCheck)
: _contentPermissions.CheckPermissions(
resource.Content,
_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser,
resource.PermissionsToCheck);
return Task.FromResult(permissionResult != ContentPermissions.ContentAccess.Denied);
}
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// An authorization requirement for <see cref="ContentPermissionsResourceHandler" />
/// </summary>
public class ContentPermissionsResourceRequirement : IAuthorizationRequirement
{
}

View File

@@ -1,27 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Web.BackOffice.Security;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Ensures the resource cannot be accessed if <see cref="IBackOfficeExternalLoginProviders.HasDenyLocalLogin" />
/// returns true.
/// </summary>
public class DenyLocalLoginHandler : MustSatisfyRequirementAuthorizationHandler<DenyLocalLoginRequirement>
{
private readonly IBackOfficeExternalLoginProviders _externalLogins;
/// <summary>
/// Initializes a new instance of the <see cref="DenyLocalLoginHandler" /> class.
/// </summary>
/// <param name="externalLogins">Provides access to <see cref="BackOfficeExternalLoginProvider" /> instances.</param>
public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins) => _externalLogins = externalLogins;
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
DenyLocalLoginRequirement requirement) =>
Task.FromResult(!_externalLogins.HasDenyLocalLogin());
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Marker requirement for the <see cref="DenyLocalLoginHandler" />.
/// </summary>
public class DenyLocalLoginRequirement : IAuthorizationRequirement
{
}

View File

@@ -1,72 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Used to authorize if the user has the correct permission access to the media for the media id specified in a query
/// string.
/// </summary>
public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<MediaPermissionsQueryStringRequirement>
{
private readonly MediaPermissions _mediaPermissions;
/// <summary>
/// Initializes a new instance of the <see cref="MediaPermissionsQueryStringHandler" /> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
/// <param name="httpContextAccessor">Accessor for the HTTP context of the current request.</param>
/// <param name="entityService">Service for entity operations.</param>
/// <param name="mediaPermissions">Helper for media authorization checks.</param>
public MediaPermissionsQueryStringHandler(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
IEntityService entityService,
MediaPermissions mediaPermissions)
: base(backOfficeSecurityAccessor, httpContextAccessor, entityService) => _mediaPermissions = mediaPermissions;
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
MediaPermissionsQueryStringRequirement requirement)
{
if (HttpContextAccessor.HttpContext is null ||
!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName,
out StringValues routeVal))
{
// Must succeed this requirement since we cannot process it.
return Task.FromResult(true);
}
var argument = routeVal.ToString();
if (!TryParseNodeId(argument, out var nodeId))
{
// Must succeed this requirement since we cannot process it.
return Task.FromResult(true);
}
MediaPermissions.MediaAccess permissionResult = _mediaPermissions.CheckPermissions(
BackOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser,
nodeId,
out IMedia? mediaItem);
if (mediaItem != null)
{
// Store the media item in request cache so it can be resolved in the controller without re-looking it up.
HttpContextAccessor.HttpContext.Items[typeof(IMedia).ToString()] = mediaItem;
}
return permissionResult switch
{
MediaPermissions.MediaAccess.Denied => Task.FromResult(false),
_ => Task.FromResult(true)
};
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// An authorization requirement for <see cref="MediaPermissionsQueryStringHandler" />
/// </summary>
public class MediaPermissionsQueryStringRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="MediaPermissionsQueryStringRequirement" /> class.
/// </summary>
/// <param name="paramName">Querystring paramter name.</param>
public MediaPermissionsQueryStringRequirement(string paramName) => QueryStringName = paramName;
/// <summary>
/// Gets the querystring paramter name.
/// </summary>
public string QueryStringName { get; }
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
public class MediaPermissionsResource
{
public MediaPermissionsResource(IMedia? media) => Media = media;
public MediaPermissionsResource(int nodeId) => NodeId = nodeId;
public int? NodeId { get; }
public IMedia? Media { get; }
}

View File

@@ -1,48 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the <see cref="IContent" />
/// specified.
/// </summary>
public class MediaPermissionsResourceHandler : MustSatisfyRequirementAuthorizationHandler<
MediaPermissionsResourceRequirement, MediaPermissionsResource>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly MediaPermissions _mediaPermissions;
/// <summary>
/// Initializes a new instance of the <see cref="MediaPermissionsResourceHandler" /> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
/// <param name="mediaPermissions">Helper for media authorization checks.</param>
public MediaPermissionsResourceHandler(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
MediaPermissions mediaPermissions)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_mediaPermissions = mediaPermissions;
}
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context,
MediaPermissionsResourceRequirement requirement, MediaPermissionsResource resource)
{
MediaPermissions.MediaAccess permissionResult = resource.NodeId.HasValue
? _mediaPermissions.CheckPermissions(
_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser,
resource.NodeId.Value,
out _)
: _mediaPermissions.CheckPermissions(
resource.Media,
_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser);
return Task.FromResult(permissionResult != MediaPermissions.MediaAccess.Denied);
}
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// An authorization requirement for <see cref="MediaPermissionsResourceHandler" />
/// </summary>
public class MediaPermissionsResourceRequirement : IAuthorizationRequirement
{
}

View File

@@ -1,81 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what.
/// </summary>
/// <typeparam name="T">Authorization requirement.</typeparam>
/// <remarks>
/// aspnetcore Authz handlers are not required to satisfy the requirement and generally don't explicitly call Fail when
/// the requirement
/// isn't satisfied, however in many simple cases explicitly calling Succeed or Fail is what we want which is what this
/// class is used for.
/// </remarks>
public abstract class MustSatisfyRequirementAuthorizationHandler<T> : AuthorizationHandler<T>
where T : IAuthorizationRequirement
{
/// <inheritdoc />
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement)
{
var isAuth = await IsAuthorized(context, requirement);
if (isAuth)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
/// <summary>
/// Return true if the requirement is succeeded or ignored, return false if the requirement is explicitly not met
/// </summary>
/// <param name="context">The authorization context.</param>
/// <param name="requirement">The authorization requirement.</param>
/// <returns>True if request is authorized, false if not.</returns>
protected abstract Task<bool> IsAuthorized(AuthorizationHandlerContext context, T requirement);
}
/// <summary>
/// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what.
/// </summary>
/// <typeparam name="T">Authorization requirement.</typeparam>
/// <typeparam name="TResource">Resource to authorize access to.</typeparam>
/// <remarks>
/// aspnetcore Authz handlers are not required to satisfy the requirement and generally don't explicitly call Fail when
/// the requirement
/// isn't satisfied, however in many simple cases explicitly calling Succeed or Fail is what we want which is what this
/// class is used for.
/// </remarks>
public abstract class MustSatisfyRequirementAuthorizationHandler<T, TResource> : AuthorizationHandler<T, TResource>
where T : IAuthorizationRequirement
{
/// <inheritdoc />
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement,
TResource resource)
{
var isAuth = await IsAuthorized(context, requirement, resource);
if (isAuth)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
/// <summary>
/// Return true if the requirement is succeeded or ignored, return false if the requirement is explicitly not met
/// </summary>
/// <param name="context">The authorization context.</param>
/// <param name="requirement">The authorization requirement.</param>
/// <param name="resource">The resource to authorize access to.</param>
/// <returns>True if request is authorized, false if not.</returns>
protected abstract Task<bool> IsAuthorized(AuthorizationHandlerContext context, T requirement, TResource resource);
}

View File

@@ -1,85 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Abstract base class providing common functionality for authorization checks based on querystrings.
/// </summary>
/// <typeparam name="T">Authorization requirement</typeparam>
public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementAuthorizationHandler<T>
where T : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="PermissionsQueryStringHandler{T}" /> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
/// <param name="httpContextAccessor">Accessor for the HTTP context of the current request.</param>
/// <param name="entityService">Service for entity operations.</param>
public PermissionsQueryStringHandler(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
IEntityService entityService)
{
BackOfficeSecurityAccessor = backOfficeSecurityAccessor;
HttpContextAccessor = httpContextAccessor;
EntityService = entityService;
}
/// <summary>
/// Gets or sets the <see cref="IBackOfficeSecurityAccessor" /> instance.
/// </summary>
protected IBackOfficeSecurityAccessor BackOfficeSecurityAccessor { get; set; }
/// <summary>
/// Gets or sets the <see cref="IHttpContextAccessor" /> instance.
/// </summary>
protected IHttpContextAccessor HttpContextAccessor { get; set; }
/// <summary>
/// Gets or sets the <see cref="IEntityService" /> instance.
/// </summary>
protected IEntityService EntityService { get; set; }
/// <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>
protected bool TryParseNodeId(string argument, out int nodeId)
{
// If the argument is an int, it will parse and can be assigned to nodeId.
// It might be a udi, so check that next.
// Otherwise treat it as a guid - unlikely we ever get here.
// Failing that, we can't parse it.
if (int.TryParse(argument, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedId))
{
nodeId = parsedId;
return true;
}
if (UdiParser.TryParse(argument, true, out Udi? udi))
{
nodeId = EntityService.GetId(udi).Result;
return true;
}
if (Guid.TryParse(argument, out Guid key))
{
nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
return true;
}
nodeId = 0;
return false;
}
}

View File

@@ -1,36 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Ensures that the current user has access to the section
/// </summary>
/// <remarks>
/// The user only needs access to one of the sections specified, not all of the sections.
/// </remarks>
public class SectionHandler : MustSatisfyRequirementAuthorizationHandler<SectionRequirement>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="SectionHandler" /> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
public SectionHandler(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) =>
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, SectionRequirement requirement)
{
var authorized = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser != null &&
requirement.SectionAliases
.Any(app => _backOfficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess(
app, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser));
return Task.FromResult(authorized);
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Authorization requirements for <see cref="SectionHandler" />
/// </summary>
public class SectionRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="SectionRequirement" /> class.
/// </summary>
/// <param name="aliases">Aliases for sections that the user will need access to.</param>
public SectionRequirement(params string[] aliases) => SectionAliases = aliases;
/// <summary>
/// Gets the aliases for sections that the user will need access to.
/// </summary>
public IReadOnlyCollection<string> SectionAliases { get; }
}

View File

@@ -1,51 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Ensures that the current user has access to the section for which the specified tree(s) belongs
/// </summary>
/// <remarks>
/// This would allow a tree to be moved between sections.
/// The user only needs access to one of the trees specified, not all of the trees.
/// </remarks>
public class TreeHandler : MustSatisfyRequirementAuthorizationHandler<TreeRequirement>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly ITreeService _treeService;
/// <summary>
/// Initializes a new instance of the <see cref="TreeHandler" /> class.
/// </summary>
/// <param name="treeService">Service for section tree operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back-office security.</param>
public TreeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_treeService = treeService ?? throw new ArgumentNullException(nameof(treeService));
_backOfficeSecurityAccessor = backOfficeSecurityAccessor ??
throw new ArgumentNullException(nameof(backOfficeSecurityAccessor));
}
/// <inheritdoc />
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, TreeRequirement requirement)
{
var apps = requirement.TreeAliases
.Select(x => _treeService.GetByAlias(x))
.WhereNotNull()
.Select(x => x.SectionAlias)
.Distinct()
.ToArray();
var isAuth = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser != null &&
apps.Any(app => _backOfficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess(
app, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser));
return Task.FromResult(isAuth);
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Authorization;
/// <summary>
/// Authorization requirements for <see cref="TreeHandler" />
/// </summary>
public class TreeRequirement : IAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="TreeRequirement" /> class.
/// </summary>
/// <param name="aliases">The aliases for trees that the user will need access to.</param>
public TreeRequirement(params string[] aliases) => TreeAliases = aliases;
/// <summary>
/// Gets the aliases for trees that the user will need access to.
/// </summary>
public IReadOnlyCollection<string> TreeAliases { get; }
}

Some files were not shown because too many files have changed in this diff Show More