Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config to limit concurrent logins #14967

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Umbraco.Core/Configuration/Models/SecuritySettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class SecuritySettings
internal const bool StaticHideDisabledUsersInBackOffice = false;
internal const bool StaticAllowPasswordReset = true;
internal const bool StaticAllowEditInvariantFromNonDefault = false;
internal const bool StaticAllowConcurrentLogins = true;
internal const string StaticAuthCookieName = "UMB_UCONTEXT";

internal const string StaticAllowedUserNameCharacters =
Expand Down Expand Up @@ -109,4 +110,10 @@ public class SecuritySettings
[Obsolete("Use ContentSettings.AllowEditFromInvariant instead")]
[DefaultValue(StaticAllowEditInvariantFromNonDefault)]
public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault;

/// <summary>
/// Gets or sets a value indicating whether to allow concurrent logins.
/// </summary>
[DefaultValue(StaticAllowConcurrentLogins)]
public bool AllowConcurrentLogins { get; set; } = StaticAllowConcurrentLogins;
}
47 changes: 43 additions & 4 deletions src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Security.Claims;

Check notice on line 1 in src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 4.83 to 4.54, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
Expand Down Expand Up @@ -36,27 +36,66 @@
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation,
IEventAggregator eventAggregator)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
IEventAggregator eventAggregator,
IOptions<SecuritySettings> securitySettings)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, securitySettings)

Check notice on line 41 in src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ Getting worse: Constructor Over-Injection

BackOfficeSignInManager increases from 10 to 11 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
{
_userManager = userManager;
_externalLogins = externalLogins;
_eventAggregator = eventAggregator;
_globalSettings = globalSettings.Value;
}

[Obsolete("Use ctor with all params")]
[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public BackOfficeSignInManager(
BackOfficeUserManager userManager,
IHttpContextAccessor contextAccessor,
IBackOfficeExternalLoginProviders externalLogins,
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
IOptions<GlobalSettings> globalSettings,
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation,
IEventAggregator eventAggregator)
: this(
userManager,
contextAccessor,
externalLogins,
claimsFactory,
optionsAccessor,
globalSettings,
logger,
schemes,
confirmation,
eventAggregator,
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}

Check notice on line 74 in src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ Getting worse: Constructor Over-Injection

BackOfficeSignInManager increases from 9 to 10 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.

[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public BackOfficeSignInManager(
BackOfficeUserManager userManager,
IHttpContextAccessor contextAccessor,
IBackOfficeExternalLoginProviders externalLogins,
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
IOptions<GlobalSettings> globalSettings,
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation)
: this(userManager, contextAccessor, externalLogins, claimsFactory, optionsAccessor, globalSettings, logger, schemes, confirmation, StaticServiceProvider.Instance.GetRequiredService<IEventAggregator>())
: this(
userManager,
contextAccessor,
externalLogins,
claimsFactory,
optionsAccessor,
globalSettings,
logger,
schemes,
confirmation,
StaticServiceProvider.Instance.GetRequiredService<IEventAggregator>(),
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())

Check notice on line 98 in src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ New issue: Constructor Over-Injection

BackOfficeSignInManager has 9 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Web.Common.Security;

namespace Umbraco.Cms.Web.BackOffice.Security;

/// <summary>
/// Configures the back office security stamp options
/// Configures the back office security stamp options.
/// </summary>
public class
ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOptions<BackOfficeSecurityStampValidatorOptions>
public class ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOptions<BackOfficeSecurityStampValidatorOptions>
{
private readonly SecuritySettings _securitySettings;

public ConfigureBackOfficeSecurityStampValidatorOptions()
: this(StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}

public ConfigureBackOfficeSecurityStampValidatorOptions(IOptions<SecuritySettings> securitySettings)
=> _securitySettings = securitySettings.Value;

/// <inheritdoc />
public void Configure(BackOfficeSecurityStampValidatorOptions options)
=> ConfigureSecurityStampOptions.ConfigureOptions(options);
=> ConfigureSecurityStampOptions.ConfigureOptions(options, _securitySettings);
}
33 changes: 29 additions & 4 deletions src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;

namespace Umbraco.Cms.Web.Common.Security;

public class ConfigureSecurityStampOptions : IConfigureOptions<SecurityStampValidatorOptions>
{
private readonly SecuritySettings _securitySettings;

public ConfigureSecurityStampOptions()
: this(StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}

public ConfigureSecurityStampOptions(IOptions<SecuritySettings> securitySettings)
=> _securitySettings = securitySettings.Value;

[Obsolete("Use the overload accepting SecuritySettings instead. Scheduled for removal in v14.")]
public static void ConfigureOptions(SecurityStampValidatorOptions options)
=> ConfigureOptions(options, StaticServiceProvider.Instance.GetRequiredService<SecuritySettings>());

/// <summary>
/// Configures security stamp options and ensures any custom claims
/// set on the identity are persisted to the new identity when it's refreshed.
/// </summary>
/// <param name="options"></param>
public static void ConfigureOptions(SecurityStampValidatorOptions options)
/// <param name="options">Options for <see cref="ISecurityStampValidator"/>.</param>
/// <param name="securitySettings">The <see cref="SecuritySettings" /> options.</param>
public static void ConfigureOptions(SecurityStampValidatorOptions options, SecuritySettings securitySettings)
{
options.ValidationInterval = TimeSpan.FromMinutes(30);
// Adjust the security stamp validation interval to a shorter duration
// when concurrent logins are not allowed and the duration has the default interval value
// (currently defaults to 30 minutes), ensuring quicker re-validation.
if (securitySettings.AllowConcurrentLogins is false && options.ValidationInterval == TimeSpan.FromMinutes(30))
{
options.ValidationInterval = TimeSpan.FromSeconds(30);
}

// When refreshing the principal, ensure custom claims that
// might have been set with an external identity continue
Expand All @@ -34,6 +58,7 @@ public static void ConfigureOptions(SecurityStampValidatorOptions options)
};
}

/// <inheritdoc />
public void Configure(SecurityStampValidatorOptions options)
=> ConfigureOptions(options);
=> ConfigureOptions(options, _securitySettings);
}
33 changes: 30 additions & 3 deletions src/Umbraco.Web.Common/Security/MemberSignInManager.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Security.Claims;

Check notice on line 1 in src/Umbraco.Web.Common/Security/MemberSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

✅ No longer an issue: Overall Code Complexity

The mean cyclomatic complexity in this module is no longer above the threshold
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
Expand All @@ -30,14 +31,40 @@
IAuthenticationSchemeProvider schemes,
IUserConfirmation<MemberIdentityUser> confirmation,
IMemberExternalLoginProviders memberExternalLoginProviders,
IEventAggregator eventAggregator)
: base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
IEventAggregator eventAggregator,
IOptions<SecuritySettings> securitySettings)
: base(memberManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, securitySettings)

