diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index 1707813433..b36cd585df 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Security private readonly ILogger _logger; private readonly IOwinRequest _request; - public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) + public BackOfficeSignInManager(UserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) : base(userManager, authenticationManager) { if (logger == null) throw new ArgumentNullException("logger"); @@ -35,7 +35,7 @@ namespace Umbraco.Core.Security public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, ILogger logger) { return new BackOfficeSignInManager( - context.GetUserManager(), + context.GetBackOfficeUserManager(), context.Authentication, logger, context.Request); diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index 683b68fe56..8421d7f90c 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -15,6 +15,8 @@ namespace Umbraco.Core.Security /// public class BackOfficeUserManager : BackOfficeUserManager { + public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; + public BackOfficeUserManager(IUserStore store) : base(store) { diff --git a/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs b/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs new file mode 100644 index 0000000000..9dd5afd9da --- /dev/null +++ b/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + /// + /// This class is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked + /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this + /// class to resolve the 'real' type of the registered user manager + /// + /// + /// + internal class BackOfficeUserManagerMarker : IBackOfficeUserManagerMarker + where TManager : BackOfficeUserManager + where TUser : BackOfficeIdentityUser + { + public BackOfficeUserManager GetManager(IOwinContext owin) + { + var mgr = owin.Get() as BackOfficeUserManager; + if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager)); + return mgr; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/IBackOfficeUserManagerMarker.cs b/src/Umbraco.Core/Security/IBackOfficeUserManagerMarker.cs new file mode 100644 index 0000000000..0dd98cd57a --- /dev/null +++ b/src/Umbraco.Core/Security/IBackOfficeUserManagerMarker.cs @@ -0,0 +1,15 @@ +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + /// + /// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked + /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this + /// class to resolve the 'real' type of the registered user manager + /// + internal interface IBackOfficeUserManagerMarker + { + BackOfficeUserManager GetManager(IOwinContext owin); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/OwinExtensions.cs b/src/Umbraco.Core/Security/OwinExtensions.cs new file mode 100644 index 0000000000..251f008a8c --- /dev/null +++ b/src/Umbraco.Core/Security/OwinExtensions.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public static class OwinExtensions + { + /// + /// Gets the back office sign in manager out of OWIN + /// + /// + /// + public static BackOfficeSignInManager GetBackOfficeSignInManager(this IOwinContext owinContext) + { + var mgr = owinContext.Get(); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); + } + return mgr; + } + + /// + /// Gets the back office user manager out of OWIN + /// + /// + /// + /// + /// This is required because to extract the user manager we need to user a custom service since owin only deals in generics and + /// developers could register their own user manager types + /// + public static BackOfficeUserManager GetBackOfficeUserManager(this IOwinContext owinContext) + { + var marker = owinContext.Get(BackOfficeUserManager.OwinMarkerKey); + if (marker == null) throw new NullReferenceException("No " + typeof(IBackOfficeUserManagerMarker) + " has been registered with Owin which means that no Umbraco back office user manager has been registered"); + + var mgr = marker.GetManager(owinContext); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeUserManager)); + } + return mgr; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 46930e3665..762c531ea8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -507,11 +507,14 @@ + + + diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 1cfd0312c2..426adcf02f 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -15,6 +15,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models; @@ -38,41 +39,15 @@ namespace Umbraco.Web.Editors public class AuthenticationController : UmbracoApiController { - private BackOfficeUserManager _userManager; + private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; - - protected BackOfficeUserManager UserManager + protected BackOfficeUserManager UserManager { - get - { - if (_userManager == null) - { - var mgr = TryGetOwinContext().Result.GetUserManager(); - if (mgr == null) - { - throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeUserManager) + " from the " + typeof(IOwinContext) + " GetUserManager method"); - } - _userManager = mgr; - } - return _userManager; - } + get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } } - protected BackOfficeSignInManager SignInManager { - get - { - if (_signInManager == null) - { - var mgr = TryGetOwinContext().Result.Get(); - if (mgr == null) - { - throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); - } - _signInManager = mgr; - } - return _signInManager; - } + get { return _signInManager ?? (_signInManager = TryGetOwinContext().Result.GetBackOfficeSignInManager()); } } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 9049aafac2..b505387257 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -39,6 +39,7 @@ using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi.Filters; using Umbraco.Web.WebServices; using Umbraco.Core.Services; +using Umbraco.Web.Security; using Action = umbraco.BusinessLogic.Actions.Action; using Constants = Umbraco.Core.Constants; @@ -51,7 +52,7 @@ namespace Umbraco.Web.Editors [DisableClientCache] public class BackOfficeController : UmbracoController { - private BackOfficeUserManager _userManager; + private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; private const string TokenExternalSignInError = "ExternalSignInError"; @@ -60,12 +61,11 @@ namespace Umbraco.Web.Editors protected BackOfficeSignInManager SignInManager { - get { return _signInManager ?? (_signInManager = OwinContext.Get()); } + get { return _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); } } - - protected BackOfficeUserManager UserManager + protected BackOfficeUserManager UserManager { - get { return _userManager ?? (_userManager = OwinContext.GetUserManager()); } + get { return _userManager ?? (_userManager = OwinContext.GetBackOfficeUserManager()); } } protected IAuthenticationManager AuthenticationManager diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index ef1f213c89..c69b483a3a 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -74,6 +74,8 @@ namespace Umbraco.Web.Security.Identity appContext.Services.UserService, appContext.Services.ExternalLoginService, userMembershipProvider)); + + app.SetBackOfficeUserManagerType(); //Create a sign in manager per request app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger())); @@ -102,6 +104,8 @@ namespace Umbraco.Web.Security.Identity customUserStore, userMembershipProvider)); + app.SetBackOfficeUserManagerType(); + //Create a sign in manager per request app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } @@ -124,8 +128,11 @@ namespace Umbraco.Web.Security.Identity //Configure Umbraco user manager to be created per request app.CreatePerOwinContext(userManager); + app.SetBackOfficeUserManagerType(); + //Create a sign in manager per request - app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); + app.CreatePerOwinContext( + (options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -210,6 +217,35 @@ namespace Umbraco.Web.Security.Identity return app; } + private static bool _markerSet = false; + + /// + /// This registers the exact type of the user manager in owin so we can extract it + /// when required in order to extract the user manager instance + /// + /// + /// + /// + /// + /// This is required because a developer can specify a custom user manager and due to generic types the key name will registered + /// differently in the owin context + /// + private static void SetBackOfficeUserManagerType(this IAppBuilder app) + where TManager : BackOfficeUserManager + where TUser : BackOfficeIdentityUser + { + if (_markerSet) throw new InvalidOperationException("The back office user manager marker has already been set, only one back office user manager can be configured"); + + //on each request set the user manager getter - + // this is required purely because Microsoft.Owin.IOwinContext is super inflexible with it's Get since it can only be + // a generic strongly typed instance + app.Use((context, func) => + { + context.Set(BackOfficeUserManager.OwinMarkerKey, new BackOfficeUserManagerMarker()); + return func(); + }); + } + private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, ApplicationContext appContext, PipelineStage stage) { if (app == null) diff --git a/src/Umbraco.Web/Security/OwinExtensions.cs b/src/Umbraco.Web/Security/OwinExtensions.cs index d1f1fbc1ed..8217dc7f6c 100644 --- a/src/Umbraco.Web/Security/OwinExtensions.cs +++ b/src/Umbraco.Web/Security/OwinExtensions.cs @@ -1,11 +1,16 @@ +using System; using System.Web; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Umbraco.Web.Security.Identity; namespace Umbraco.Web.Security { internal static class OwinExtensions - { + { /// /// Nasty little hack to get httpcontextbase from an owin context diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index eb0a1eb66a..f0c0861059 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Security; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using umbraco.businesslogic.Exceptions; +using Umbraco.Core.Models.Identity; using Umbraco.Web.Models.ContentEditing; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using User = umbraco.BusinessLogic.User; @@ -98,23 +99,11 @@ namespace Umbraco.Web.Security } } - private BackOfficeUserManager _userManager; - protected BackOfficeUserManager UserManager + private BackOfficeUserManager _userManager; + protected BackOfficeUserManager UserManager { - get - { - if (_userManager == null) - { - var mgr = _httpContext.GetOwinContext().GetUserManager(); - if (mgr == null) - { - throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeUserManager) + " from the " + typeof(IOwinContext) + " GetUserManager method"); - } - _userManager = mgr; - } - return _userManager; - } - } + get { return _userManager ?? (_userManager = _httpContext.GetOwinContext().GetBackOfficeUserManager()); } + } /// /// Logs a user in. diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs index cb6bd451b0..83dd5b2dc8 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -11,7 +11,9 @@ using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using umbraco.interfaces; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; +using Umbraco.Web.Security; namespace umbraco.presentation.webservices { @@ -67,7 +69,7 @@ namespace umbraco.presentation.webservices // to ensure that the lockout policies are applied, though because we are not authenticating // the user with their real password, we need to do this a bit manually. - var userMgr = Context.GetOwinContext().GetUserManager(); + var userMgr = Context.GetOwinContext().GetBackOfficeUserManager(); var user = ApplicationContext.Current.Services.UserService.GetByUsername(login); if (user == null) return false;