diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 0d7c2f41da..13dee96b97 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -26,6 +26,7 @@ public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken"; + public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie"; /// /// The prefix used for external identity providers for their authentication type diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index def71a8982..7e30d4a81e 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email)) .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id)) .ForMember(user => user.LockoutEnabled, expression => expression.MapFrom(user => user.IsLockedOut)) + //Users currently are locked out for an infinite time, we do not support timed lock outs currently .ForMember(user => user.LockoutEndDateUtc, expression => expression.UseValue(DateTime.MaxValue.ToUniversalTime())) .ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username)) .ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs new file mode 100644 index 0000000000..fc7ecf5330 --- /dev/null +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -0,0 +1,72 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public class BackOfficeSignInManager : SignInManager + { + public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager) + : base(userManager, authenticationManager) + { + AuthenticationType = Constants.Security.BackOfficeAuthenticationType; + } + + public override Task CreateUserIdentityAsync(BackOfficeIdentityUser user) + { + return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); + } + + public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context) + { + return new BackOfficeSignInManager(context.GetUserManager(), context.Authentication); + } + + /// + /// Creates a user identity and then signs the identity using the AuthenticationManager + /// + /// + /// + /// + /// + public override async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, bool rememberBrowser) + { + var userIdentity = await CreateUserIdentityAsync(user); + + // Clear any partial cookies from external or two factor partial sign ins + AuthenticationManager.SignOut( + Constants.Security.BackOfficeExternalAuthenticationType, + Constants.Security.BackOfficeTwoFactorAuthenticationType); + + var nowUtc = DateTime.Now.ToUniversalTime(); + + if (rememberBrowser) + { + var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id)); + AuthenticationManager.SignIn(new AuthenticationProperties() + { + IsPersistent = isPersistent, + AllowRefresh = true, + IssuedUtc = nowUtc, + ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) + }, userIdentity, rememberBrowserIdentity); + } + else + { + AuthenticationManager.SignIn(new AuthenticationProperties() + { + IsPersistent = isPersistent, + AllowRefresh = true, + IssuedUtc = nowUtc, + ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) + }, userIdentity); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index def46b7556..798c124880 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -1,17 +1,16 @@ using System; using System.Linq; -using System.Security.Claims; using System.Text; -using System.Threading.Tasks; using System.Web.Security; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; -using Microsoft.Owin; using Umbraco.Core.Models.Identity; using Umbraco.Core.Services; namespace Umbraco.Core.Security { + + /// /// Default back office user manager /// @@ -89,6 +88,7 @@ namespace Umbraco.Core.Security RequireDigit = false, RequireLowercase = false, RequireUppercase = false + //TODO: Do we support the old regex match thing that membership providers used? }; //use a custom hasher based on our membership provider @@ -100,6 +100,9 @@ namespace Umbraco.Core.Security manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); } + manager.UserLockoutEnabledByDefault = true; + manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts; + //custom identity factory for creating the identity object for which we auth against in the back office manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); @@ -149,13 +152,9 @@ namespace Umbraco.Core.Security get { return false; } } - //TODO: Support this - public override bool SupportsUserLockout - { - get { return false; } - } - - //TODO: Support this + /// + /// Developers will need to override this to support custom 2 factor auth + /// public override bool SupportsUserTwoFactor { get { return false; } diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index cf433b729c..5745abb02e 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Web.Security; using AutoMapper; using Microsoft.AspNet.Identity; +using Microsoft.Owin; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -17,15 +18,12 @@ namespace Umbraco.Core.Security IUserEmailStore, IUserLoginStore, IUserRoleStore, - IUserSecurityStampStore - + IUserSecurityStampStore, + IUserLockoutStore, + IUserTwoFactorStore + //TODO: This would require additional columns/tables for now people will need to implement this on their own //IUserPhoneNumberStore, - //IUserTwoFactorStore, - - //TODO: This will require additional columns/tables - //IUserLockoutStore - //TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation //IQueryableUserStore { @@ -506,6 +504,117 @@ namespace Umbraco.Core.Security return user; } + /// + /// Sets whether two factor authentication is enabled for the user + /// + /// + /// + public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled) + { + user.TwoFactorEnabled = false; + return Task.FromResult(0); + } + + /// + /// Returns whether two factor authentication is enabled for the user + /// + /// + /// + public virtual Task GetTwoFactorEnabledAsync(BackOfficeIdentityUser user) + { + return Task.FromResult(false); + } + + #region IUserLockoutStore + + /// + /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out. + /// + /// + /// + /// + /// Currently we do not suport a timed lock out, when they are locked out, an admin will have to reset the status + /// + public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user) + { + if (user == null) throw new ArgumentNullException("user"); + + return user.LockoutEndDateUtc.HasValue + ? Task.FromResult(new DateTimeOffset(user.LockoutEndDateUtc.Value, TimeSpan.FromHours(0))) + : Task.FromResult(DateTimeOffset.MaxValue); + } + + /// + /// Locks a user out until the specified end date (set to a past date, to unlock a user) + /// + /// + /// + public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd) + { + if (user == null) throw new ArgumentNullException("user"); + user.LockoutEndDateUtc = lockoutEnd.UtcDateTime; + return Task.FromResult(0); + } + + /// + /// Used to record when an attempt to access the user has failed + /// + /// + /// + public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user) + { + if (user == null) throw new ArgumentNullException("user"); + user.AccessFailedCount++; + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Used to reset the access failed count, typically after the account is successfully accessed + /// + /// + /// + public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user) + { + if (user == null) throw new ArgumentNullException("user"); + throw new NotImplementedException(); + } + + /// + /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is + /// verified or the account is locked out. + /// + /// + /// + public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Returns whether the user can be locked out. + /// + /// + /// + public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return Task.FromResult(user.LockoutEnabled); + } + + /// + /// Sets whether the user can be locked out. + /// + /// + /// + public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled) + { + if (user == null) throw new ArgumentNullException("user"); + user.LockoutEnabled = enabled; + return Task.FromResult(0); + } + #endregion + private bool UpdateMemberProperties(Models.Membership.IUser user, BackOfficeIdentityUser identityUser) { var anythingChanged = false; @@ -579,10 +688,13 @@ namespace Umbraco.Core.Security return anythingChanged; } + private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(GetType().Name); } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 007d12500b..c6883c6a94 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -58,11 +58,11 @@ False - ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll False - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll False @@ -428,6 +428,7 @@ + diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 6ffcf860e6..9edfbb097e 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -3,8 +3,8 @@ - - + + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e2b2d7b924..454250855b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -151,14 +151,14 @@ ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll True False - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll - + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll True @@ -174,7 +174,7 @@ False ..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - + False ..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll @@ -224,7 +224,7 @@ - + False @@ -343,7 +343,7 @@ Properties\SolutionInfo.cs - + loadStarterKits.ascx ASPXCodeBehind diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 12afdc860d..a81a154970 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -9,8 +9,8 @@ - - + + diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index e3e2abf42f..95f985148c 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -31,6 +31,7 @@ using Microsoft.AspNet.Identity.Owin; using Umbraco.Core.Logging; using Newtonsoft.Json.Linq; using Umbraco.Core.Models.Identity; +using Umbraco.Web.Security.Identity; using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors @@ -47,6 +48,7 @@ namespace Umbraco.Web.Editors { private BackOfficeUserManager _userManager; + private BackOfficeSignInManager _signInManager; protected BackOfficeUserManager UserManager { @@ -64,6 +66,24 @@ namespace Umbraco.Web.Editors return _userManager; } } + + 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; + } + } + [WebApi.UmbracoAuthorize] [ValidateAngularAntiForgeryToken] @@ -76,7 +96,7 @@ namespace Umbraco.Web.Editors if (result.Succeeded) { var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - await SignInAsync(user, isPersistent: false); + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); return Request.CreateResponse(HttpStatusCode.OK); } else @@ -146,36 +166,73 @@ namespace Umbraco.Web.Editors if (http.Success == false) throw new InvalidOperationException("This method requires that an HttpContext be active"); - if (UmbracoContext.Security.ValidateBackOfficeCredentials(loginModel.Username, loginModel.Password)) + var result = await SignInManager.PasswordSignInAsync( + loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); + + switch (result) { - //get the user - var user = Security.GetBackOfficeUser(loginModel.Username); - var userDetail = Mapper.Map(user); + case SignInStatus.Success: - //create a response with the userDetail object - var response = Request.CreateResponse(HttpStatusCode.OK, userDetail); + //get the user + var user = Security.GetBackOfficeUser(loginModel.Username); + var userDetail = Mapper.Map(user); - //set the response cookies with the ticket (NOTE: This needs to be done with the custom webapi extension because - // we cannot mix HttpContext.Response.Cookies and the way WebApi/Owin work) - var ticket = response.UmbracoLoginWebApi(user); + //create a response with the userDetail object + var response = Request.CreateResponse(HttpStatusCode.OK, userDetail); - //Identity does some of it's own checks as well so we need to use it's sign in process too... this will essentially re-create the - // ticket/cookie above but we need to create the ticket now so we can assign the Current Thread User/IPrinciple below - await SignInAsync(Mapper.Map(user), isPersistent: true); - //This ensure the current principal is set, otherwise any logic executing after this wouldn't actually be authenticated - http.Result.AuthenticateCurrentRequest(ticket, false); - - //update the userDetail and set their remaining seconds - userDetail.SecondsUntilTimeout = ticket.GetRemainingAuthSeconds(); + //set the response cookies with the ticket (NOTE: This needs to be done with the custom webapi extension because + // we cannot mix HttpContext.Response.Cookies and the way WebApi/Owin work) + var ticket = response.UmbracoLoginWebApi(user); - return response; + //This ensure the current principal is set, otherwise any logic executing after this wouldn't actually be authenticated + http.Result.AuthenticateCurrentRequest(ticket, false); + + //update the userDetail and set their remaining seconds + userDetail.SecondsUntilTimeout = ticket.GetRemainingAuthSeconds(); + + return response; + + case SignInStatus.RequiresVerification: + + var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; + if (twofactorOptions == null) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.BadRequest, + "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); + } + + var twofactorView = twofactorOptions.GetTwoFactorView( + TryGetOwinContext().Result, + UmbracoContext, + loginModel.Username); + + if (twofactorView.IsNullOrWhiteSpace()) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.BadRequest, + typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); + } + + //create a with information to display a custom two factor send code view + var verifyResponse = Request.CreateResponse(HttpStatusCode.OK, new + { + twoFactorView = twofactorView + }); + + return verifyResponse; + + case SignInStatus.LockedOut: + case SignInStatus.Failure: + default: + //return BadRequest (400), we don't want to return a 401 because that get's intercepted + // by our angular helper because it thinks that we need to re-perform the request once we are + // authorized and we don't want to return a 403 because angular will show a warning msg indicating + // that the user doesn't have access to perform this function, we just want to return a normal invalid msg. + throw new HttpResponseException(HttpStatusCode.BadRequest); } - - //return BadRequest (400), we don't want to return a 401 because that get's intercepted - // by our angular helper because it thinks that we need to re-perform the request once we are - // authorized and we don't want to return a 403 because angular will show a warning msg indicating - // that the user doesn't have access to perform this function, we just want to return a normal invalid msg. - throw new HttpResponseException(HttpStatusCode.BadRequest); } @@ -199,24 +256,5 @@ namespace Umbraco.Web.Editors } } - private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent) - { - var owinContext = TryGetOwinContext().Result; - - owinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType); - - var nowUtc = DateTime.Now.ToUniversalTime(); - - owinContext.Authentication.SignIn( - new AuthenticationProperties() - { - IsPersistent = isPersistent, - AllowRefresh = true, - IssuedUtc = nowUtc, - ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) - }, - await user.GenerateUserIdentityAsync(UserManager)); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index ad1e4ea571..46dc4df910 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -52,12 +52,23 @@ namespace Umbraco.Web.Editors public class BackOfficeController : UmbracoController { private BackOfficeUserManager _userManager; + private BackOfficeSignInManager _signInManager; + + protected BackOfficeSignInManager SignInManager + { + get { return _signInManager ?? (_signInManager = OwinContext.Get()); } + } protected BackOfficeUserManager UserManager { get { return _userManager ?? (_userManager = OwinContext.GetUserManager()); } } + protected IAuthenticationManager AuthenticationManager + { + get { return OwinContext.Authentication; } + } + /// /// Render the default view /// @@ -450,7 +461,7 @@ namespace Umbraco.Web.Editors var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync( Core.Constants.Security.BackOfficeExternalAuthenticationType); - if (loginInfo == null) + if (loginInfo == null || loginInfo.ExternalIdentity.IsAuthenticated == false) { return defaultResponse(); } @@ -475,7 +486,7 @@ namespace Umbraco.Web.Editors // that the ticket is created and stored and that the user is logged in. //sign in - await SignInAsync(user, isPersistent: false); + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } else { @@ -537,8 +548,7 @@ namespace Umbraco.Web.Editors } else { - //var userMembershipProvider = global::Umbraco.Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - + var autoLinkUser = new BackOfficeIdentityUser() { Email = loginInfo.Email, @@ -548,6 +558,13 @@ namespace Umbraco.Web.Editors Culture = autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo), UserName = loginInfo.Email }; + + //call the callback if one is assigned + if (autoLinkOptions.OnAutoLinking != null) + { + autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo); + } + var userCreationResult = await UserManager.CreateAsync(autoLinkUser); if (userCreationResult.Succeeded == false) @@ -571,14 +588,8 @@ namespace Umbraco.Web.Editors } else { - - //Ok, we're all linked up! Assign the auto-link options to a ViewBag property, this can be used - // in the view to render a custom view (AutoLinkExternalAccountView) if required, which will allow - // a developer to display a custom angular view to prompt the user for more information if required. - ViewBag.ExternalSignInAutoLinkOptions = autoLinkOptions; - //sign in - await SignInAsync(autoLinkUser, isPersistent: false); + await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false); } } } @@ -591,28 +602,6 @@ namespace Umbraco.Web.Editors return false; } - - private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent) - { - OwinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType); - - var nowUtc = DateTime.Now.ToUniversalTime(); - - OwinContext.Authentication.SignIn( - new AuthenticationProperties() - { - IsPersistent = isPersistent, - AllowRefresh = true, - IssuedUtc = nowUtc, - ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) - }, - await user.GenerateUserIdentityAsync(UserManager)); - } - - private IAuthenticationManager AuthenticationManager - { - get { return OwinContext.Authentication; } - } /// /// Returns the server variables regarding the application state diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index e3f58bb69e..75e628851e 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -47,10 +47,10 @@ namespace Umbraco.Web "; return html.Raw(str); - } + } /// - /// Used to render the script tag that will pass in the angular externalLoginInfo service on page load + /// Used to render the script that will pass in the angular externalLoginInfo service on page load /// /// /// @@ -67,10 +67,6 @@ namespace Umbraco.Web }) .ToArray(); - - //define a callback that is executed when we bootstrap angular, this is used to inject angular values - //with server side info - var sb = new StringBuilder(); sb.AppendLine(); sb.AppendLine(@"var errors = [];"); diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index a9a884a2e7..58a77d91cf 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -55,6 +55,9 @@ namespace Umbraco.Web.Security.Identity appContext.Services.UserService, appContext.Services.ExternalLoginService, userMembershipProvider)); + + //Create a sign in manager per request + app.CreatePerOwinContext(BackOfficeSignInManager.Create); } /// @@ -82,6 +85,9 @@ namespace Umbraco.Web.Security.Identity options, customUserStore, userMembershipProvider)); + + //Create a sign in manager per request + app.CreatePerOwinContext(BackOfficeSignInManager.Create); } /// @@ -104,6 +110,9 @@ namespace Umbraco.Web.Security.Identity //Configure Umbraco user manager to be created per request app.CreatePerOwinContext(userManager); + + //Create a sign in manager per request + app.CreatePerOwinContext(BackOfficeSignInManager.Create); } /// diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs index 9fb9a88a45..832f0b3a30 100644 --- a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs @@ -1,7 +1,9 @@ +using System; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Security.Identity { @@ -10,22 +12,27 @@ namespace Umbraco.Web.Security.Identity /// public sealed class ExternalSignInAutoLinkOptions { - public ExternalSignInAutoLinkOptions( bool autoLinkExternalAccount = false, - string defaultUserType = "editor", string[] defaultAllowedSections = null, string defaultCulture = null, string autoLinkExternalAccountView = null) + string defaultUserType = "editor", + string[] defaultAllowedSections = null, + string defaultCulture = null) { Mandate.ParameterNotNullOrEmpty(defaultUserType, "defaultUserType"); _defaultUserType = defaultUserType; _defaultAllowedSections = defaultAllowedSections ?? new[] { "content", "media" }; _autoLinkExternalAccount = autoLinkExternalAccount; - _autoLinkExternalAccountView = autoLinkExternalAccountView; _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; } private readonly string _defaultUserType; + /// + /// A callback executed during account auto-linking and before the user is persisted + /// + public Action OnAutoLinking { get; set; } + /// /// The default User Type alias to use for auto-linking users /// @@ -56,18 +63,7 @@ namespace Umbraco.Web.Security.Identity { return _autoLinkExternalAccount; } - - private readonly string _autoLinkExternalAccountView; - - /// - /// Generally this is empty which means auto-linking will be silent, however in some cases developers may want to - /// prompt the user to enter additional user information that they want to save with the user that has been created. - /// - public string GetAutoLinkExternalAccountView(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) - { - return _autoLinkExternalAccountView; - } - + private readonly string _defaultCulture; /// diff --git a/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..54d1d6bdce --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs @@ -0,0 +1,12 @@ +using Microsoft.Owin; + +namespace Umbraco.Web.Security.Identity +{ + /// + /// Used to display a custom view in the back office if developers choose to implement their own custom 2 factor authentication + /// + public interface IUmbracoBackOfficeTwoFactorOptions + { + string GetTwoFactorView(IOwinContext owinContext, UmbracoContext umbracoContext, string username); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b9df10254d..f22db0c3b2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -134,11 +134,11 @@ False - ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll False - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll @@ -309,6 +309,7 @@ + diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 33c259c885..eecd485e24 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -6,8 +6,8 @@ - - + +