diff --git a/Directory.Packages.props b/Directory.Packages.props
index 090afcd216..78d394ab7c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,30 +5,31 @@
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
+
+
@@ -44,22 +45,22 @@
-
-
+
+
-
+
-
-
-
+
+
+
@@ -68,7 +69,7 @@
-
+
@@ -79,7 +80,7 @@
-
+
diff --git a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs
new file mode 100644
index 0000000000..f428957bd9
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureOpenIddict.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.Options;
+using OpenIddict.Server.AspNetCore;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Api.Common.Configuration;
+
+internal class ConfigureOpenIddict : IConfigureOptions
+{
+ private readonly IOptions _globalSettings;
+
+ public ConfigureOpenIddict(IOptions globalSettings) => _globalSettings = globalSettings;
+
+ public void Configure(OpenIddictServerAspNetCoreOptions options)
+ => options.DisableTransportSecurityRequirement = _globalSettings.Value.UseHttps is false;
+}
diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
index d5556f63c2..98068791af 100644
--- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
+++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
@@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Server;
using OpenIddict.Validation;
+using Umbraco.Cms.Api.Common.Configuration;
using Umbraco.Cms.Api.Common.Security;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
@@ -132,5 +133,6 @@ public static class UmbracoBuilderAuthExtensions
});
builder.Services.AddRecurringBackgroundJob();
+ builder.Services.ConfigureOptions();
}
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs
index 08d7a916e6..37651a4158 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestHeaderHandler.cs
@@ -8,11 +8,5 @@ internal abstract class RequestHeaderHandler
protected RequestHeaderHandler(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
- protected string? GetHeaderValue(string headerName)
- {
- HttpContext httpContext = _httpContextAccessor.HttpContext ??
- throw new InvalidOperationException("Could not obtain an HTTP context");
-
- return httpContext.Request.Headers[headerName];
- }
+ protected string? GetHeaderValue(string headerName) => _httpContextAccessor.HttpContext?.Request.Headers[headerName];
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs
new file mode 100644
index 0000000000..98f1526ce9
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/User/CalculateStartNodesUserController.cs
@@ -0,0 +1,59 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Management.Factories;
+using Umbraco.Cms.Api.Management.ViewModels.User;
+using Umbraco.Cms.Core.Models.Membership;
+using Umbraco.Cms.Core.Security.Authorization;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.OperationStatus;
+using Umbraco.Cms.Web.Common.Authorization;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Api.Management.Controllers.User;
+
+[ApiVersion("1.0")]
+public class CalculatedStartNodesUserController : UserControllerBase
+{
+ private readonly IAuthorizationService _authorizationService;
+ private readonly IUserService _userService;
+ private readonly IUserPresentationFactory _userPresentationFactory;
+
+ public CalculatedStartNodesUserController(
+ IAuthorizationService authorizationService,
+ IUserService userService,
+ IUserPresentationFactory userPresentationFactory)
+ {
+ _authorizationService = authorizationService;
+ _userService = userService;
+ _userPresentationFactory = userPresentationFactory;
+ }
+
+ [HttpGet("{id:guid}/calculate-start-nodes")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(CalculatedUserStartNodesResponseModel), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
+ public async Task CalculatedStartNodes(CancellationToken cancellationToken, Guid id)
+ {
+ AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
+ User,
+ UserPermissionResource.WithKeys(id),
+ AuthorizationPolicies.UserPermissionByResource);
+
+ if (!authorizationResult.Succeeded)
+ {
+ return Forbidden();
+ }
+
+ IUser? user = await _userService.GetAsync(id);
+
+ if (user is null)
+ {
+ return UserOperationStatusResult(UserOperationStatus.UserNotFound);
+ }
+
+ CalculatedUserStartNodesResponseModel responseModel = await _userPresentationFactory.CreateCalculatedUserStartNodesResponseModelAsync(user);
+ return Ok(responseModel);
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs
index e36f103e92..5ddb98b570 100644
--- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs
+++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs
@@ -27,11 +27,8 @@ public static class BackOfficeAuthBuilderExtensions
public static IUmbracoBuilder AddTokenRevocation(this IUmbracoBuilder builder)
{
- builder.AddNotificationAsyncHandler();
builder.AddNotificationAsyncHandler();
builder.AddNotificationAsyncHandler();
- builder.AddNotificationAsyncHandler();
- builder.AddNotificationAsyncHandler();
builder.AddNotificationAsyncHandler();
return builder;
diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs
index 4714c54c74..45eccad5ec 100644
--- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs
+++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs
@@ -28,6 +28,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddAuthorization(CreatePolicies);
return builder;
@@ -35,14 +36,12 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
private static void CreatePolicies(AuthorizationOptions options)
{
- void AddPolicy(string policyName, string claimType, params string[] allowedClaimValues)
- {
- options.AddPolicy(policyName, policy =>
+ void AddAllowedApplicationsPolicy(string policyName, params string[] allowedClaimValues)
+ => options.AddPolicy(policyName, policy =>
{
policy.AuthenticationSchemes.Add(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
- policy.RequireClaim(claimType, allowedClaimValues);
+ policy.Requirements.Add(new AllowedApplicationRequirement(allowedClaimValues));
});
- }
options.AddPolicy(AuthorizationPolicies.BackOfficeAccess, policy =>
{
@@ -56,39 +55,39 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
policy.RequireRole(Constants.Security.AdminGroupAlias);
});
- AddPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content);
- AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Media);
- AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, Constants.Security.AllowedApplicationsClaimType,
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Applications.Content);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Applications.Content, Constants.Applications.Media);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForContentTree,
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members);
- AddPolicy(AuthorizationPolicies.SectionAccessForMediaTree, Constants.Security.AllowedApplicationsClaimType,
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMediaTree,
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members);
- AddPolicy(AuthorizationPolicies.SectionAccessForMemberTree, Constants.Security.AllowedApplicationsClaimType,
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMemberTree,
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members);
- AddPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media);
- AddPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members);
- AddPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Packages);
- AddPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Users);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Applications.Media);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Applications.Members);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Applications.Packages);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Applications.Users);
- AddPolicy(AuthorizationPolicies.TreeAccessDataTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessDictionary, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Translation);
- AddPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Translation, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content);
- AddPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members);
- AddPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
- AddPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDataTypes, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDictionary, Constants.Applications.Translation);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, Constants.Applications.Translation, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Applications.Content);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Applications.Content, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Applications.Media, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Applications.Members);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Applications.Settings);
+ AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Applications.Settings);
// Contextual permissions
options.AddPolicy(AuthorizationPolicies.ContentPermissionByResource, policy =>
diff --git a/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs
index 9af4ace9e1..a1e8a79edf 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs
@@ -25,4 +25,6 @@ public interface IUserPresentationFactory
Task CreateCurrentUserConfigurationModelAsync();
UserItemResponseModel CreateItemResponseModel(IUser user);
+
+ Task CreateCalculatedUserStartNodesResponseModelAsync(IUser user);
}
diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
index 641f5883ed..9164be6772 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
@@ -212,6 +212,23 @@ public class UserPresentationFactory : IUserPresentationFactory
});
}
+ public async Task CreateCalculatedUserStartNodesResponseModelAsync(IUser user)
+ {
+ var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ ISet mediaStartNodeKeys = GetKeysFromIds(mediaStartNodeIds, UmbracoObjectTypes.Media);
+ var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches);
+ ISet documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document);
+
+ return await Task.FromResult(new CalculatedUserStartNodesResponseModel()
+ {
+ Id = user.Key,
+ MediaStartNodeIds = mediaStartNodeKeys,
+ HasMediaRootAccess = HasRootAccess(mediaStartNodeIds),
+ DocumentStartNodeIds = documentStartNodeKeys,
+ HasDocumentRootAccess = HasRootAccess(contentStartNodeIds),
+ });
+ }
+
private ISet GetKeysFromIds(IEnumerable? ids, UmbracoObjectTypes type)
{
IEnumerable? models = ids?
diff --git a/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs b/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs
index acf6cb508a..e56e776f85 100644
--- a/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs
+++ b/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs
@@ -13,77 +13,31 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Handlers;
internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
- INotificationAsyncHandler,
INotificationAsyncHandler,
INotificationAsyncHandler,
- INotificationAsyncHandler,
- INotificationAsyncHandler,
INotificationAsyncHandler
{
- private const string NotificationStateKey = "Umbraco.Cms.Api.Management.Handlers.RevokeUserAuthenticationTokensNotificationHandler";
-
private readonly IUserService _userService;
- private readonly IUserGroupService _userGroupService;
private readonly IOpenIddictTokenManager _tokenManager;
private readonly ILogger _logger;
private readonly SecuritySettings _securitySettings;
public RevokeUserAuthenticationTokensNotificationHandler(
IUserService userService,
- IUserGroupService userGroupService,
IOpenIddictTokenManager tokenManager,
ILogger logger,
IOptions securitySettingsOptions)
{
_userService = userService;
- _userGroupService = userGroupService;
_tokenManager = tokenManager;
_logger = logger;
_securitySettings = securitySettingsOptions.Value;
}
- // We need to know the pre-saving state of the saved users in order to compare if their access has changed
- public async Task HandleAsync(UserSavingNotification notification, CancellationToken cancellationToken)
- {
- try
- {
- var usersAccess = new Dictionary();
- foreach (IUser user in notification.SavedEntities)
- {
- UserStartNodesAndGroupAccess? priorUserAccess = await GetRelevantUserAccessDataByUserKeyAsync(user.Key);
- if (priorUserAccess == null)
- {
- continue;
- }
-
- usersAccess.Add(user.Key, priorUserAccess);
- }
-
- notification.State[NotificationStateKey] = usersAccess;
- }
- catch (DbException e)
- {
- _logger.LogWarning(e, "This is expected when we upgrade from < Umbraco 14. Otherwise it should not happen");
- }
- }
-
public async Task HandleAsync(UserSavedNotification notification, CancellationToken cancellationToken)
{
try
{
- Dictionary? preSavingUsersState = null;
-
- if (notification.State.TryGetValue(NotificationStateKey, out var value))
- {
- preSavingUsersState = value as Dictionary;
- }
-
- // If we have a new user, there is no token
- if (preSavingUsersState is null || preSavingUsersState.Count == 0)
- {
- return;
- }
-
foreach (IUser user in notification.SavedEntities)
{
if (user.IsSuper())
@@ -95,23 +49,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
if (user.IsLockedOut || user.IsApproved is false)
{
await RevokeTokensAsync(user);
- continue;
- }
-
- // Don't revoke admin tokens to prevent log out when accidental changes
- if (user.IsAdmin())
- {
- continue;
- }
-
- // Check if the user access has changed - we also need to revoke all tokens in this case
- if (preSavingUsersState.TryGetValue(user.Key, out UserStartNodesAndGroupAccess? preSavingState))
- {
- UserStartNodesAndGroupAccess postSavingState = MapToUserStartNodesAndGroupAccess(user);
- if (preSavingState.CompareAccess(postSavingState) == false)
- {
- await RevokeTokensAsync(user);
- }
}
}
}
@@ -131,49 +68,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
}
}
- // We need to know the pre-deleting state of the users part of the deleted group to revoke their tokens
- public async Task HandleAsync(UserGroupDeletingNotification notification, CancellationToken cancellationToken)
- {
- var usersInGroups = new Dictionary>();
- foreach (IUserGroup userGroup in notification.DeletedEntities)
- {
- var users = await GetUsersByGroupKeyAsync(userGroup.Key);
- if (users == null)
- {
- continue;
- }
-
- usersInGroups.Add(userGroup.Key, users);
- }
-
- notification.State[NotificationStateKey] = usersInGroups;
- }
-
- public async Task HandleAsync(UserGroupDeletedNotification notification, CancellationToken cancellationToken)
- {
- Dictionary>? preDeletingUsersInGroups = null;
-
- if (notification.State.TryGetValue(NotificationStateKey, out var value))
- {
- preDeletingUsersInGroups = value as Dictionary>;
- }
-
- if (preDeletingUsersInGroups is null)
- {
- return;
- }
-
- // since the user group was deleted, we can only use the information we collected before the deletion
- // this means that we will not be able to detect users in any groups that were eventually deleted (due to implementor/3th party supplier interference)
- // that were not in the initial to be deleted list
- foreach (IUser user in preDeletingUsersInGroups
- .Where(group => notification.DeletedEntities.Any(entity => group.Key == entity.Key))
- .SelectMany(group => group.Value))
- {
- await RevokeTokensAsync(user);
- }
- }
-
public async Task HandleAsync(UserLoginSuccessNotification notification, CancellationToken cancellationToken)
{
if (_securitySettings.AllowConcurrentLogins is false)
@@ -190,29 +84,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
}
}
- // Get data about the user before saving
- private async Task GetRelevantUserAccessDataByUserKeyAsync(Guid userKey)
- {
- IUser? user = await _userService.GetAsync(userKey);
-
- return user is null
- ? null
- : MapToUserStartNodesAndGroupAccess(user);
- }
-
- private UserStartNodesAndGroupAccess MapToUserStartNodesAndGroupAccess(IUser user)
- => new(user.Groups.Select(g => g.Key), user.StartContentIds, user.StartMediaIds);
-
- // Get data about the users part of a group before deleting it
- private async Task?> GetUsersByGroupKeyAsync(Guid userGroupKey)
- {
- IUserGroup? userGroup = await _userGroupService.GetAsync(userGroupKey);
-
- return userGroup is null
- ? null
- : _userService.GetAllInGroup(userGroup.Id);
- }
-
private async Task RevokeTokensAsync(IUser user)
{
_logger.LogInformation("Revoking active tokens for user with ID {id}", user.Id);
@@ -236,35 +107,4 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
return null;
}
-
- private class UserStartNodesAndGroupAccess
- {
- public IEnumerable GroupKeys { get; }
-
- public int[]? StartContentIds { get; }
-
- public int[]? StartMediaIds { get; }
-
- public UserStartNodesAndGroupAccess(IEnumerable groupKeys, int[]? startContentIds, int[]? startMediaIds)
- {
- GroupKeys = groupKeys;
- StartContentIds = startContentIds;
- StartMediaIds = startMediaIds;
- }
-
- public bool CompareAccess(UserStartNodesAndGroupAccess other)
- {
- var areContentStartNodesEqual = (StartContentIds == null && other.StartContentIds == null) ||
- (StartContentIds != null && other.StartContentIds != null &&
- StartContentIds.SequenceEqual(other.StartContentIds));
-
- var areMediaStartNodesEqual = (StartMediaIds == null && other.StartMediaIds == null) ||
- (StartMediaIds != null && other.StartMediaIds != null &&
- StartMediaIds.SequenceEqual(other.StartMediaIds));
-
- return areContentStartNodesEqual &&
- areMediaStartNodesEqual &&
- GroupKeys.SequenceEqual(other.GroupKeys);
- }
- }
}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 0adc3c36b7..11dee452ca 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -30298,6 +30298,66 @@
]
}
},
+ "/umbraco/management/api/v1/user/{id}/calculate-start-nodes": {
+ "get": {
+ "tags": [
+ "User"
+ ],
+ "operationId": "GetUserByIdCalculateStartNodes",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/CalculatedUserStartNodesResponseModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user do not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
"/umbraco/management/api/v1/user/{id}/change-password": {
"post": {
"tags": [
@@ -33447,6 +33507,51 @@
},
"additionalProperties": false
},
+ "CalculatedUserStartNodesResponseModel": {
+ "required": [
+ "documentStartNodeIds",
+ "hasDocumentRootAccess",
+ "hasMediaRootAccess",
+ "id",
+ "mediaStartNodeIds"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "documentStartNodeIds": {
+ "uniqueItems": true,
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/ReferenceByIdModel"
+ }
+ ]
+ }
+ },
+ "hasDocumentRootAccess": {
+ "type": "boolean"
+ },
+ "mediaStartNodeIds": {
+ "uniqueItems": true,
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/ReferenceByIdModel"
+ }
+ ]
+ }
+ },
+ "hasMediaRootAccess": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false
+ },
"ChangePasswordCurrentUserRequestModel": {
"required": [
"newPassword"
diff --git a/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs
new file mode 100644
index 0000000000..a36a592827
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationHandler.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNetCore.Authorization;
+using Umbraco.Cms.Core.Models.Membership;
+using Umbraco.Cms.Core.Security.Authorization;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Api.Management.Security.Authorization.User;
+
+///
+/// Authorizes that the current user has the correct permission access to the applications listed in the requirement.
+///
+internal sealed class AllowedApplicationHandler : MustSatisfyRequirementAuthorizationHandler
+{
+ private readonly IAuthorizationHelper _authorizationHelper;
+
+ public AllowedApplicationHandler(IAuthorizationHelper authorizationHelper)
+ => _authorizationHelper = authorizationHelper;
+
+ protected override Task IsAuthorized(AuthorizationHandlerContext context, AllowedApplicationRequirement requirement)
+ {
+ IUser user = _authorizationHelper.GetUmbracoUser(context.User);
+ var allowed = user.AllowedSections.ContainsAny(requirement.Applications);
+ return Task.FromResult(allowed);
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs
new file mode 100644
index 0000000000..dce6d8773e
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Security/Authorization/User/AllowedApplicationRequirement.cs
@@ -0,0 +1,14 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Umbraco.Cms.Api.Management.Security.Authorization.User;
+
+///
+/// Authorization requirement for the .
+///
+internal sealed class AllowedApplicationRequirement : IAuthorizationRequirement
+{
+ public string[] Applications { get; }
+
+ public AllowedApplicationRequirement(params string[] applications)
+ => Applications = applications;
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs
new file mode 100644
index 0000000000..8cc71e8482
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/User/CalculatedUserStartNodesResponseModel.cs
@@ -0,0 +1,14 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.User;
+
+public class CalculatedUserStartNodesResponseModel
+{
+ public required Guid Id { get; init; }
+
+ public ISet DocumentStartNodeIds { get; set; } = new HashSet();
+
+ public bool HasDocumentRootAccess { get; set; }
+
+ public ISet MediaStartNodeIds { get; set; } = new HashSet();
+
+ public bool HasMediaRootAccess { get; set; }
+}
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
index 13126a24b5..549ea5cb40 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs
index 842add06fa..2f24fa182d 100644
--- a/src/Umbraco.Core/Constants-Security.cs
+++ b/src/Umbraco.Core/Constants-Security.cs
@@ -93,12 +93,15 @@ public static partial class Constants
public const string MemberExternalAuthenticationTypePrefix = "UmbracoMembers.";
+ [Obsolete("Please use the UserExtensions class to access user start node info. Will be removed in V15.")]
public const string StartContentNodeIdClaimType =
"http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode";
+ [Obsolete("Please use the UserExtensions class to access user start node info. Will be removed in V15.")]
public const string StartMediaNodeIdClaimType =
"http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
+ [Obsolete("Please use IUser.AllowedSections instead. Will be removed in V15.")]
public const string AllowedApplicationsClaimType =
"http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
diff --git a/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs b/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs
index 20373b1d3b..f8ebee826b 100644
--- a/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs
+++ b/src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs
@@ -17,6 +17,6 @@ public sealed class ApiMediaUrlProvider : IApiMediaUrlProvider
throw new ArgumentException("Media URLs can only be generated from Media items.", nameof(media));
}
- return _publishedUrlProvider.GetMediaUrl(media, UrlMode.Relative);
+ return _publishedUrlProvider.GetMediaUrl(media);
}
}
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
index e72560cd5f..3e4e9ea08f 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
@@ -409,6 +409,27 @@
+
+ X-Frame-Options koji se koristi za kontrolu da li neko mjesto može biti IFRAMED od strane drugog je pronađen.]]>
+
+ X-Frame-Options koji se koristi za kontrolu da li neko mjesto može biti IFRAMED od strane drugog nije pronađen.]]>
+
+ X-Content-Type-Options koji se koristi za zaštitu od ranjivosti MIME sniffinga je pronađen.]]>
+
+ X-Content-Type-Options koji se koristi za zaštitu od ranjivosti MIME sniffinga nije pronađen.]]>
+
+ Strict-Transport-Security, takođe poznat kao HSTS-header, je pronađen.]]>
+
+ Strict-Transport-Security nije pronađeno.]]>
+
+ Strict-Transport-Security, takođe poznat kao HSTS-header, je pronađen. Ovo zaglavlje ne bi trebalo biti prisutno na lokalnom hostu.]]>
+
+
+ Strict-Transport-Security nije pronađeno. Ovo zaglavlje ne bi trebalo biti prisutno na lokalnom hostu.]]>
+
+ X-XSS-Protection je pronađeno.]]>
+
+ X-XSS-Protection nije pronađeno.]]>%0%.]]>Nisu pronađena zaglavlja koja otkrivaju informacije o tehnologiji web stranice.
@@ -437,4 +458,4 @@
Pisanje fajlovaKreiranje medijskog foldera
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml
index 8efd2506a2..e2f37a9ec7 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml
@@ -338,6 +338,14 @@
+ X-Frame-Options, které určuje, zda může být obsah webu zobrazen na jiném webu pomocí IFRAME.]]>
+ X-Frame-Options, které určuje, zda může být obsah webu zobrazen na jiném webu pomocí IFRAME.]]>
+ X-Content-Type-Options použitá k ochraně před zranitelnostmi čichání MIME.]]>
+ X-Content-Type-Options použité k ochraně před zranitelnostmi čichání MIME nebyly nalezeny.]]>
+ Strict-Transport-Security, také známo jako HSTS-header, bylo nalezeno.]]>
+ Strict-Transport-Security nebylo nalezeno.]]>
+ X-XSS-Protection bylo nalezeno.]]>
+ X-XSS-Protection bylo nalezeno.]]>%0%.]]>Nebyly nalezeny žádné hlavičky odhalující informace o technologii webových stránek.Nastavení SMTP jsou správně nakonfigurována a služba funguje jak má.
@@ -350,4 +358,4 @@
Obsah s ID: {0} v koši souvisí s původním nadřazeným obsahem s ID: {1}Média s ID: {0} v koši souvisí s původním nadřazeným médiem s ID: {1}
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml
index d0d67339bd..201f18059e 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml
@@ -420,6 +420,20 @@
+ X-Frame-Options sy'n cael ei ddefnyddio i reoli os mae safle'n gallu cael ei osod o fewn IFRAME gan safle arall wedi'i ganfod.]]>
+ X-Frame-Options sy'n cael ei ddefnyddio i reoli os mae safle'n gallu cael ei osod o fewn IFRAME gan safle arall wedi'i ganfod.]]>
+ X-Content-Type-Options sy'n cael ei ddefnyddio i amddiffyn yn erbyn gwendidau sniffio MIME wedi'i ganfod.]]>
+ X-Content-Type-Options sy'n cael ei ddefnyddio i amddiffyn yn erbyn gwendidau sniffio MIME wedi'i ganfod.]]>
+ Strict-Transport-Security, hefyd wedi'i adnabod fel HSTS-header, wedi'i ganfod.]]>
+ Strict-Transport-Security wedi'i ganfod.]]>
+
+ Strict-Transport-Security, a elwir hefyd yn HSTS-header. Ni ddylai'r pennyn hwn fod yn bresennol ar localhost.]]>
+
+
+ Strict-Transport-Security. Ni ddylai'r pennyn hwn fod yn bresennol ar localhost.]]>
+
+ X-XSS-Protection wedi'i ganfod.]]>
+ X-XSS-Protection wedi'i ganfod.]]>%0%.]]>Dim peniadau sy'n datgelu gwynodaeth am dechnoleg eich gwefan wedi'u canfod.Gosodiadau SMTP wedi ffurfweddu'n gywir ac mae'r gwasanaeth yn gweithio fel y disgwylir.
@@ -453,4 +467,4 @@
ID safle dienw, fersiwn Umbraco, a phecynnau wedi'u gosod.
Nifer o: Nodau gwraidd, Nodau Cynnwys, Macros, Cyfryngau, Mathau o Ddogfen, Templedi, Ieithoedd, Parthau, Grŵp Defnyddwyr, Defnyddwyr, Aelodau, a Golygyddion Eiddo a ddefnyddir.
Gwybodaeth system: Webserver, gweinydd OS, fframwaith gweinydd, iaith gweinyddwr OS, a darparwr cronfa ddata.
Gosodiadau cyfluniad: Modd Modelsbuilder, os oes llwybr Umbraco arferol yn bodoli, amgylchedd ASP, ac os ydych chi yn y modd dadfygio.
Efallai y byddwn yn newid yr hyn a anfonwn ar y lefel Fanwl yn y dyfodol. Os felly, fe'i rhestrir uchod.
Drwy ddewis "Manwl" rydych yn cytuno i wybodaeth ddienw yn awr ac yn y dyfodol gael ei chasglu.
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
index aaccac8607..599232b13a 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
@@ -442,6 +442,29 @@
+
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]>
+
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]>
+
+ X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was found.]]>
+
+ X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was not found.]]>
+
+ Strict-Transport-Security, also known as the HSTS-header, was found.]]>
+
+ Strict-Transport-Security was not found.]]>
+
+ Strict-Transport-Security, also known as the HSTS-header, was found. This header should not be present on localhost.]]>
+
+
+ Strict-Transport-Security was not found. This header should not be present on localhost.]]>
+
+
+ X-XSS-Protection was found. It is recommended not to add this header to your website.
+ You can read about this on the Mozilla website ]]>
+
+ X-XSS-Protection was not found.]]>%0%.]]>No headers revealing information about the website technology were found.
@@ -483,4 +506,4 @@
We might change what we send on the Detailed level in the future. If so, it will be listed above.
By choosing "Detailed" you agree to current and future anonymized information being collected.]]>
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
index 01482e3863..7517a43b12 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
@@ -430,6 +430,29 @@
-->
%0%.]]>The appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' is not set.
+
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]>
+
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]>
+
+ X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was found.]]>
+
+ X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was not found.]]>
+
+ Strict-Transport-Security, also known as the HSTS-header, was found.]]>
+
+ Strict-Transport-Security was not found.]]>
+
+ Strict-Transport-Security, also known as the HSTS-header, was found. This header should not be present on localhost.]]>
+
+
+ Strict-Transport-Security was not found. This header should not be present on localhost.]]>
+
+
+ X-XSS-Protection was found. It is recommended not to add this header to your website.
+ You can read about this on the Mozilla website ]]>
+
+ X-XSS-Protection was not found.]]>
%0%.]]>No headers revealing information about the website technology were found.
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml
index 7e82adb9c9..cfde5a04e7 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml
@@ -285,6 +285,8 @@
+ X-Frame-Options usado para controlar si un sitio puede ser IFRAMEd por otra fue encontrado.]]>
+ X-Frame-Options usado para controlar si un sitio puede ser IFRAMEd por otra no se ha encontrado.]]>%0%.]]>No se ha encontrado ninguna cabecera que revele información sobre la tecnología del sitio.Los valores SMTP están configurados correctamente y el servicio opera con normalidad.
@@ -293,4 +295,4 @@
Los resultados de los Chequeos de Salud de Umbraco programados para ejecutarse el %0% a las %1% son: