Security stamp validator. Removed more references to ASP.NET Identity 2

This commit is contained in:
Scott Brady
2020-04-06 17:23:04 +01:00
parent 02dfa893e9
commit e5f3c4a186
9 changed files with 100 additions and 90 deletions

View File

@@ -33,7 +33,7 @@
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />
<dependency id="LightInject.WebApi" version="[2.0.0,2.999999)" />
<dependency id="Markdown" version="[2.2.1,2.999999)" />
<dependency id="Microsoft.AspNet.Identity.Owin" version="[2.2.2,2.999999)" />
<dependency id="Microsoft.AspNet.Identity.Core" version="[2.2.2,2.999999)" />
<dependency id="Microsoft.AspNet.Mvc" version="[5.2.7,5.999999)" />
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.4.0,2.999999)" />
<dependency id="Microsoft.AspNet.WebApi" version="[5.2.7,5.999999)" />

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Web.Models.Identity
@@ -13,7 +12,7 @@ namespace Umbraco.Web.Models.Identity
/// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want
/// references to that so we will create our own here
/// </remarks>
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
public class IdentityUser<TKey, TLogin, TRole, TClaim>
where TLogin : IIdentityUserLogin
//NOTE: Making our role id a string
where TRole : IdentityUserRole<string>

View File

@@ -147,15 +147,14 @@ namespace Umbraco.Web.Security
authOptions.Provider = new BackOfficeCookieAuthenticationProvider(userService, runtimeState, globalSettings, ioHelper, umbracoSettingsSection)
{
// TODO: SB: SecurityStampValidator
// Enables the application to validate the security stamp when the user
// logs in. This is a security feature which is used when you
// change a password or add an external login to your account.
/*OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<BackOfficeUserManager, BackOfficeIdentityUser, int>(
TimeSpan.FromMinutes(30),
(manager, user) => manager.GenerateUserIdentityAsync(user),
identity => identity.GetUserId<int>()),*/
OnValidateIdentity = UmbracoSecurityStampValidator
.OnValidateIdentity<BackOfficeUserManager, BackOfficeIdentityUser>(
TimeSpan.FromMinutes(3),
(signInManager, manager, user) => signInManager.CreateUserIdentityAsync(user),
identity => identity.GetUserId()),
};

View File

@@ -89,7 +89,7 @@ namespace Umbraco.Web.Security
options.Password.RequireDigit = passwordConfiguration.RequireDigit;
options.Password.RequireLowercase = passwordConfiguration.RequireLowercase;
options.Password.RequireUppercase = passwordConfiguration.RequireUppercase;
// Ensure Umbraco security stamp claim type is used
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
@@ -202,7 +202,7 @@ namespace Umbraco.Web.Security
protected virtual IPasswordHasher<T> GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration)
{
//we can use the user aware password hasher (which will be the default and preferred way)
return new UserAwarePasswordHasher2<T>(new PasswordSecurity(passwordConfiguration));
return new UserAwarePasswordHasher<T>(new PasswordSecurity(passwordConfiguration));
}
/// <summary>

View File

@@ -1,56 +0,0 @@
using System;
using System.ComponentModel;
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Configuration;
namespace Umbraco.Core.Security
{
/// <summary>
/// The <see cref="IIdentityMessageService"/> implementation for Umbraco
/// </summary>
public class EmailService : IIdentityMessageService
{
private readonly string _notificationEmailAddress;
private readonly IEmailSender _defaultEmailSender;
public EmailService(string notificationEmailAddress, IEmailSender defaultEmailSender)
{
_notificationEmailAddress = notificationEmailAddress;
_defaultEmailSender = defaultEmailSender;
}
public async Task SendAsync(IdentityMessage message)
{
var mailMessage = new MailMessage(
_notificationEmailAddress,
message.Destination,
message.Subject,
message.Body)
{
IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false
&& message.Body.Contains("<") && message.Body.Contains("</")
};
try
{
//check if it's a custom message and if so use it's own defined mail sender
var umbMsg = message as UmbracoEmailMessage;
if (umbMsg != null)
{
await umbMsg.MailSender.SendAsync(mailMessage);
}
else
{
await _defaultEmailSender.SendAsync(mailMessage);
}
}
finally
{
mailMessage.Dispose();
}
}
}
}

View File

