diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs index dbac7c9f76..f9d5d9f7da 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers Assert.Multiple(() => { Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); - Assert.AreEqual(")]}',\n{\"Message\":\"No variants flagged for saving\"}", body); + Assert.AreEqual(AngularJsonMediaTypeFormatter.XsrfPrefix + "{\"Message\":\"No variants flagged for saving\"}", body); }); } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index d0bc38ea0b..1f9283b9f3 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -2,7 +2,6 @@ using System; using System.Linq.Expressions; using System.Net.Http; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -11,20 +10,18 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; -using Umbraco.Core.DependencyInjection; -using Umbraco.Web.Common.Controllers; -using Microsoft.Extensions.Hosting; -using Umbraco.Core.Cache; -using Umbraco.Core.Persistence; -using Umbraco.Core.Runtime; using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Website.Controllers; namespace Umbraco.Tests.Integration.TestServerTest { @@ -133,8 +130,14 @@ namespace Umbraco.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { services.AddTransient(); - var typeLoader = services.AddTypeLoader(GetType().Assembly, TestHelper.GetWebHostEnvironment(), TestHelper.GetHostingEnvironment(), - TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, Configuration, TestHelper.Profiler); + var typeLoader = services.AddTypeLoader( + GetType().Assembly, + TestHelper.GetWebHostEnvironment(), + TestHelper.GetHostingEnvironment(), + TestHelper.ConsoleLoggerFactory, + AppCaches.NoCache, + Configuration, + TestHelper.Profiler); var builder = new UmbracoBuilder(services, Configuration, typeLoader); @@ -147,10 +150,16 @@ namespace Umbraco.Tests.Integration.TestServerTest .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) .AddPreviewSupport() - //.WithMiniProfiler() // we don't want this running in tests .AddMvcAndRazor(mvcBuilding: mvcBuilder => { + // Adds Umbraco.Web.BackOffice mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); + + // Adds Umbraco.Web.Common + mvcBuilder.AddApplicationPart(typeof(RenderController).Assembly); + + // Adds Umbraco.Web.Website + mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); }) .AddWebServer() .Build(); @@ -159,6 +168,8 @@ namespace Umbraco.Tests.Integration.TestServerTest public override void Configure(IApplicationBuilder app) { app.UseUmbraco(); + app.UseUmbracoBackOffice(); + app.UseUmbracoWebsite(); } } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs deleted file mode 100644 index 34b649d3bb..0000000000 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.Owin.Testing; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.WebApi; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - public class TestRunner - { - private readonly Func _controllerFactory; - - public TestRunner(Func controllerFactory) - { - _controllerFactory = controllerFactory; - } - - public async Task> Execute(string controllerName, string actionName, HttpMethod method, - HttpContent content = null, - MediaTypeWithQualityHeaderValue mediaTypeHeader = null, - bool assertOkResponse = true, object routeDefaults = null, string url = null) - { - if (mediaTypeHeader == null) - { - mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json"); - } - if (routeDefaults == null) - { - routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional }; - } - - var startup = new TestStartup( - configuration => - { - configuration.Routes.MapHttpRoute("Default", - routeTemplate: "{controller}/{action}/{id}", - defaults: routeDefaults); - }, - _controllerFactory); - - using (var server = TestServer.Create(builder => startup.Configuration(builder))) - { - var request = new HttpRequestMessage - { - RequestUri = new Uri("https://testserver/" + (url ?? "")), - Method = method - }; - - if (content != null) - request.Content = content; - - request.Headers.Accept.Add(mediaTypeHeader); - - Console.WriteLine(request); - var response = await server.HttpClient.SendAsync(request); - Console.WriteLine(response); - - if (response.IsSuccessStatusCode == false) - { - WriteResponseError(response); - } - - var json = (await ((StreamContent)response.Content).ReadAsStringAsync()).TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); - if (!json.IsNullOrWhiteSpace()) - { - var deserialized = JsonConvert.DeserializeObject(json); - Console.Write(JsonConvert.SerializeObject(deserialized, Formatting.Indented)); - } - - if (assertOkResponse) - { - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } - - return Tuple.Create(response, json); - } - } - - private static void WriteResponseError(HttpResponseMessage response) - { - var result = response.Content.ReadAsStringAsync().Result; - Console.Out.WriteLine("Http operation unsuccessfull"); - Console.Out.WriteLine($"Status: '{response.StatusCode}'"); - Console.Out.WriteLine($"Reason: '{response.ReasonPhrase}'"); - Console.Out.WriteLine(result); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 2a7ada4c15..2d5fb4fa1b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -174,7 +174,6 @@ - diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index a6f6bc8fb8..74856f4d1b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -636,19 +636,20 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Saves content /// - /// [FileUploadCleanupFilter] [ContentSaveValidation] public async Task PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { - var contentItemDisplay = await PostSaveInternal(contentItem, + var contentItemDisplay = await PostSaveInternal( + contentItem, content => { EnsureUniqueName(content.Name, content, "Name"); _contentService.SaveBlueprint(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - //we need to reuse the underlying logic so return the result that it wants - return OperationResult.Succeed(new EventMessages()); + + // we need to reuse the underlying logic so return the result that it wants + return OperationResult.Succeed(new EventMessages()); }, content => { @@ -663,7 +664,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Saves content /// - /// [FileUploadCleanupFilter] [ContentSaveValidation] [OutgoingEditorModelEvent] @@ -679,9 +679,9 @@ namespace Umbraco.Web.BackOffice.Controllers private async Task PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) { - //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). + // Recent versions of IE/Edge may send in the full client side file path instead of just the file name. + // To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + // uploaded files to being *only* the actual file name (as it should be). if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) { foreach (var file in contentItem.UploadedFiles) @@ -690,7 +690,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - //If we've reached here it means: + // If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use @@ -700,20 +700,20 @@ namespace Umbraco.Web.BackOffice.Controllers var passesCriticalValidationRules = ValidateCriticalData(contentItem, out var variantCount); - //we will continue to save if model state is invalid, however we cannot save if critical data is missing. + // we will continue to save if model state is invalid, however we cannot save if critical data is missing. if (!ModelState.IsValid) { - //check for critical data validation issues, we can't continue saving if this data is invalid + // check for critical data validation issues, we can't continue saving if this data is invalid if (!passesCriticalValidationRules) { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw a validation message var forDisplay = mapToDisplay(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw HttpResponseException.CreateValidationErrorResponse(forDisplay); } - //if there's only one variant and the model state is not valid we cannot publish so change it to save + // if there's only one variant and the model state is not valid we cannot publish so change it to save if (variantCount == 1) { switch (contentItem.Action) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs index aef6abdd5e..50012c7921 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net; using System.Net.Http; @@ -29,14 +29,12 @@ namespace Umbraco.Web.BackOffice.Controllers [JsonDateTimeFormat] public abstract class ContentControllerBase : BackOfficeNotificationsController { - protected ICultureDictionary CultureDictionary { get; } - protected ILoggerFactory LoggerFactory { get; } - protected IShortStringHelper ShortStringHelper { get; } - protected IEventMessagesFactory EventMessages { get; } - protected ILocalizedTextService LocalizedTextService { get; } private readonly ILogger _logger; private readonly IJsonSerializer _serializer; + /// + /// Initializes a new instance of the class. + /// protected ContentControllerBase( ICultureDictionary cultureDictionary, ILoggerFactory loggerFactory, @@ -54,6 +52,31 @@ namespace Umbraco.Web.BackOffice.Controllers _serializer = serializer; } + /// + /// Gets the + /// + protected ICultureDictionary CultureDictionary { get; } + + /// + /// Gets the + /// + protected ILoggerFactory LoggerFactory { get; } + + /// + /// Gets the + /// + protected IShortStringHelper ShortStringHelper { get; } + + /// + /// Gets the + /// + protected IEventMessagesFactory EventMessages { get; } + + /// + /// Gets the + /// + protected ILocalizedTextService LocalizedTextService { get; } + protected NotFoundObjectResult HandleContentNotFound(object id, bool throwException = true) { ModelState.AddModelError("id", $"content with id: {id} was not found"); diff --git a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs index 9c9496b282..031aeb1f4c 100644 --- a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -34,6 +34,7 @@ namespace Umbraco.Web.Common.Filters _arrayPool = arrayPool; _options = options; } + public void OnResultExecuted(ResultExecutedContext context) { } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b84a003813..62060169d0 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -192,8 +192,6 @@ - - diff --git a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs deleted file mode 100644 index 0e7cf6453a..0000000000 --- a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Umbraco.Core.Logging; - -namespace Umbraco.Web.WebApi -{ - /// - /// This will format the JSON output for use with AngularJs's approach to JSON Vulnerability attacks - /// - /// - /// See: http://docs.angularjs.org/api/ng.$http (Security considerations) - /// - public class AngularJsonMediaTypeFormatter : JsonMediaTypeFormatter - { - - public const string XsrfPrefix = ")]}',\n"; - - /// - /// This will prepend the special chars to the stream output that angular will strip - /// - /// - /// - /// - /// - /// - /// - public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) - { - if (type == null) throw new ArgumentNullException("type"); - if (writeStream == null) throw new ArgumentNullException("writeStream"); - - var effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers); - - using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding, - //we are only writing a few chars so we don't need to allocate a large buffer - 128, - //this is important! We don't want to close the stream, the base class is in charge of stream management, we just want to write to it. - leaveOpen:true)) - { - //write the special encoding for angular json to the start - // (see: http://docs.angularjs.org/api/ng.$http) - streamWriter.Write(XsrfPrefix); - streamWriter.Flush(); - await base.WriteToStreamAsync(type, value, writeStream, content, transportContext); - } - } - - } -} diff --git a/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs b/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs deleted file mode 100644 index 6a6e63f335..0000000000 --- a/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http.Formatting; -using System.Web.Http.Controllers; - -namespace Umbraco.Web.WebApi -{ - /// - /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. - /// - public class AngularJsonOnlyConfigurationAttribute : Attribute, IControllerConfiguration - { - public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - //remove all json/xml formatters then add our custom one - var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList(); - foreach (var r in toRemove) - { - controllerSettings.Formatters.Remove(r); - } - controllerSettings.Formatters.Add(new AngularJsonMediaTypeFormatter()); - } - } -}