diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index f450c2300c..76af3252b4 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -88,7 +88,6 @@
-
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs
index d5c11ca871..684d19be81 100644
--- a/src/Umbraco.Web/Editors/BackOfficeController.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeController.cs
@@ -374,7 +374,7 @@ namespace Umbraco.Web.Editors
return await ExternalSignInAsync(loginInfo, externalSignInResponse);
}
- private async Task ExternalSignInAsync(ExternalLoginInfo2 loginInfo, Func response)
+ private async Task ExternalSignInAsync(ExternalLoginInfo loginInfo, Func response)
{
if (loginInfo == null) throw new ArgumentNullException("loginInfo");
if (response == null) throw new ArgumentNullException("response");
@@ -437,7 +437,7 @@ namespace Umbraco.Web.Editors
return response();
}
- private async Task AutoLinkAndSignInExternalAccount(ExternalLoginInfo2 loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
+ private async Task AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
{
if (autoLinkOptions == null)
return false;
diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs
index 058f787324..4ede63b294 100644
--- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs
@@ -21,6 +21,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Runtime;
using Umbraco.Core.WebAssets;
+using Umbraco.Web.Security;
namespace Umbraco.Web.Editors
{
diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs
index eefb02ef6d..e680f155ca 100644
--- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs
+++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs
@@ -15,6 +15,7 @@ using Umbraco.Web.Composing;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Models;
+using Umbraco.Web.Security;
using Umbraco.Web.Trees;
using Umbraco.Web.WebAssets;
diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs
index 6905a5373a..52c1187707 100644
--- a/src/Umbraco.Web/OwinExtensions.cs
+++ b/src/Umbraco.Web/OwinExtensions.cs
@@ -90,6 +90,12 @@ namespace Umbraco.Web
return context.Get(GetKey(typeof(T)));
}
+ public static IOwinContext Set(this IOwinContext context, T value)
+ {
+ if (context == null) throw new ArgumentNullException(nameof(context));
+ return context.Set(GetKey(typeof(T)), value);
+ }
+
private static string GetKey(Type t)
{
return "AspNet.Identity.Owin:" + t.AssemblyQualifiedName;
diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs
index b7218a3723..9c929704e5 100644
--- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs
+++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs
@@ -2,6 +2,7 @@
using System.Threading;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Owin;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
@@ -14,11 +15,8 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Hosting;
-using Umbraco.Core.IO;
using Umbraco.Core.Mapping;
-using Umbraco.Core.Models.Identity;
using Umbraco.Net;
-using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.Identity;
@@ -421,5 +419,41 @@ namespace Umbraco.Web.Security
return authOptions;
}
+ public static IAppBuilder CreatePerOwinContext(this IAppBuilder app, Func createCallback)
+ where T : class, IDisposable
+ {
+ return CreatePerOwinContext(app, (options, context) => createCallback());
+ }
+
+ public static IAppBuilder CreatePerOwinContext(this IAppBuilder app,
+ Func, IOwinContext, T> createCallback) where T : class, IDisposable
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException("app");
+ }
+ return app.CreatePerOwinContext(createCallback, (options, instance) => instance.Dispose());
+ }
+
+ public static IAppBuilder CreatePerOwinContext(this IAppBuilder app,
+ Func, IOwinContext, T> createCallback,
+ Action, T> disposeCallback) where T : class, IDisposable
+ {
+ if (app == null) throw new ArgumentNullException(nameof(app));
+ if (createCallback == null) throw new ArgumentNullException(nameof(createCallback));
+ if (disposeCallback == null) throw new ArgumentNullException(nameof(disposeCallback));
+
+ app.Use(typeof(IdentityFactoryMiddleware>),
+ new IdentityFactoryOptions
+ {
+ DataProtectionProvider = app.GetDataProtectionProvider(),
+ Provider = new IdentityFactoryProvider
+ {
+ OnCreate = createCallback,
+ OnDispose = disposeCallback
+ }
+ });
+ return app;
+ }
}
}
diff --git a/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs b/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs
index d648d9ce78..84b9fcbe0b 100644
--- a/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs
+++ b/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs
@@ -1,14 +1,16 @@
using System;
+using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
+using UserLoginInfo = Microsoft.AspNetCore.Identity.UserLoginInfo;
namespace Umbraco.Web.Security
{
public static class AuthenticationManagerExtensions
{
- private static ExternalLoginInfo2 GetExternalLoginInfo(AuthenticateResult result)
+ private static ExternalLoginInfo GetExternalLoginInfo(AuthenticateResult result)
{
if (result == null || result.Identity == null)
{
@@ -27,7 +29,7 @@ namespace Umbraco.Web.Security
}
var email = result.Identity.FindFirst(ClaimTypes.Email)?.Value;
- return new ExternalLoginInfo2
+ return new ExternalLoginInfo
{
ExternalIdentity = result.Identity,
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value, idClaim.Issuer),
@@ -47,7 +49,7 @@ namespace Umbraco.Web.Security
/// dictionary
///
///
- public static async Task GetExternalLoginInfoAsync(this IAuthenticationManager manager,
+ public static async Task GetExternalLoginInfoAsync(this IAuthenticationManager manager,
string authenticationType,
string xsrfKey, string expectedValue)
{
@@ -74,7 +76,7 @@ namespace Umbraco.Web.Security
///
///
///
- public static async Task GetExternalLoginInfoAsync(this IAuthenticationManager manager, string authenticationType)
+ public static async Task GetExternalLoginInfoAsync(this IAuthenticationManager manager, string authenticationType)
{
if (manager == null)
{
@@ -82,9 +84,25 @@ namespace Umbraco.Web.Security
}
return GetExternalLoginInfo(await manager.AuthenticateAsync(authenticationType));
}
+
+ public static IEnumerable GetExternalAuthenticationTypes(this IAuthenticationManager manager)
+ {
+ if (manager == null) throw new ArgumentNullException(nameof(manager));
+ return manager.GetAuthenticationTypes(d => d.Properties != null && d.Properties.ContainsKey("Caption"));
+ }
+
+ public static ClaimsIdentity CreateTwoFactorRememberBrowserIdentity(this IAuthenticationManager manager, string userId)
+ {
+ if (manager == null) throw new ArgumentNullException(nameof(manager));
+
+ var rememberBrowserIdentity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
+ rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
+
+ return rememberBrowserIdentity;
+ }
}
- public class ExternalLoginInfo2
+ public class ExternalLoginInfo
{
/// Associated login data
public UserLoginInfo Login { get; set; }
diff --git a/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs
index fd6446f5ab..bd0d3b98a4 100644
--- a/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs
+++ b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs
@@ -31,13 +31,13 @@ namespace Umbraco.Web.Security
///
/// A callback executed during account auto-linking and before the user is persisted
///
- public Action OnAutoLinking { get; set; }
+ public Action OnAutoLinking { get; set; }
///
/// A callback executed during every time a user authenticates using an external login.
/// returns a boolean indicating if sign in should continue or not.
///
- public Func OnExternalLogin { get; set; }
+ public Func OnExternalLogin { get; set; }
/// B
@@ -46,7 +46,7 @@ namespace Umbraco.Web.Security
///
///
///
- public string[] GetDefaultUserGroups(IUmbracoContext umbracoContext, ExternalLoginInfo2 loginInfo)
+ public string[] GetDefaultUserGroups(IUmbracoContext umbracoContext, ExternalLoginInfo loginInfo)
{
return _defaultUserGroups;
}
@@ -59,7 +59,7 @@ namespace Umbraco.Web.Security
///
/// For public auth providers this should always be false!!!
///
- public bool ShouldAutoLinkExternalAccount(IUmbracoContext umbracoContext, ExternalLoginInfo2 loginInfo)
+ public bool ShouldAutoLinkExternalAccount(IUmbracoContext umbracoContext, ExternalLoginInfo loginInfo)
{
return _autoLinkExternalAccount;
}
@@ -69,7 +69,7 @@ namespace Umbraco.Web.Security
///
/// The default Culture to use for auto-linking users
///
- public string GetDefaultCulture(IUmbracoContext umbracoContext, ExternalLoginInfo2 loginInfo)
+ public string GetDefaultCulture(IUmbracoContext umbracoContext, ExternalLoginInfo loginInfo)
{
return _defaultCulture;
}
diff --git a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs
new file mode 100644
index 0000000000..ddd0703878
--- /dev/null
+++ b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Owin;
+using Microsoft.Owin.Security.DataProtection;
+
+namespace Umbraco.Web.Security
+{
+ ///
+ /// Adapted from Microsoft.AspNet.Identity.Owin.IdentityFactoryMiddleware
+ ///
+ public class IdentityFactoryMiddleware : OwinMiddleware
+ where TResult : class, IDisposable
+ where TOptions : IdentityFactoryOptions
+ {
+ ///
+ /// Constructor
+ ///
+ /// The next middleware in the OWIN pipeline to invoke
+ /// Configuration options for the middleware
+ public IdentityFactoryMiddleware(OwinMiddleware next, TOptions options)
+ : base(next)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+ if (options.Provider == null)
+ {
+ throw new ArgumentNullException("options.Provider");
+ }
+ Options = options;
+ }
+
+ ///
+ /// Configuration options
+ ///
+ public TOptions Options { get; private set; }
+
+ ///
+ /// Create an object using the Options.Provider, storing it in the OwinContext and then disposes the object when finished
+ ///
+ ///
+ ///
+ public override async Task Invoke(IOwinContext context)
+ {
+ var instance = Options.Provider.Create(Options, context);
+ try
+ {
+ context.Set(instance);
+ if (Next != null)
+ {
+ await Next.Invoke(context);
+ }
+ }
+ finally
+ {
+ Options.Provider.Dispose(Options, instance);
+ }
+ }
+ }
+
+ public class IdentityFactoryOptions where T : class, IDisposable
+ {
+ ///
+ /// Used to configure the data protection provider
+ ///
+ public IDataProtectionProvider DataProtectionProvider { get; set; }
+
+ ///
+ /// Provider used to Create and Dispose objects
+ ///
+ public IdentityFactoryProvider Provider { get; set; }
+ }
+
+ public class IdentityFactoryProvider where T : class, IDisposable
+ {
+ ///
+ /// Constructor
+ ///
+ public IdentityFactoryProvider()
+ {
+ OnDispose = (options, instance) => { };
+ OnCreate = (options, context) => null;
+ }
+
+ ///
+ /// A delegate assigned to this property will be invoked when the related method is called
+ ///
+ public Func, IOwinContext, T> OnCreate { get; set; }
+
+ ///
+ /// A delegate assigned to this property will be invoked when the related method is called
+ ///
+ public Action, T> OnDispose { get; set; }
+
+ ///
+ /// Calls the OnCreate Delegate
+ ///
+ ///
+ ///
+ ///
+ public virtual T Create(IdentityFactoryOptions options, IOwinContext context)
+ {
+ return OnCreate(options, context);
+ }
+
+ ///
+ /// Calls the OnDispose delegate
+ ///
+ ///
+ ///
+ public virtual void Dispose(IdentityFactoryOptions options, T instance)
+ {
+ OnDispose(options, instance);
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 11923b7b55..81e011fe32 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -77,7 +77,7 @@
-
+
@@ -147,6 +147,7 @@
+