Revoke previous sessions when AllowConcurrentLogins is false (#15892)

This commit is contained in:
Elitsa Marinovska
2024-03-18 13:18:57 +01:00
committed by GitHub
parent 76066eac5a
commit e9cfcf4e99
2 changed files with 45 additions and 5 deletions

View File

@@ -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<UserDeletedNotification, RevokeUserAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<UserGroupDeletingNotification, RevokeUserAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<UserGroupDeletedNotification, RevokeUserAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<UserLoginSuccessNotification, RevokeUserAuthenticationTokensNotificationHandler>();
return builder;
}

View File

@@ -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<UserSavedNotification>,
INotificationAsyncHandler<UserDeletedNotification>,
INotificationAsyncHandler<UserGroupDeletingNotification>,
INotificationAsyncHandler<UserGroupDeletedNotification>
INotificationAsyncHandler<UserGroupDeletedNotification>,
INotificationAsyncHandler<UserLoginSuccessNotification>
{
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<RevokeUserAuthenticationTokensNotificationHandler> _logger;
private readonly SecuritySettings _securitySettings;
public RevokeUserAuthenticationTokensNotificationHandler(
IUserService userService,
IUserGroupService userGroupService,
IOpenIddictTokenManager tokenManager,
ILogger<RevokeUserAuthenticationTokensNotificationHandler> logger)
ILogger<RevokeUserAuthenticationTokensNotificationHandler> logger,
IOptions<SecuritySettings> 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<UserStartNodesAndGroupAccess?> GetRelevantUserAccessDataByUserKeyAsync(Guid userKey)
{
@@ -206,6 +228,23 @@ internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
}
}
private async Task<IUser?> 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<Guid> GroupKeys { get; }