Check notice on line 36 in src/Umbraco.Web.Common/Security/MemberSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ Getting worse: Constructor Over-Injection

MemberSignInManager increases from 9 to 10 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
{
_memberExternalLoginProviders = memberExternalLoginProviders;
_eventAggregator = eventAggregator;
}

[Obsolete("Use ctor with all params")]
[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public MemberSignInManager(
UserManager<MemberIdentityUser> memberManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<MemberIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<MemberIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<MemberIdentityUser> confirmation,
IMemberExternalLoginProviders memberExternalLoginProviders,
IEventAggregator eventAggregator)
: this(
memberManager,
contextAccessor,
claimsFactory,
optionsAccessor,
logger,
schemes,
confirmation,
StaticServiceProvider.Instance.GetRequiredService<IMemberExternalLoginProviders>(),
StaticServiceProvider.Instance.GetRequiredService<IEventAggregator>(),
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}

Check notice on line 65 in src/Umbraco.Web.Common/Security/MemberSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ Getting worse: Constructor Over-Injection

MemberSignInManager increases from 7 to 9 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.

[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]

Check notice on line 67 in src/Umbraco.Web.Common/Security/MemberSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ New issue: Constructor Over-Injection

MemberSignInManager has 7 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
public MemberSignInManager(
UserManager<MemberIdentityUser> memberManager,
IHttpContextAccessor contextAccessor,
Expand Down
37 changes: 35 additions & 2 deletions src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
using System.Security.Claims;

Check notice on line 1 in src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 4.43 to 4.32, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.

Check notice on line 1 in src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

✅ Getting better: Primitive Obsession

The ratio of primitive types in function arguments decreases from 48.84% to 41.18%, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security;
using Umbraco.Extensions;

namespace Umbraco.Cms.Web.Common.Security;

/// <summary>
/// Abstract sign in manager implementation allowing modifying all defeault authentication schemes
/// Abstract sign in manager implementation allowing modifying all default authentication schemes.
/// </summary>
/// <typeparam name="TUser"></typeparam>
public abstract class UmbracoSignInManager<TUser> : SignInManager<TUser>
where TUser : UmbracoIdentityUser
{
private SecuritySettings _securitySettings;

// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
protected const string UmbracoSignInMgrLoginProviderKey = "LoginProvider";

// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
protected const string UmbracoSignInMgrXsrfKey = "XsrfId";

[Obsolete("Use non-obsolete constructor. This is scheduled for removal in V14.")]
public UmbracoSignInManager(
UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
Expand All @@ -30,8 +36,30 @@
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation)
: this(
userManager,
contextAccessor,
claimsFactory,
optionsAccessor,
logger,
schemes,
confirmation,
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
{
}

public UmbracoSignInManager(
UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation,
IOptions<SecuritySettings> securitySettingsOptions)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_securitySettings = securitySettingsOptions.Value;

Check notice on line 62 in src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

ℹ New issue: Constructor Over-Injection

UmbracoSignInManager has 8 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
}

protected abstract string AuthenticationType { get; }
Expand All @@ -47,7 +75,7 @@
{
// override to handle logging/events
SignInResult result = await base.PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
return await HandleSignIn(user, user.UserName, result);
return result;
}

/// <inheritdoc />
Expand Down Expand Up @@ -340,6 +368,11 @@

await UserManager.UpdateAsync(user);

if (_securitySettings.AllowConcurrentLogins is false)
{
await UserManager.UpdateSecurityStampAsync(user);
}

Check warning on line 375 in src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v12/dev)

❌ Getting worse: Complex Method

HandleSignIn increases in cyclomatic complexity from 10 to 11, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
Logger.LogInformation("User: {UserName} logged in from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress);
}
else if (result.IsLockedOut)
Expand Down
3 changes: 3 additions & 0 deletions templates/UmbracoProject/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
},
"Unattended": {
"UpgradeUnattended": true
},
"Security": {
"AllowConcurrentLogins": false
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public MemberSignInManager CreateSut()
Mock.Of<IAuthenticationSchemeProvider>(),
Mock.Of<IUserConfirmation<MemberIdentityUser>>(),
Mock.Of<IMemberExternalLoginProviders>(),
Mock.Of<IEventAggregator>());
Mock.Of<IEventAggregator>(),
Mock.Of<IOptions<SecuritySettings>>(x => x.Value == new SecuritySettings()));
}

private static Mock<MemberManager> MockMemberManager()
Expand Down
Loading