diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index 7199f414b1..7630dad842 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -33,7 +33,7 @@
-
+
diff --git a/src/Umbraco.Web/Models/Identity/IdentityUser.cs b/src/Umbraco.Web/Models/Identity/IdentityUser.cs
index df104fcafe..7bd077e879 100644
--- a/src/Umbraco.Web/Models/Identity/IdentityUser.cs
+++ b/src/Umbraco.Web/Models/Identity/IdentityUser.cs
@@ -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
///
- public class IdentityUser : IUser
+ public class IdentityUser
where TLogin : IIdentityUserLogin
//NOTE: Making our role id a string
where TRole : IdentityUserRole
diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs
index 2c8129a521..20105cabe8 100644
--- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs
+++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs
@@ -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(
- TimeSpan.FromMinutes(30),
- (manager, user) => manager.GenerateUserIdentityAsync(user),
- identity => identity.GetUserId()),*/
+ OnValidateIdentity = UmbracoSecurityStampValidator
+ .OnValidateIdentity(
+ TimeSpan.FromMinutes(3),
+ (signInManager, manager, user) => signInManager.CreateUserIdentityAsync(user),
+ identity => identity.GetUserId()),
};
diff --git a/src/Umbraco.Web/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs
index b9d72ce0c1..ab69349afc 100644
--- a/src/Umbraco.Web/Security/BackOfficeUserManager.cs
+++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs
@@ -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 GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration)
{
//we can use the user aware password hasher (which will be the default and preferred way)
- return new UserAwarePasswordHasher2(new PasswordSecurity(passwordConfiguration));
+ return new UserAwarePasswordHasher(new PasswordSecurity(passwordConfiguration));
}
///
diff --git a/src/Umbraco.Web/Security/EmailService.cs b/src/Umbraco.Web/Security/EmailService.cs
deleted file mode 100644
index e6454544ab..0000000000
--- a/src/Umbraco.Web/Security/EmailService.cs
+++ /dev/null
@@ -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
-{
- ///
- /// The implementation for Umbraco
- ///
- 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();
- }
- }
- }
-}
diff --git a/src/Umbraco.Web/Security/UmbracoEmailMessage.cs b/src/Umbraco.Web/Security/UmbracoEmailMessage.cs
deleted file mode 100644
index 0a81b2299b..0000000000
--- a/src/Umbraco.Web/Security/UmbracoEmailMessage.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Microsoft.AspNet.Identity;
-
-namespace Umbraco.Core.Security
-{
- ///
- /// A custom implementation for IdentityMessage that allows the customization of how an email is sent
- ///
- internal class UmbracoEmailMessage : IdentityMessage
- {
- public IEmailSender MailSender { get; private set; }
-
- public UmbracoEmailMessage(IEmailSender mailSender)
- {
- MailSender = mailSender;
- }
- }
-}
diff --git a/src/Umbraco.Web/Security/UmbracoSecurityStampValidator.cs b/src/Umbraco.Web/Security/UmbracoSecurityStampValidator.cs
new file mode 100644
index 0000000000..f506a64fba
--- /dev/null
+++ b/src/Umbraco.Web/Security/UmbracoSecurityStampValidator.cs
@@ -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
+{
+ ///
+ /// Adapted from Microsoft.AspNet.Identity.Owin.SecurityStampValidator
+ ///
+ public class UmbracoSecurityStampValidator
+ {
+ public static Func OnValidateIdentity(
+ TimeSpan validateInterval,
+ Func> regenerateIdentityCallback,
+ Func getUserIdCallback)
+ where TManager : BackOfficeUserManager
+ 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();
+ 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);
+ }
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Security/UserAwarePasswordHasher2.cs b/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs
similarity index 91%
rename from src/Umbraco.Web/Security/UserAwarePasswordHasher2.cs
rename to src/Umbraco.Web/Security/UserAwarePasswordHasher.cs
index 9a44533427..d804ef0ae4 100644
--- a/src/Umbraco.Web/Security/UserAwarePasswordHasher2.cs
+++ b/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs
@@ -4,12 +4,12 @@ using Umbraco.Web.Models.Identity;
namespace Umbraco.Web.Security
{
- public class UserAwarePasswordHasher2 : IPasswordHasher
+ public class UserAwarePasswordHasher : IPasswordHasher
where T : BackOfficeIdentityUser
{
private readonly PasswordSecurity _passwordSecurity;
- public UserAwarePasswordHasher2(PasswordSecurity passwordSecurity)
+ public UserAwarePasswordHasher(PasswordSecurity passwordSecurity)
{
_passwordSecurity = passwordSecurity;
}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 703a52ccdc..9a2896a53a 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -1,4 +1,4 @@
-
+
@@ -205,7 +205,6 @@
-
@@ -213,9 +212,9 @@
-
-
+
+