From e9cfcf4e99d61bdf463ce1875cda62456f86c3ef Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:18:57 +0100 Subject: [PATCH] Revoke previous sessions when AllowConcurrentLogins is false (#15892) --- .../BackOfficeAuthBuilderExtensions.cs | 5 ++- ...AuthenticationTokensNotificationHandler.cs | 45 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs index 1821aad479..e36f103e92 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs @@ -3,10 +3,10 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Common.DependencyInjection; using Umbraco.Cms.Api.Management.Configuration; using Umbraco.Cms.Api.Management.Handlers; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Api.Management.Middleware; using Umbraco.Cms.Api.Management.Security; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.Security; using Umbraco.Cms.Web.Common.ApplicationBuilder; @@ -32,6 +32,7 @@ public static class BackOfficeAuthBuilderExtensions builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); return builder; } diff --git a/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs b/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs index a418f3caed..87475e26ae 100644 --- a/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs @@ -1,6 +1,9 @@ using System.Data.Common; +using System.Globalization; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using OpenIddict.Abstractions; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; @@ -14,7 +17,8 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : INotificationAsyncHandler, INotificationAsyncHandler, INotificationAsyncHandler, - INotificationAsyncHandler + INotificationAsyncHandler, + INotificationAsyncHandler { private const string NotificationStateKey = "Umbraco.Cms.Api.Management.Handlers.RevokeUserAuthenticationTokensNotificationHandler"; @@ -22,17 +26,20 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : 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) + 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 @@ -112,7 +119,6 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : { _logger.LogWarning(e, "This is expected when we upgrade from < Umbraco 14. Otherwise it should not happen"); } - } // We can only delete non-logged in users in Umbraco, meaning that such will not have a token, @@ -168,6 +174,22 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : } } + public async Task HandleAsync(UserLoginSuccessNotification notification, CancellationToken cancellationToken) + { + if (_securitySettings.AllowConcurrentLogins is false) + { + var userId = notification.AffectedUserId; + IUser? user = userId is not null ? await FindUserFromString(userId) : null; + + if (user is null) + { + return; + } + + await RevokeTokensAsync(user); + } + } + // Get data about the user before saving private async Task GetRelevantUserAccessDataByUserKeyAsync(Guid userKey) { @@ -206,6 +228,23 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler : } } + private async Task FindUserFromString(string userId) + { + if (int.TryParse(userId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id)) + { + return _userService.GetUserById(id); + } + + // We couldn't directly convert the ID to an int, this is because the user logged in with external login. + // So we need to look up the user by key. + if (Guid.TryParse(userId, out Guid key)) + { + return await _userService.GetAsync(key); + } + + return null; + } + private class UserStartNodesAndGroupAccess { public IEnumerable GroupKeys { get; }