@@ -1,17 +0,0 @@
using Microsoft.AspNet.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// A custom implementation for IdentityMessage that allows the customization of how an email is sent
/// </summary>
internal class UmbracoEmailMessage : IdentityMessage
{
public IEmailSender MailSender { get; private set; }
public UmbracoEmailMessage(IEmailSender mailSender)
{
MailSender = mailSender;
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Cookies;
using Umbraco.Core;
using Umbraco.Web.Models.Identity;
namespace Umbraco.Web.Security
{
/// <summary>
/// Adapted from Microsoft.AspNet.Identity.Owin.SecurityStampValidator
/// </summary>
public class UmbracoSecurityStampValidator
{
public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser>(
TimeSpan validateInterval,
Func<BackOfficeSignInManager, TManager, TUser, Task<ClaimsIdentity>> regenerateIdentityCallback,
Func<ClaimsIdentity, string> getUserIdCallback)
where TManager : BackOfficeUserManager<TUser>
where TUser : BackOfficeIdentityUser
{
if (getUserIdCallback == null) throw new ArgumentNullException(nameof(getUserIdCallback));
return async context =>
{
var currentUtc = DateTimeOffset.UtcNow;
if (context.Options != null && context.Options.SystemClock != null)
{
currentUtc = context.Options.SystemClock.UtcNow;
}
var issuedUtc = context.Properties.IssuedUtc;
// Only validate if enough time has elapsed
var validate = issuedUtc == null;
if (issuedUtc != null)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > validateInterval;
}
if (validate)
{
var manager = context.OwinContext.Get<TManager>();
var signInManager = context.OwinContext.GetBackOfficeSignInManager();
var userId = getUserIdCallback(context.Identity);
if (manager != null && userId != null)
{
var user = await manager.FindByIdAsync(userId);
var reject = true;
// Refresh the identity if the stamp matches, otherwise reject
if (user != null && manager.SupportsUserSecurityStamp)
{
var securityStamp = context.Identity.FindFirst(Constants.Web.SecurityStampClaimType)?.Value;
if (securityStamp == await manager.GetSecurityStampAsync(user))
{
reject = false;
// Regenerate fresh claims if possible and resign in
if (regenerateIdentityCallback != null)
{
var identity = await regenerateIdentityCallback.Invoke(signInManager, manager, user);
if (identity != null)
{
// Fix for regression where this value is not updated
// Setting it to null so that it is refreshed by the cookie middleware
context.Properties.IssuedUtc = null;
context.Properties.ExpiresUtc = null;
context.OwinContext.Authentication.SignIn(context.Properties, identity);
}
}
}
}
if (reject)
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
}
}
}
};
}
}
}

View File

@@ -4,12 +4,12 @@ using Umbraco.Web.Models.Identity;
namespace Umbraco.Web.Security
{
public class UserAwarePasswordHasher2<T> : IPasswordHasher<T>
public class UserAwarePasswordHasher<T> : IPasswordHasher<T>
where T : BackOfficeIdentityUser
{
private readonly PasswordSecurity _passwordSecurity;
public UserAwarePasswordHasher2(PasswordSecurity passwordSecurity)
public UserAwarePasswordHasher(PasswordSecurity passwordSecurity)
{
_passwordSecurity = passwordSecurity;
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -205,7 +205,6 @@
<Compile Include="Security\BackOfficeUserStore.cs" />
<Compile Include="Security\BackOfficeUserValidator.cs" />
<Compile Include="Security\ConfiguredPasswordValidator.cs" />
<Compile Include="Security\EmailService.cs" />
<Compile Include="Security\IUserSessionStore.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
<Compile Include="Security\MembershipProviderExtensions.cs" />
@@ -213,9 +212,9 @@
<Compile Include="Security\OwinDataProtectorTokenProvider.cs" />
<Compile Include="Security\PasswordSecurity.cs" />
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
<Compile Include="Security\UmbracoEmailMessage.cs" />
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
<Compile Include="Security\UserAwarePasswordHasher2.cs" />
<Compile Include="Security\UmbracoSecurityStampValidator.cs" />
<Compile Include="Security\UserAwarePasswordHasher.cs" />
<Compile Include="StringExtensions.cs" />
<Compile Include="Trees\TreeCollectionBuilder.cs" />
<Compile Include="UmbracoContext.cs" />