diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index fc7ecf5330..85d6a0c715 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; +using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Identity; @@ -12,9 +14,16 @@ namespace Umbraco.Core.Security { public class BackOfficeSignInManager : SignInManager { - public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager) + private readonly ILogger _logger; + private readonly IOwinRequest _request; + + public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) : base(userManager, authenticationManager) { + if (logger == null) throw new ArgumentNullException("logger"); + if (request == null) throw new ArgumentNullException("request"); + _logger = logger; + _request = request; AuthenticationType = Constants.Security.BackOfficeAuthenticationType; } @@ -23,9 +32,54 @@ namespace Umbraco.Core.Security return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); } - public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context) + public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, ILogger logger) { - return new BackOfficeSignInManager(context.GetUserManager(), context.Authentication); + return new BackOfficeSignInManager( + context.GetUserManager(), + context.Authentication, + logger, + context.Request); + } + + /// + /// Sign in the user in using the user name and password + /// + /// + /// + public async override Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) + { + var result = await base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout); + + switch (result) + { + case SignInStatus.Success: + break; + case SignInStatus.LockedOut: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt failed for username {0} from IP address {1}, the user is locked", + userName, + _request.RemoteIpAddress), null, null); + break; + case SignInStatus.RequiresVerification: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt failed for username {0} from IP address {1}, the user requires verification", + userName, + _request.RemoteIpAddress), null, null); + break; + case SignInStatus.Failure: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt failed for username {0} from IP address {1}", + userName, + _request.RemoteIpAddress), null, null); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return result; } /// @@ -67,6 +121,12 @@ namespace Umbraco.Core.Security ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes) }, userIdentity); } + + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "Login attempt succeeded for username {0} from IP address {1}", + user.UserName, + _request.RemoteIpAddress), null, null); } } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 8d99086ba5..a73e51e54d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -102,7 +102,14 @@ angular.module('umbraco.services') if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) angularHelper.safeApply($rootScope, function () { - userAuthExpired(); + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } + finally { + userAuthExpired(); + } }); } else { diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 95f985148c..7f7b59199a 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Security.Claims; +using System.ServiceModel.Channels; using System.Text; using System.Threading.Tasks; using System.Web; @@ -244,7 +245,11 @@ namespace Umbraco.Web.Editors [ClearAngularAntiForgeryToken] [ValidateAngularAntiForgeryToken] public HttpResponseMessage PostLogout() - { + { + Logger.Info("User {0} from IP address {1} has logged out", + () => User.Identity == null ? "UNKNOWN" : User.Identity.Name, + () => TryGetOwinContext().Result.Request.RemoteIpAddress); + return Request.CreateResponse(HttpStatusCode.OK); } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 23a7d768a0..96ccf10e71 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -3,44 +3,36 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Security.Claims; -using System.ServiceModel.Security; using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Web; using System.Web.Mvc; using System.Web.UI; -using dotless.Core.Parser.Tree; +using ClientDependency.Core.Config; using Microsoft.AspNet.Identity; -using Microsoft.Owin; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Manifest; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.PropertyEditors; +using Umbraco.Web.Security.Identity; using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; -using Umbraco.Web.PropertyEditors; -using Umbraco.Web.Models; -using Umbraco.Web.WebServices; using Umbraco.Web.WebApi.Filters; -using System.Web; -using AutoMapper; -using Microsoft.AspNet.Identity.Owin; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; -using Task = System.Threading.Tasks.Task; -using Umbraco.Web.Security.Identity; +using Umbraco.Web.WebServices; +using Action = umbraco.BusinessLogic.Actions.Action; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -417,7 +409,7 @@ namespace Umbraco.Web.Editors public async Task ExternalLinkLoginCallback() { var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync( - Core.Constants.Security.BackOfficeExternalAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType, XsrfKey, User.Identity.GetUserId()); if (loginInfo == null) @@ -459,7 +451,7 @@ namespace Umbraco.Web.Editors //First check if there's external login info, if there's not proceed as normal var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync( - Core.Constants.Security.BackOfficeExternalAuthenticationType); + Constants.Security.BackOfficeExternalAuthenticationType); if (loginInfo == null || loginInfo.ExternalIdentity.IsAuthenticated == false) { @@ -496,9 +488,9 @@ namespace Umbraco.Web.Editors } //Remove the cookie otherwise this message will keep appearing - if (Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName] != null) + if (Response.Cookies[Constants.Security.BackOfficeExternalCookieName] != null) { - Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue; + Response.Cookies[Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue; } } @@ -620,7 +612,7 @@ namespace Umbraco.Web.Editors var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); app.Add("version", version); - app.Add("cdf", ClientDependency.Core.Config.ClientDependencySettings.Instance.Version); + app.Add("cdf", ClientDependencySettings.Instance.Version); //useful for dealing with virtual paths on the client side when hosted in virtual directories especially app.Add("applicationPath", HttpContext.Request.ApplicationPath.EnsureEndsWith('/')); return app; @@ -719,7 +711,7 @@ namespace Umbraco.Web.Editors /// private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) { - return GetLegacyActionJsForActions(type, global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences()); + return GetLegacyActionJsForActions(type, Action.GetJavaScriptFileReferences()); } internal enum LegacyJsActionType diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 58a77d91cf..11bdf48897 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.Security.Identity userMembershipProvider)); //Create a sign in manager per request - app.CreatePerOwinContext(BackOfficeSignInManager.Create); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger())); } /// @@ -87,7 +87,7 @@ namespace Umbraco.Web.Security.Identity userMembershipProvider)); //Create a sign in manager per request - app.CreatePerOwinContext(BackOfficeSignInManager.Create); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -112,7 +112,7 @@ namespace Umbraco.Web.Security.Identity app.CreatePerOwinContext(userManager); //Create a sign in manager per request - app.CreatePerOwinContext(BackOfficeSignInManager.Create); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -148,7 +148,10 @@ namespace Umbraco.Web.Security.Identity }; //This is a custom middleware, we need to return the user's remaining logged in seconds - app.Use(authOptions, UmbracoConfig.For.UmbracoSettings().Security); + app.Use( + authOptions, + UmbracoConfig.For.UmbracoSettings().Security, + app.CreateLogger()); app.UseCookieAuthentication(authOptions); diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs index eeebe13f3e..d909ff76e2 100644 --- a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; using Microsoft.Owin; +using Microsoft.Owin.Logging; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -21,15 +23,20 @@ namespace Umbraco.Web.Security.Identity { private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; private readonly ISecuritySection _security; + private readonly ILogger _logger; public GetUserSecondsMiddleWare( OwinMiddleware next, UmbracoBackOfficeCookieAuthOptions authOptions, - ISecuritySection security) + ISecuritySection security, + ILogger logger) : base(next) { + if (authOptions == null) throw new ArgumentNullException("authOptions"); + if (logger == null) throw new ArgumentNullException("logger"); _authOptions = authOptions; _security = security; + _logger = logger; } public override async Task Invoke(IOwinContext context) @@ -88,6 +95,17 @@ namespace Umbraco.Web.Security.Identity _authOptions.CookieName, cookieValue, cookieOptions); + + remainingSeconds = (ticket.Properties.ExpiresUtc.Value - DateTime.Now.ToUniversalTime()).TotalSeconds; + } + else if (remainingSeconds <=30) + { + //NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in + // the timeout process. + + _logger.WriteCore(TraceEventType.Information, 0, + string.Format("User logged will be logged out due to timeout: {0}, IP Address: {1}", ticket.Identity.Name, request.RemoteIpAddress), + null, null); } await response.WriteAsync(remainingSeconds.ToString(CultureInfo.InvariantCulture));