using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; namespace Umbraco.Web.Security { public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider { private readonly IUserService _userService; private readonly IRuntimeState _runtimeState; private readonly IGlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly ISecuritySettings _securitySettings; public BackOfficeCookieAuthenticationProvider(IUserService userService, IRuntimeState runtimeState, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, ISecuritySettings securitySettings) { _userService = userService; _runtimeState = runtimeState; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; _securitySettings = securitySettings; } public override void ResponseSignIn(CookieResponseSignInContext context) { if (context.Identity is UmbracoBackOfficeIdentity backOfficeIdentity) { //generate a session id and assign it //create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one var session = _runtimeState.Level == RuntimeLevel.Run ? _userService.CreateLoginSession(backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress()) : Guid.NewGuid(); backOfficeIdentity.SessionId = session.ToString(); //since it is a cookie-based authentication add that claim backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); } base.ResponseSignIn(context); } public override void ResponseSignOut(CookieResponseSignOutContext context) { //Clear the user's session on sign out if (context?.OwinContext?.Authentication?.User?.Identity != null) { var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity; var sessionId = claimsIdentity.FindFirst(Core.Constants.Security.SessionIdClaimType)?.Value; if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession)) { _userService.ClearLoginSession(guidSession); } } base.ResponseSignOut(context); //Make sure the definitely all of these cookies are cleared when signing out with cookies context.Response.Cookies.Append(SessionIdValidator.CookieName, "", new CookieOptions { Expires = DateTime.Now.AddYears(-1), Path = "/" }); context.Response.Cookies.Append(_securitySettings.AuthCookieName, "", new CookieOptions { Expires = DateTime.Now.AddYears(-1), Path = "/" }); context.Response.Cookies.Append(Core.Constants.Web.PreviewCookieName, "", new CookieOptions { Expires = DateTime.Now.AddYears(-1), Path = "/" }); context.Response.Cookies.Append(Core.Constants.Security.BackOfficeExternalCookieName, "", new CookieOptions { Expires = DateTime.Now.AddYears(-1), Path = "/" }); } /// /// Ensures that the culture is set correctly for the current back office user and that the user's session token is valid /// /// /// public override async Task ValidateIdentity(CookieValidateIdentityContext context) { //ensure the thread culture is set context?.Identity?.EnsureCulture(); await EnsureValidSessionId(context); if (context?.Identity == null) { context?.OwinContext.Authentication.SignOut(context.Options.AuthenticationType); return; } await base.ValidateIdentity(context); } /// /// Ensures that the user has a valid session id /// /// /// So that we are not overloading the database this throttles it's check to every minute /// protected virtual async Task EnsureValidSessionId(CookieValidateIdentityContext context) { if (_runtimeState.Level == RuntimeLevel.Run) await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _globalSettings, _hostingEnvironment); } } }