From 96d9d3bd21608f3076511a8a3828c357f49cd171 Mon Sep 17 00:00:00 2001 From: Scott Brady Date: Mon, 20 Apr 2020 20:14:36 +0100 Subject: [PATCH] Removed use of Microsoft.AspNet.Identity.Owin, but had to port across some OWIN extensions --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - .../Editors/BackOfficeController.cs | 4 +- .../Editors/BackOfficeServerVariables.cs | 1 + .../HtmlHelperBackOfficeExtensions.cs | 1 + src/Umbraco.Web/OwinExtensions.cs | 6 + .../Security/AppBuilderExtensions.cs | 40 +++++- .../AuthenticationManagerExtensions.cs | 30 ++++- .../Security/ExternalSignInAutoLinkOptions.cs | 10 +- .../Security/IdentityFactoryMiddleware.cs | 117 ++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 3 +- 10 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs 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 @@ +