diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 008fbc47d7..44e81d604a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -195,7 +195,6 @@ - diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs deleted file mode 100644 index fd30f1b5ec..0000000000 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO; -using System.Net; -using System.Security.Principal; -using System.Web; -using NUnit.Framework; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Tests.Web.AngularIntegration -{ - [TestFixture] - public class AngularAntiForgeryTests - { - - [TearDown] - public void TearDown() - { - HttpContext.Current = null; - } - - [Test] - public void Can_Validate_Generated_Tokens() - { - using (var writer = new StringWriter()) - { - HttpContext.Current = new HttpContext(new HttpRequest("test.html", "http://test/", ""), new HttpResponse(writer)); - - string cookieToken, headerToken; - AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - - Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); - } - - } - - [Test] - public void Can_Validate_Generated_Tokens_With_User() - { - using (var writer = new StringWriter()) - { - HttpContext.Current = new HttpContext(new HttpRequest("test.html", "http://test/", ""), new HttpResponse(writer)) - { - User = new GenericPrincipal(new HttpListenerBasicIdentity("test", "test"), new string[] {}) - }; - - string cookieToken, headerToken; - AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - - Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); - } - - } - - } -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs index a807c663d0..9c415fe180 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; @@ -16,7 +16,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// is logged in using forms authentication which indicates the seconds remaining /// before their timeout expires. /// - [IsBackOffice] + [IsBackOffice] [UmbracoUserTimeoutFilter] [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] [DisableBrowserCache] diff --git a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs index f7fd174e03..ef8e22c9d8 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Net; using System.Security.Claims; using System.Threading.Tasks; @@ -23,7 +23,8 @@ namespace Umbraco.Web.BackOffice.Filters /// public sealed class ValidateAngularAntiForgeryTokenAttribute : TypeFilterAttribute { - public ValidateAngularAntiForgeryTokenAttribute() : base(typeof(ValidateAngularAntiForgeryTokenFilter)) + public ValidateAngularAntiForgeryTokenAttribute() + : base(typeof(ValidateAngularAntiForgeryTokenFilter)) { } @@ -44,12 +45,13 @@ namespace Umbraco.Web.BackOffice.Filters { if (context.Controller is ControllerBase controller && controller.User.Identity is ClaimsIdentity userIdentity) { - //if there is not CookiePath claim, then exit + // if there is not CookiePath claim, then exit if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) { await next(); } } + var cookieToken = _cookieManager.GetCookieValue(Constants.Web.CsrfValidationCookieName); var httpContext = context.HttpContext; diff --git a/src/Umbraco.Web/AppBuilderExtensions.cs b/src/Umbraco.Web/AppBuilderExtensions.cs deleted file mode 100644 index f4766bc414..0000000000 --- a/src/Umbraco.Web/AppBuilderExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Web; -using Microsoft.AspNet.SignalR; -using Microsoft.Owin.Logging; -using Owin; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Logging; - -namespace Umbraco.Web -{ - /// - /// Provides general extension methods to IAppBuilder. - /// - public static class AppBuilderExtensions - { - /// - /// Called at the end of configuring middleware - /// - /// The app builder. - /// - /// This could be used for something else in the future - maybe to inform Umbraco that middleware is done/ready, but for - /// now this is used to raise the custom event - /// - /// This is an extension method in case developer entirely replace the UmbracoDefaultOwinStartup class, in which case they will - /// need to ensure they call this extension method in their startup class. - /// - public static void FinalizeMiddlewareConfiguration(this IAppBuilder app) - { - UmbracoDefaultOwinStartup.OnMiddlewareConfigured(new OwinMiddlewareConfiguredEventArgs(app)); - } - - /// - /// Sets the OWIN logger to use Umbraco's logging system. - /// - /// The app builder. - public static void SetUmbracoLoggerFactory(this IAppBuilder app) - { - app.SetLoggerFactory(new OwinLoggerFactory()); - } - - /// - /// Configures SignalR. - /// - /// The app builder. - /// - /// - public static IAppBuilder UseSignalR(this IAppBuilder app, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var umbracoPath = globalSettings.GetUmbracoMvcArea(hostingEnvironment); - var signalrPath = HttpRuntime.AppDomainAppVirtualPath + umbracoPath + "/BackOffice/signalr"; - return app.MapSignalR(signalrPath, new HubConfiguration { EnableDetailedErrors = true }); - } - } -} diff --git a/src/Umbraco.Web/Logging/OwinLogger.cs b/src/Umbraco.Web/Logging/OwinLogger.cs deleted file mode 100644 index 7983ee36c7..0000000000 --- a/src/Umbraco.Web/Logging/OwinLogger.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Diagnostics; -using Microsoft.Extensions.Logging; - -namespace Umbraco.Web.Logging -{ - internal class OwinLogger : Microsoft.Owin.Logging.ILogger - { - private readonly ILogger _logger; - private readonly Lazy _type; - - public OwinLogger(ILogger logger, Lazy type) - { - _logger = logger; - _type = type; - } - - /// - /// Aggregates most logging patterns to a single method. This must be compatible with the Func representation in the OWIN environment. - /// To check IsEnabled call WriteCore with only TraceEventType and check the return value, no event will be written. - /// - /// - /// - public bool WriteCore(TraceEventType eventType, int eventId, object state, Exception exception, Func formatter) - { - if (state == null) state = ""; - switch (eventType) - { - case TraceEventType.Critical: - _logger.LogCritical(exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Error: - _logger.LogError(exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Warning: - _logger.LogWarning("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Information: - _logger.LogInformation("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Verbose: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Start: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Stop: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Suspend: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Resume: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Transfer: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - default: - throw new ArgumentOutOfRangeException("eventType"); - } - } - } -} diff --git a/src/Umbraco.Web/Logging/OwinLoggerFactory.cs b/src/Umbraco.Web/Logging/OwinLoggerFactory.cs deleted file mode 100644 index d8b76145c6..0000000000 --- a/src/Umbraco.Web/Logging/OwinLoggerFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Owin.Logging; -using Umbraco.Core; - -namespace Umbraco.Web.Logging -{ - public class OwinLoggerFactory : ILoggerFactory - { - /// - /// Creates a new ILogger instance of the given name. - /// - /// - /// - public Microsoft.Owin.Logging.ILogger Create(string name) - { - return new OwinLogger(StaticApplicationLogging.Logger, new Lazy(() => Type.GetType(name) ?? typeof (OwinLogger))); - } - } -} diff --git a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs deleted file mode 100644 index 484ee70ff7..0000000000 --- a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Security.Claims; -using System.Web.Mvc; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Web.Mvc -{ - /// - /// A filter to check for the csrf token based on Angular's standard approach - /// - /// - /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// - /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled - /// - public sealed class ValidateMvcAngularAntiForgeryTokenAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - var userIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity; - if (userIdentity != null) - { - //if there is not CookiePath claim, then exit - if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) - { - base.OnActionExecuting(filterContext); - return; - } - } - - string failedReason; - var headers = new List>>(); - foreach (var key in filterContext.HttpContext.Request.Headers.AllKeys) - { - if (headers.Any(x => x.Key == key)) - { - var found = headers.First(x => x.Key == key); - found.Value.Add(filterContext.HttpContext.Request.Headers[key]); - } - else - { - headers.Add(new KeyValuePair>(key, new List { filterContext.HttpContext.Request.Headers[key] })); - } - } - var cookie = filterContext.HttpContext.Request.Cookies[Core.Constants.Web.CsrfValidationCookieName]; - if (AngularAntiForgeryHelper.ValidateHeaders( - headers.Select(x => new KeyValuePair>(x.Key, x.Value)).ToArray(), - cookie == null ? "" : cookie.Value, - out failedReason) == false) - { - var result = new HttpStatusCodeResult(HttpStatusCode.ExpectationFailed); - filterContext.Result = result; - return; - } - - base.OnActionExecuting(filterContext); - } - } -} diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs deleted file mode 100644 index 801ceae191..0000000000 --- a/src/Umbraco.Web/OwinExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Web; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Umbraco.Core; -using Umbraco.Web.Security; - -namespace Umbraco.Web -{ - public static class OwinExtensions - { - - /// - /// Gets the for the Umbraco back office cookie - /// - /// - /// - internal static ISecureDataFormat GetUmbracoAuthTicketDataProtector(this IOwinContext owinContext) - { - var found = owinContext.Get(); - return found?.Protector; - } - - public static string GetCurrentRequestIpAddress(this IOwinContext owinContext) - { - if (owinContext == null) - { - return "Unknown, owinContext is null"; - } - if (owinContext.Request == null) - { - return "Unknown, owinContext.Request is null"; - } - - var httpContext = owinContext.TryGetHttpContext(); - if (httpContext == false) - { - return "Unknown, cannot resolve HttpContext from owinContext"; - } - - return httpContext.Result.GetCurrentRequestIpAddress(); - } - - /// - /// Nasty little hack to get HttpContextBase from an owin context - /// - /// - /// - internal static Attempt TryGetHttpContext(this IOwinContext owinContext) - { - var ctx = owinContext.Get(typeof(HttpContextBase).FullName); - return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); - } - - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions - /// - public static T Get(this IOwinContext context) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - 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/OwinMiddlewareConfiguredEventArgs.cs b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs deleted file mode 100644 index 8b4d46a373..0000000000 --- a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Owin; - -namespace Umbraco.Web -{ - public class OwinMiddlewareConfiguredEventArgs : EventArgs - { - public OwinMiddlewareConfiguredEventArgs(IAppBuilder appBuilder) - { - AppBuilder = appBuilder; - } - - public IAppBuilder AppBuilder { get; private set; } - } -} diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs deleted file mode 100644 index aa5ff7a4e1..0000000000 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Threading; -using Microsoft.Owin; -using Microsoft.Owin.Extensions; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.DataProtection; -using Owin; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Composing; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Security -{ - /// - /// Provides security/identity extension methods to IAppBuilder. - /// - public static class AppBuilderExtensions - { - - // TODO: Migrate this! - - /// - /// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct - /// Umbraco back office configuration - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// By default this will be configured to execute on PipelineStage.Authenticate - /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache) - { - return app.UseUmbracoBackOfficeExternalCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, hostingEnvironment, requestCache, PipelineStage.Authenticate); - } - - /// - /// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct - /// Umbraco back office configuration - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, - IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, - GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, PipelineStage stage) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); - if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); - - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType, - AuthenticationMode = AuthenticationMode.Passive, - CookieName = Constants.Security.BackOfficeExternalCookieName, - ExpireTimeSpan = TimeSpan.FromMinutes(5), - CookiePath = "/", - CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest, - CookieHttpOnly = true, - CookieDomain = new SecuritySettings().AuthCookieDomain // TODO inject settings - }, stage); - - app.UseStageMarker(stage); - return app; - } - - public static void SanitizeThreadCulture(this IAppBuilder app) - { - Thread.CurrentThread.SanitizeThreadCulture(); - } - - } -} diff --git a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs deleted file mode 100644 index 62724a4846..0000000000 --- a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Threading.Tasks; -using System.Web; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Logging; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.Security -{ - /// - /// Custom middleware to return the remaining seconds the user has before they are logged out - /// - /// - /// This is quite a custom request because in most situations we just want to return the seconds and don't want - /// to renew the auth ticket, however if KeepUserLoggedIn is true, then we do want to renew the auth ticket for - /// this request! - /// - internal class GetUserSecondsMiddleWare : OwinMiddleware - { - private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; - private readonly GlobalSettings _globalSettings; - private readonly SecuritySettings _security; - private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; - - public GetUserSecondsMiddleWare( - OwinMiddleware next, - UmbracoBackOfficeCookieAuthOptions authOptions, - GlobalSettings globalSettings, - IOptions security, - ILogger logger, - IHostingEnvironment hostingEnvironment) - : base(next) - { - _authOptions = authOptions ?? throw new ArgumentNullException(nameof(authOptions)); - _globalSettings = globalSettings; - _security = security.Value; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _hostingEnvironment = hostingEnvironment; - } - - public override async Task Invoke(IOwinContext context) - { - var request = context.Request; - var response = context.Response; - - if (request.Uri.Scheme.InvariantStartsWith("http") - && request.Uri.AbsolutePath.InvariantEquals( - $"{_globalSettings.GetBackOfficePath(_hostingEnvironment)}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds")) - { - var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName); - if (cookie.IsNullOrWhiteSpace() == false) - { - var ticket = _authOptions.TicketDataFormat.Unprotect(cookie); - if (ticket != null) - { - var remainingSeconds = ticket.Properties.ExpiresUtc.HasValue - ? (ticket.Properties.ExpiresUtc.Value - _authOptions.SystemClock.UtcNow).TotalSeconds - : 0; - - response.ContentType = "application/json; charset=utf-8"; - response.StatusCode = 200; - response.Headers.Add("Cache-Control", new[] { "no-cache" }); - response.Headers.Add("Pragma", new[] { "no-cache" }); - response.Headers.Add("Expires", new[] { "-1" }); - response.Headers.Add("Date", new[] { _authOptions.SystemClock.UtcNow.ToString("R") }); - - //Ok, so here we need to check if we want to process/renew the auth ticket for each - // of these requests. If that is the case, the user will really never be logged out until they - // close their browser (there will be edge cases of that, especially when debugging) - if (_security.KeepUserLoggedIn) - { - var currentUtc = _authOptions.SystemClock.UtcNow; - var issuedUtc = ticket.Properties.IssuedUtc; - var expiresUtc = ticket.Properties.ExpiresUtc; - - if (expiresUtc.HasValue && issuedUtc.HasValue) - { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); - - //if it's time to renew, then do it - if (timeRemaining < timeElapsed) - { - // TODO: This would probably be simpler just to do: context.OwinContext.Authentication.SignIn(context.Properties, identity); - // this will invoke the default Cookie middleware to basically perform this logic for us. - - ticket.Properties.IssuedUtc = currentUtc; - var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); - ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); - - var cookieValue = _authOptions.TicketDataFormat.Protect(ticket); - - var cookieOptions = _authOptions.CreateRequestCookieOptions(context, ticket); - - _authOptions.CookieManager.AppendResponseCookie( - context, - _authOptions.CookieName, - cookieValue, - cookieOptions); - - remainingSeconds = (ticket.Properties.ExpiresUtc.Value - currentUtc).TotalSeconds; - } - } - - // NOTE: SessionIdValidator has been moved to netcore - ////We also need to re-validate the user's session if we are relying on this ping to keep their session alive - //await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity, _globalSettings); - } - 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)); - return; - } - } - - // HACK: we need to suppress the stupid forms authentication module but we can only do that by using non owin stuff - if (HttpContext.Current != null && HttpContext.Current.Response != null) - { - HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true; - } - - response.StatusCode = 401; - } - else if (Next != null) - { - await Next.Invoke(context); - } - } - } -} diff --git a/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs b/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs deleted file mode 100644 index b3b193a78f..0000000000 --- a/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Threading; -using Umbraco.Extensions; - - -namespace Umbraco.Web.Security -{ - - /// - /// This class is used by events raised from the BackofficeUserManager - /// - public class IdentityAuditEventArgs : EventArgs - { - /// - /// The action that got triggered from the audit event - /// - public AuditEvent Action { get; private set; } - - /// - /// Current date/time in UTC format - /// - public DateTime DateTimeUtc { get; private set; } - - /// - /// The source IP address of the user performing the action - /// - public string IpAddress { get; private set; } - - /// - /// The user affected by the event raised - /// - public int AffectedUser { get; private set; } - - /// - /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 - /// - public int PerformingUser { get; private set; } - - /// - /// An optional comment about the action being logged - /// - public string Comment { get; private set; } - - /// - /// This property is always empty except in the LoginFailed event for an unknown user trying to login - /// - public string Username { get; private set; } - - - /// - /// Default constructor - /// - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Comment = comment; - AffectedUser = affectedUser; - - PerformingUser = performingUser == -1 - ? GetCurrentRequestBackOfficeUserId() - : performingUser; - } - - /// - /// Creates an instance without a performing or affected user (the id will be set to -1) - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Username = username; - Comment = comment; - - PerformingUser = -1; - } - - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment, int performingUser) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Username = username; - Comment = comment; - - PerformingUser = performingUser == -1 - ? GetCurrentRequestBackOfficeUserId() - : performingUser; - } - - /// - /// Returns the current logged in backoffice user's Id logging if there is one - /// - /// - protected int GetCurrentRequestBackOfficeUserId() - { - var userId = -1; - var backOfficeIdentity = Thread.CurrentPrincipal.GetUmbracoIdentity(); - if (backOfficeIdentity != null) - int.TryParse(backOfficeIdentity.Id.ToString(), out userId); - return userId; - } - } - - public enum AuditEvent - { - AccountLocked, - AccountUnlocked, - ForgotPasswordRequested, - ForgotPasswordChangedSuccess, - LoginFailed, - LoginRequiresVerification, - LoginSucces, - LogoutSuccess, - PasswordChanged, - PasswordReset, - ResetAccessFailedCount, - SendingUserInvite - } -} diff --git a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs deleted file mode 100644 index 2975149107..0000000000 --- a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs +++ /dev/null @@ -1,106 +0,0 @@ -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 - { - /// 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(nameof(options)); - if (options.Provider == null) throw new ArgumentException("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 - { - 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/Security/UmbracoAuthTicketDataProtector.cs b/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs deleted file mode 100644 index 3824935559..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Owin.Security; -using Umbraco.Core; - -namespace Umbraco.Web.Security -{ - /// - /// This is used so that we can retrieve the auth ticket protector from an IOwinContext - /// - internal class UmbracoAuthTicketDataProtector : DisposableObjectSlim - { - public UmbracoAuthTicketDataProtector(ISecureDataFormat protector) - { - Protector = protector ?? throw new ArgumentNullException(nameof(protector)); - } - - public ISecureDataFormat Protector { get; } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs deleted file mode 100644 index 34669bc5ae..0000000000 --- a/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; - -namespace Umbraco.Web.Security -{ - /// - /// Umbraco auth cookie options - /// - public sealed class UmbracoBackOfficeCookieAuthOptions : CookieAuthenticationOptions - { - public int LoginTimeoutMinutes { get; } - - /// - /// Creates the cookie options for saving the auth cookie - /// - /// - /// - /// - public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) - { - if (ctx == null) throw new ArgumentNullException(nameof(ctx)); - if (ticket == null) throw new ArgumentNullException(nameof(ticket)); - - var issuedUtc = ticket.Properties.IssuedUtc ?? SystemClock.UtcNow; - var expiresUtc = ticket.Properties.ExpiresUtc ?? issuedUtc.Add(ExpireTimeSpan); - - var cookieOptions = new CookieOptions - { - Path = "/", - Domain = this.CookieDomain ?? null, - HttpOnly = true, - Secure = this.CookieSecure == CookieSecureOption.Always - || (this.CookieSecure == CookieSecureOption.SameAsRequest && ctx.Request.IsSecure), - }; - - if (ticket.Properties.IsPersistent) - { - cookieOptions.Expires = expiresUtc.UtcDateTime; - } - - return cookieOptions; - } - - } -} diff --git a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs deleted file mode 100644 index d1b0c54279..0000000000 --- a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Microsoft.Owin.Security; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - - /// - /// Custom secure format that ensures the Identity in the ticket is and not just a ClaimsIdentity - /// - internal class UmbracoSecureDataFormat : ISecureDataFormat - { - private readonly int _loginTimeoutMinutes; - private readonly ISecureDataFormat _ticketDataFormat; - - public UmbracoSecureDataFormat(int loginTimeoutMinutes, ISecureDataFormat ticketDataFormat) - { - _loginTimeoutMinutes = loginTimeoutMinutes; - _ticketDataFormat = ticketDataFormat ?? throw new ArgumentNullException(nameof(ticketDataFormat)); - } - - public string Protect(AuthenticationTicket data) - { - var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity; - - //create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins - var ticket = new AuthenticationTicket(backofficeIdentity, - new AuthenticationProperties(data.Properties.Dictionary) - { - IssuedUtc = data.Properties.IssuedUtc, - ExpiresUtc = data.Properties.ExpiresUtc ?? DateTimeOffset.UtcNow.AddMinutes(_loginTimeoutMinutes), - AllowRefresh = data.Properties.AllowRefresh, - IsPersistent = data.Properties.IsPersistent, - RedirectUri = data.Properties.RedirectUri - }); - - return _ticketDataFormat.Protect(ticket); - } - - /// - /// Un-protects the cookie - /// - /// - /// - public AuthenticationTicket Unprotect(string protectedText) - { - AuthenticationTicket decrypt; - try - { - decrypt = _ticketDataFormat.Unprotect(protectedText); - if (decrypt == null) return null; - } - catch (Exception) - { - return null; - } - - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(decrypt.Identity, out var identity)) - return null; - - //return the ticket with a UmbracoBackOfficeIdentity - var ticket = new AuthenticationTicket(identity, decrypt.Properties); - - return ticket; - } - } -} diff --git a/src/Umbraco.Web/Security/WebAuthExtensions.cs b/src/Umbraco.Web/Security/WebAuthExtensions.cs deleted file mode 100644 index f008dc8ba7..0000000000 --- a/src/Umbraco.Web/Security/WebAuthExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Net.Http; -using System.Security.Principal; -using System.ServiceModel.Channels; -using System.Threading; -using System.Web; -using Umbraco.Web.WebApi; - -namespace Umbraco.Web.Security -{ - internal static class WebAuthExtensions - { - /// - /// This will set a an authenticated IPrincipal to the current request for webforms & webapi - /// - /// - /// - /// - internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IPrincipal principal) - { - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - var http = request.TryGetHttpContext(); - if (http) - { - http.Result.User = principal; - } - Thread.CurrentPrincipal = principal; - - //For WebAPI - request.SetUserPrincipal(principal); - - return principal; - } - - /// - /// This will set a an authenticated IPrincipal to the current request given the IUser object - /// - /// - /// - /// - internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, IPrincipal principal) - { - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - httpContext.User = principal; - Thread.CurrentPrincipal = principal; - return principal; - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 65cba08ec9..9e7a9fe7af 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -126,7 +126,6 @@ Properties\SolutionInfo.cs - @@ -141,14 +140,10 @@ - - - - @@ -159,8 +154,6 @@ - - @@ -175,12 +168,9 @@ - - - @@ -190,8 +180,6 @@ - - @@ -200,26 +188,15 @@ - - - - - - - - - - - diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index c7066c664f..f10f4491d9 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -1,16 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading; using System.Web; using System.Web.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Serilog; using Serilog.Context; using Umbraco.Core; using Umbraco.Core.Cache; @@ -22,10 +21,8 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Logging.Serilog.Enrichers; -using Umbraco.Core.Runtime; using Umbraco.Net; using Umbraco.Web.Hosting; -using Umbraco.Web.Logging; using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; using Current = Umbraco.Web.Composing.Current; using GlobalSettings = Umbraco.Core.Configuration.Models.GlobalSettings; diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs deleted file mode 100644 index 58fb36e13b..0000000000 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Owin; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.Mapping; -using Umbraco.Net; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.Security; - - -[assembly: OwinStartup("UmbracoDefaultOwinStartup", typeof(UmbracoDefaultOwinStartup))] - -namespace Umbraco.Web -{ - /// - /// The default way to configure OWIN for Umbraco - /// - /// - /// The startup type is specified in appSettings under owin:appStartup - /// - public class UmbracoDefaultOwinStartup - { - protected IUmbracoContextAccessor UmbracoContextAccessor => Current.UmbracoContextAccessor; - protected GlobalSettings GlobalSettings => Current.Factory.GetRequiredService(); - protected SecuritySettings SecuritySettings => Current.Factory.GetRequiredService>().Value; - protected IUserPasswordConfiguration UserPasswordConfig => Current.Factory.GetRequiredService(); - protected IRuntimeState RuntimeState => Current.RuntimeState; - protected ServiceContext Services => Current.Services; - protected UmbracoMapper Mapper => Current.Mapper; - protected IIpResolver IpResolver => Current.IpResolver; - protected IHostingEnvironment HostingEnvironment => Current.HostingEnvironment; - protected IRequestCache RequestCache => Current.AppCaches.RequestCache; - - /// - /// Main startup method - /// - /// - public virtual void Configuration(IAppBuilder app) - { - app.SanitizeThreadCulture(); - - // there's nothing we can do really - if (RuntimeState.Level == RuntimeLevel.BootFailed) - return; - - ConfigureServices(app, Services); - ConfigureMiddleware(app); - } - - /// - /// Configures services to be created in the OWIN context (CreatePerOwinContext) - /// - /// - /// - protected virtual void ConfigureServices(IAppBuilder app, ServiceContext services) - { - app.SetUmbracoLoggerFactory(); - } - - /// - /// Configures middleware to be used (i.e. app.Use...) - /// - /// - protected virtual void ConfigureMiddleware(IAppBuilder app) - { - - // Configure OWIN for authentication. - ConfigureUmbracoAuthentication(app); - - app - .UseSignalR(GlobalSettings, HostingEnvironment) - .FinalizeMiddlewareConfiguration(); - } - - /// - /// Configure external/OAuth login providers - /// - /// - protected virtual void ConfigureUmbracoAuthentication(IAppBuilder app) - { - // Ensure owin is configured for Umbraco back office authentication. - // Front-end OWIN cookie configuration must be declared after this code. - app - .UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate); - // TODO: this would be considered a breaking change but this must come after all authentication so should be moved within ConfigureMiddleware - } - - public static event EventHandler MiddlewareConfigured; - - internal static void OnMiddlewareConfigured(OwinMiddlewareConfiguredEventArgs args) - { - MiddlewareConfigured?.Invoke(null, args); - } - } -} diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs deleted file mode 100644 index 4b16e650da..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http.Headers; -using System.Web.Helpers; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// A helper class to deal with csrf prevention with angularjs and webapi - /// - public static class AngularAntiForgeryHelper - { - /// - /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value - /// - /// - /// - /// - /// .Net provides us a way to validate one token with another for added security. With the way angular works, this - /// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate - /// this header value against our original cookie value. - /// - public static void GetTokens(out string cookieToken, out string headerToken) - { - AntiForgery.GetTokens(null, out cookieToken, out headerToken); - } - - /// - /// Validates the header token against the validation cookie value - /// - /// - /// - /// - public static bool ValidateTokens(string cookieToken, string headerToken) - { - // ensure that the cookie matches the header and then ensure it matches the correct value! - try - { - AntiForgery.Validate(cookieToken, headerToken); - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Could not validate XSRF token"); - return false; - } - return true; - } - - internal static bool ValidateHeaders( - KeyValuePair>[] requestHeaders, - string cookieToken, - out string failedReason) - { - failedReason = ""; - - if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false) - { - failedReason = "Missing token"; - return false; - } - - var headerToken = requestHeaders - .Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) - .Select(z => z.Value) - .SelectMany(z => z) - .FirstOrDefault(); - - // both header and cookie must be there - if (cookieToken == null || headerToken == null) - { - failedReason = "Missing token null"; - return false; - } - - if (ValidateTokens(cookieToken, headerToken) == false) - { - failedReason = "Invalid token"; - return false; - } - - return true; - } - - /// - /// Validates the headers/cookies passed in for the request - /// - /// - /// - /// - public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) - { - var cookieToken = requestHeaders.GetCookieValue(Constants.Web.CsrfValidationCookieName); - - return ValidateHeaders( - requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), - cookieToken == null ? null : cookieToken, - out failedReason); - } - - } -} diff --git a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs deleted file mode 100644 index f147a2a4cb..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Security.Claims; -using System.Web.Http; -using System.Web.Http.Filters; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// A filter to check for the csrf token based on Angular's standard approach - /// - /// - /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// - /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled - /// - public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) - { - var userIdentity = ((ApiController) actionContext.ControllerContext.Controller).User.Identity as ClaimsIdentity; - if (userIdentity != null) - { - //if there is not CookiePath claim, then exit - if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) - { - base.OnActionExecuting(actionContext); - return; - } - } - - string failedReason; - if (AngularAntiForgeryHelper.ValidateHeaders(actionContext.Request.Headers, out failedReason) == false) - { - actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.ExpectationFailed); - actionContext.Response.ReasonPhrase = failedReason; - return; - } - - base.OnActionExecuting(actionContext); - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs deleted file mode 100644 index 47dd0908dd..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.IO; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Runtime; - -namespace Umbraco.Web.WebAssets.CDF -{ - [ComposeAfter(typeof(WebInitialComponent))] - public sealed class ClientDependencyComponent : IComponent - { - private readonly HostingSettings _hostingSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly RuntimeSettings _settings; - - public ClientDependencyComponent( - IOptions hostingSettings, - IHostingEnvironment hostingEnvironment, - IOptions settings) - { - _hostingSettings = hostingSettings.Value; - _hostingEnvironment = hostingEnvironment; - _settings = settings.Value; - } - - public void Initialize() - { - if (_hostingEnvironment.IsHosted) - { - ConfigureClientDependency(); - } - } - - private void ConfigureClientDependency() - { - // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] - XmlFileMapper.FileMapDefaultFolder = Core.Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ClientDependency"; - BaseCompositeFileProcessingProvider.UrlTypeDefault = CompositeUrlType.Base64QueryStrings; - - // Now we need to detect if we are running 'Umbraco.Core.LocalTempStorage' as EnvironmentTemp and in that case we want to change the CDF file - // location to be there - if (_hostingSettings.LocalTempStorageLocation == LocalTempStorage.EnvironmentTemp) - { - var cachePath = _hostingEnvironment.LocalTempPath; - - //set the file map and composite file default location to the %temp% location - BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder - = XmlFileMapper.FileMapDefaultFolder - = Path.Combine(cachePath, "ClientDependency"); - } - - if (_settings.MaxQueryStringLength.HasValue || _settings.MaxRequestLength.HasValue) - { - //set the max url length for CDF to be the smallest of the max query length, max request length - ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(_settings.MaxQueryStringLength.GetValueOrDefault(), _settings.MaxRequestLength.GetValueOrDefault()); - } - - //Register a custom renderer - used to process property editor dependencies - var renderer = new DependencyPathRenderer(); - renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection - { - { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } - }); - - ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - } - public void Terminate() - { } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs deleted file mode 100644 index 75e0d6123f..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Core.WebAssets; - -namespace Umbraco.Web.WebAssets.CDF -{ - public sealed class ClientDependencyComposer : ComponentComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs deleted file mode 100644 index 81dd05c25a..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Web; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Semver; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// A utility class for working with CDF config and cache files - use sparingly! - /// - public class ClientDependencyConfiguration - { - private readonly ILogger _logger; - private readonly string _fileName; - - private string FileMapDefaultFolder - { - get => XmlFileMapper.FileMapDefaultFolder; - set => XmlFileMapper.FileMapDefaultFolder = value; - } - - public ClientDependencyConfiguration(ILogger logger, IHostingEnvironment hostingEnvironment) - { - if (logger == null) throw new ArgumentNullException("logger"); - _logger = logger; - _fileName = hostingEnvironment.MapPathContentRoot(string.Format("{0}/ClientDependency.config", Core.Constants.SystemDirectories.Config)); - } - - /// - /// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day - /// - /// The version of Umbraco we're upgrading to - /// A date value to use in the hash to prevent this method from updating the version on each startup - /// Allows the developer to specify the date precision for the hash (i.e. "yyyyMMdd" would be a precision for the day) - /// Boolean to indicate successful update of the ClientDependency.config file - public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat) - { - var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat)); - - //This is a way to convert a string to a long - //see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer - //We could much more easily use MD5 which would create us an INT but since that is not compliant with - //hashing standards we have to use SHA - int intHash; - using (var hash = SHA256.Create()) - { - var bytes = hash.ComputeHash(byteContents); - - var longResult = new[] { 0, 8, 16, 24 } - .Select(i => BitConverter.ToInt64(bytes, i)) - .Aggregate((x, y) => x ^ y); - - //CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes - //so there could be some collisions with this conversion but it's not a problem for our purposes - //It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32); - //which means that this value will not change per AppDomain like some GetHashCode implementations. - intHash = longResult.GetHashCode(); - } - - try - { - var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); - if (clientDependencyConfigXml.Root != null) - { - - var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); - - //Set the new version to the hashcode of now - var oldVersion = versionAttribute.Value; - var newVersion = Math.Abs(intHash).ToString(); - - //don't update if it's the same version - if (oldVersion == newVersion) - return false; - - versionAttribute.SetValue(newVersion); - clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); - - _logger.LogInformation("Updated version number from {OldVersion} to {NewVersion}", oldVersion, newVersion); - return true; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Couldn't update ClientDependency version number"); - } - - return false; - } - - /// - /// Clears the temporary files stored for the ClientDependency folder - /// - /// - public bool ClearTempFiles(HttpContextBase currentHttpContext) - { - var cdfTempDirectories = new HashSet(); - foreach (BaseCompositeFileProcessingProvider provider in ClientDependencySettings.Instance - .CompositeFileProcessingProviderCollection) - { - var path = provider.CompositeFilePath.FullName; - cdfTempDirectories.Add(path); - } - - try - { - var fullPath = FileMapDefaultFolder.StartsWith("~/") - ? currentHttpContext.Server.MapPath(FileMapDefaultFolder) - : FileMapDefaultFolder; - if (fullPath != null) - { - cdfTempDirectories.Add(fullPath); - } - } - catch (Exception ex) - { - //invalid path format or something... try/catch to be safe - _logger.LogError(ex, "Could not get path from ClientDependency.config"); - } - - var success = true; - foreach (var directory in cdfTempDirectories) - { - try - { - if (!Directory.Exists(directory)) - continue; - - Directory.Delete(directory, true); - } - catch (Exception ex) - { - // Something could be locking the directory or the was another error, making sure we don't break the upgrade installer - _logger.LogError(ex, "Could not clear temp files"); - success = false; - } - } - - return success; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs deleted file mode 100644 index c72887b4d2..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using ClientDependency.Core; -using ClientDependency.Core.CompositeFiles; -using ClientDependency.Core.Config; -using Umbraco.Core.Configuration; -using Umbraco.Core.WebAssets; -using CssFile = ClientDependency.Core.CssFile; -using JavascriptFile = ClientDependency.Core.JavascriptFile; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.WebAssets.CDF -{ - public class ClientDependencyRuntimeMinifier : IRuntimeMinifier - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly IUmbracoVersion _umbracoVersion; - - public string CacheBuster => ClientDependencySettings.Instance.Version.ToString(); - - public ClientDependencyRuntimeMinifier( - IHttpContextAccessor httpContextAccessor, - IHostingEnvironment hostingEnvironment, - ILoggerFactory loggerFactory, - IUmbracoVersion umbracoVersion) - { - _httpContextAccessor = httpContextAccessor; - _hostingEnvironment = hostingEnvironment; - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _umbracoVersion = umbracoVersion; - } - - public void CreateCssBundle(string bundleName, params string[] filePaths) - { - if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) - throw new InvalidOperationException("All file paths must be absolute"); - - BundleManager.CreateCssBundle( - bundleName, - filePaths.Select(x => new CssFile(x)).ToArray()); - } - - public Task RenderCssHereAsync(string bundleName) - { - var bundleFiles = GetCssBundleFiles(bundleName); - if (bundleFiles == null) return Task.FromResult(string.Empty); - return Task.FromResult(RenderOutput(bundleFiles, AssetType.Css)); - } - - public void CreateJsBundle(string bundleName, params string[] filePaths) - { - if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) - throw new InvalidOperationException("All file paths must be absolute"); - - BundleManager.CreateJsBundle( - bundleName, - filePaths.Select(x => new JavascriptFile(x)).ToArray()); - } - - public Task RenderJsHereAsync(string bundleName) - { - var bundleFiles = GetJsBundleFiles(bundleName); - if (bundleFiles == null) return Task.FromResult(string.Empty); - return Task.FromResult(RenderOutput(bundleFiles, AssetType.Javascript)); - } - - public Task> GetAssetPathsAsync(string bundleName) - { - var bundleFiles = GetJsBundleFiles(bundleName)?.ToList() ?? GetCssBundleFiles(bundleName)?.ToList(); - if (bundleFiles == null || bundleFiles.Count == 0) return Task.FromResult(Enumerable.Empty()); - - var assetType = bundleFiles[0].DependencyType == ClientDependencyType.Css ? AssetType.Css : AssetType.Javascript; - - // This is a hack on CDF so that we can resolve CDF urls directly since that isn't directly supported by the lib - var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(bundleFiles, new HashSet(), out var scripts, out var stylesheets, _httpContextAccessor.HttpContext); - - var toParse = assetType == AssetType.Javascript ? scripts : stylesheets; - return Task.FromResult>(toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries)); - } - - public Task MinifyAsync(string fileContent, AssetType assetType) - { - TextReader reader = new StringReader(fileContent); - - if (assetType == AssetType.Javascript) - { - var jsMinifier = new JSMin(); - return Task.FromResult(jsMinifier.Minify(reader)); - } - - // asset type is Css - var cssMinifier = new CssMinifier(); - return Task.FromResult(cssMinifier.Minify(reader)); - } - - public void Reset() - { - // Update ClientDependency version - var clientDependencyConfig = new ClientDependencyConfiguration(_loggerFactory.CreateLogger(), _hostingEnvironment); - var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( - _umbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - // Delete ClientDependency temp directories to make sure we get fresh caches - var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(_httpContextAccessor.HttpContext); - } - - private string RenderOutput(IEnumerable bundleFiles, AssetType assetType) - { - var renderer = ClientDependencySettings.Instance.DefaultMvcRenderer; - - renderer.RegisterDependencies( - bundleFiles.ToList(), - new HashSet(), - out var jsOutput, out var cssOutput, _httpContextAccessor.GetRequiredHttpContext()); - - return assetType == AssetType.Css ? cssOutput : jsOutput; - } - - private IEnumerable GetCssBundleFiles(string bundleName) - { - // internal methods needs reflection - var bundle = typeof(BundleManager) - .GetMethod("GetCssBundle", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, new object[] { bundleName }); - var bundleFiles = (IEnumerable) bundle?.GetType().GetProperty("Files").GetValue(bundle, null); - return bundleFiles; - } - - private IEnumerable GetJsBundleFiles(string bundleName) - { - // internal methods needs reflection - var bundle = typeof(BundleManager) - .GetMethod("GetJsBundle", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, new object[] { bundleName }); - var bundleFiles = (IEnumerable)bundle?.GetType().GetProperty("Files").GetValue(bundle, null); - return bundleFiles; - } - - private ClientDependencyType MapDependencyTypeValue(AssetType type) - { - return type switch - { - AssetType.Javascript => ClientDependencyType.Javascript, - AssetType.Css => ClientDependencyType.Css, - _ => (ClientDependencyType) Enum.Parse(typeof(ClientDependencyType), type.ToString(), true) - }; - } - - private IClientDependencyFile MapAssetFile(IAssetFile assetFile) - { - var assetFileType = (AssetFile)assetFile; - var basicFile = new BasicFile(MapDependencyTypeValue(assetFileType.DependencyType)) - { - FilePath = assetFile.FilePath - }; - - return basicFile; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs b/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs deleted file mode 100644 index ef73dbecb1..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using System.Web; -using ClientDependency.Core; -using ClientDependency.Core.FileRegistration.Providers; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// A custom renderer that only outputs a dependency path instead of script tags - for use with the js loader with yepnope - /// - public class DependencyPathRenderer : StandardRenderer - { - public override string Name - { - get { return "Umbraco.DependencyPathRenderer"; } - } - - /// - /// Used to delimit each dependency so we can split later - /// - public const string Delimiter = "||||"; - - /// - /// Override because the StandardRenderer replaces & with & but we don't want that so we'll reverse it - /// - /// - /// - /// - /// - /// - public override void RegisterDependencies(List allDependencies, HashSet paths, out string jsOutput, out string cssOutput, HttpContextBase http) - { - base.RegisterDependencies(allDependencies, paths, out jsOutput, out cssOutput, http); - - jsOutput = jsOutput.Replace("&", "&"); - cssOutput = cssOutput.Replace("&", "&"); - } - - protected override string RenderSingleJsFile(string js, IDictionary htmlAttributes) - { - return js + Delimiter; - } - - protected override string RenderSingleCssFile(string css, IDictionary htmlAttributes) - { - return css + Delimiter; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs b/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs deleted file mode 100644 index a0704140f1..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Web.UI; -using ClientDependency.Core.Controls; -using ClientDependency.Core.FileRegistration.Providers; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.IO; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// Used to load in all client dependencies for Umbraco. - /// Ensures that both UmbracoClient and UmbracoRoot paths are added to the loader. - /// - public class UmbracoClientDependencyLoader : ClientDependencyLoader - { - /// - /// Set the defaults - /// - public UmbracoClientDependencyLoader(GlobalSettings globalSettings, IIOHelper ioHelper) - : base() - { - this.AddPath("UmbracoRoot", ioHelper.ResolveUrl(globalSettings.UmbracoPath)); - this.ProviderName = LoaderControlProvider.DefaultName; - - } - - public static ClientDependencyLoader TryCreate(Control parent, out bool isNew, GlobalSettings globalSettings, IIOHelper ioHelper) - { - if (ClientDependencyLoader.Instance == null) - { - var loader = new UmbracoClientDependencyLoader(globalSettings, ioHelper); - parent.Controls.Add(loader); - isNew = true; - return loader; - } - else - { - isNew = false; - return ClientDependencyLoader.Instance; - } - - } - - } -}