diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs index d744488e9..6f97880e7 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs @@ -21,10 +21,15 @@ namespace Squidex.Domain.Apps.Entities.Contents { private const string DefaultColor = StatusColors.Draft; private readonly IContentWorkflow contentWorkflow; + private readonly IContextProvider contextProvider; - public ContentEnricher(IContentWorkflow contentWorkflow) + public ContentEnricher(IContentWorkflow contentWorkflow, IContextProvider contextProvider) { + Guard.NotNull(contentWorkflow, nameof(contentWorkflow)); + Guard.NotNull(contextProvider, nameof(contextProvider)); + this.contentWorkflow = contentWorkflow; + this.contextProvider = contextProvider; } public async Task EnrichAsync(IContentEntity content) @@ -51,8 +56,12 @@ namespace Squidex.Domain.Apps.Entities.Contents var result = SimpleMapper.Map(content, new ContentEntity()); await ResolveColorAsync(content, result, cache); - await ResolveNextsAsync(content, result); - await ResolveCanUpdateAsync(content, result); + + if (ShouldEnrichWithStatuses()) + { + await ResolveNextsAsync(content, result); + await ResolveCanUpdateAsync(content, result); + } results.Add(result); } @@ -61,6 +70,11 @@ namespace Squidex.Domain.Apps.Entities.Contents } } + private bool ShouldEnrichWithStatuses() + { + return contextProvider.Context.IsFrontendClient || contextProvider.Context.IsResolveFlow(); + } + private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result) { result.CanUpdate = await contentWorkflow.CanUpdateAsync(content); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs new file mode 100644 index 000000000..5daef2c39 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public static class ContextExtensions + { + private static readonly char[] Separators = { ',', ';' }; + + public static bool IsUnpublished(this Context context) + { + return context.Headers.ContainsKey("X-Unpublished"); + } + + public static Context WithUnpublished(this Context context) + { + context.Headers["X-Unpublished"] = "1"; + + return context; + } + + public static bool IsFlatten(this Context context) + { + return context.Headers.ContainsKey("X-Flatten"); + } + + public static Context WithFlatten(this Context context) + { + context.Headers["X-Flatten"] = "1"; + + return context; + } + + public static bool IsResolveFlow(this Context context) + { + return context.Headers.ContainsKey("X-ResolveFlow"); + } + + public static Context WithResolveFlow(this Context context) + { + context.Headers["X-ResolveFlow"] = "1"; + + return context; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Context.cs b/src/Squidex.Domain.Apps.Entities/Context.cs new file mode 100644 index 000000000..69574e088 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Context.cs @@ -0,0 +1,47 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Security.Claims; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Infrastructure.Security; +using Squidex.Shared.Identity; + +namespace Squidex.Domain.Apps.Entities +{ + public sealed class Context + { + public IAppEntity App { get; set; } + + public ClaimsPrincipal User { get; set; } + + public PermissionSet Permissions { get; set; } + + public IDictionary Headers { get; } = new Dictionary(); + + public Context() + { + } + + public Context(ClaimsPrincipal user, IAppEntity app) + { + User = user; + + if (user != null) + { + Permissions = user.Permissions(); + } + + App = app; + } + + public bool IsFrontendClient + { + get { return User != null && User.IsInClient("squidex-frontend"); } + } + } +} diff --git a/src/Squidex.Web/IAppFeature.cs b/src/Squidex.Domain.Apps.Entities/IContextProvider.cs similarity index 65% rename from src/Squidex.Web/IAppFeature.cs rename to src/Squidex.Domain.Apps.Entities/IContextProvider.cs index a798da598..e6e2c67cf 100644 --- a/src/Squidex.Web/IAppFeature.cs +++ b/src/Squidex.Domain.Apps.Entities/IContextProvider.cs @@ -1,16 +1,14 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; - -namespace Squidex.Web +namespace Squidex.Domain.Apps.Entities { - public interface IAppFeature + public interface IContextProvider { - IAppEntity App { get; } + Context Context { get; } } } diff --git a/src/Squidex.Web/ApiController.cs b/src/Squidex.Web/ApiController.cs index 2d4a8d517..30975e46c 100644 --- a/src/Squidex.Web/ApiController.cs +++ b/src/Squidex.Web/ApiController.cs @@ -26,14 +26,14 @@ namespace Squidex.Web { get { - var appFeature = HttpContext.Features.Get(); + var app = HttpContext.Context().App; - if (appFeature == null) + if (app == null) { throw new InvalidOperationException("Not in a app context."); } - return appFeature.App; + return app; } } diff --git a/src/Squidex.Web/ApiPermissionAttribute.cs b/src/Squidex.Web/ApiPermissionAttribute.cs index d515c76ce..e93e1fed2 100644 --- a/src/Squidex.Web/ApiPermissionAttribute.cs +++ b/src/Squidex.Web/ApiPermissionAttribute.cs @@ -36,23 +36,26 @@ namespace Squidex.Web { if (permissionIds.Length > 0) { - var set = context.HttpContext.User.Permissions(); + var permissions = context.HttpContext.Context().Permissions; var hasPermission = false; - foreach (var permissionId in permissionIds) + if (permissions != null) { - var id = permissionId; - - foreach (var routeParam in context.RouteData.Values) + foreach (var permissionId in permissionIds) { - id = id.Replace($"{{{routeParam.Key}}}", routeParam.Value?.ToString()); - } + var id = permissionId; - if (set.Allows(new Permission(id))) - { - hasPermission = true; - break; + foreach (var routeParam in context.RouteData.Values) + { + id = id.Replace($"{{{routeParam.Key}}}", routeParam.Value?.ToString()); + } + + if (permissions.Allows(new Permission(id))) + { + hasPermission = true; + break; + } } } diff --git a/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 0b7d872e5..394ee4aa7 100644 --- a/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -7,7 +7,6 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; @@ -17,20 +16,15 @@ namespace Squidex.Web.CommandMiddlewares { public sealed class EnrichWithAppIdCommandMiddleware : ICommandMiddleware { - private readonly IHttpContextAccessor httpContextAccessor; + private readonly IContextProvider contextProvider; - public EnrichWithAppIdCommandMiddleware(IHttpContextAccessor httpContextAccessor) + public EnrichWithAppIdCommandMiddleware(IContextProvider contextProvider) { - this.httpContextAccessor = httpContextAccessor; + this.contextProvider = contextProvider; } public Task HandleAsync(CommandContext context, Func next) { - if (httpContextAccessor.HttpContext == null) - { - return next(); - } - if (context.Command is IAppCommand appCommand && appCommand.AppId == null) { var appId = GetAppId(); @@ -50,14 +44,14 @@ namespace Squidex.Web.CommandMiddlewares private NamedId GetAppId() { - var appFeature = httpContextAccessor.HttpContext.Features.Get(); + var context = contextProvider.Context; - if (appFeature?.App == null) + if (context.App == null) { throw new InvalidOperationException("Cannot resolve app."); } - return appFeature.App.NamedId(); + return context.App.NamedId(); } } } \ No newline at end of file diff --git a/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index a64798783..b19a992d1 100644 --- a/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -63,16 +63,6 @@ namespace Squidex.Web.CommandMiddlewares appId = appCommand.AppId; } - if (appId == null) - { - var appFeature = actionContextAccessor.ActionContext.HttpContext.Features.Get(); - - if (appFeature?.App != null) - { - appId = appFeature.App.NamedId(); - } - } - if (appId != null) { var routeValues = actionContextAccessor.ActionContext.RouteData.Values; diff --git a/src/Squidex.Web/ContextExtensions.cs b/src/Squidex.Web/ContextExtensions.cs new file mode 100644 index 000000000..d54caf11b --- /dev/null +++ b/src/Squidex.Web/ContextExtensions.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Entities; + +namespace Squidex.Web +{ + public static class ContextExtensions + { + public static Context Context(this HttpContext httpContext) + { + var result = httpContext.Features.Get(); + + if (result == null) + { + httpContext.Features.Set(new Context { User = httpContext.User }); + } + + return result; + } + } +} diff --git a/src/Squidex.Web/ContextProvider.cs b/src/Squidex.Web/ContextProvider.cs new file mode 100644 index 000000000..44f1f0a08 --- /dev/null +++ b/src/Squidex.Web/ContextProvider.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Entities; +using Squidex.Infrastructure; + +namespace Squidex.Web +{ + public sealed class ContextProvider : IContextProvider + { + private readonly IHttpContextAccessor httpContextAccessor; + + public Context Context + { + get { return httpContextAccessor.HttpContext.Context(); } + } + + public ContextProvider(IHttpContextAccessor httpContextAccessor) + { + Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); + + this.httpContextAccessor = httpContextAccessor; + } + } +} diff --git a/src/Squidex.Web/EntityCreatedDto.cs b/src/Squidex.Web/EntityCreatedDto.cs index 95738823d..754f33f77 100644 --- a/src/Squidex.Web/EntityCreatedDto.cs +++ b/src/Squidex.Web/EntityCreatedDto.cs @@ -15,7 +15,7 @@ namespace Squidex.Web [Required] [Display(Description = "Id of the created entity.")] public string Id { get; set; } - + [Display(Description = "The new version of the entity.")] public long Version { get; set; } diff --git a/src/Squidex.Web/ErrorDto.cs b/src/Squidex.Web/ErrorDto.cs index 2d3e8f6be..2a8bda377 100644 --- a/src/Squidex.Web/ErrorDto.cs +++ b/src/Squidex.Web/ErrorDto.cs @@ -14,10 +14,10 @@ namespace Squidex.Web [Required] [Display(Description = "Error message.")] public string Message { get; set; } - + [Display(Description = "Detailed error messages.")] public string[] Details { get; set; } - + [Display(Description = "Status code of the http response.")] public int? StatusCode { get; set; } = 400; } diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs index ab63f14f8..5001c8838 100644 --- a/src/Squidex.Web/PermissionExtensions.cs +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -7,34 +7,14 @@ using Microsoft.AspNetCore.Http; using Squidex.Infrastructure.Security; -using Squidex.Shared.Identity; namespace Squidex.Web { public static class PermissionExtensions { - private sealed class PermissionFeature - { - public PermissionSet Permissions { get; } - - public PermissionFeature(PermissionSet permissions) - { - Permissions = permissions; - } - } - public static PermissionSet Permissions(this HttpContext httpContext) { - var feature = httpContext.Features.Get(); - - if (feature == null) - { - feature = new PermissionFeature(httpContext.User.Permissions()); - - httpContext.Features.Set(feature); - } - - return feature.Permissions; + return httpContext.Context().Permissions; } public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null) diff --git a/src/Squidex.Web/Pipeline/ApiCostsFilter.cs b/src/Squidex.Web/Pipeline/ApiCostsFilter.cs index 0859a26c3..9f1241dad 100644 --- a/src/Squidex.Web/Pipeline/ApiCostsFilter.cs +++ b/src/Squidex.Web/Pipeline/ApiCostsFilter.cs @@ -47,15 +47,15 @@ namespace Squidex.Web.Pipeline { context.HttpContext.Features.Set(FilterDefinition); - var appFeature = context.HttpContext.Features.Get(); + var app = context.HttpContext.Context().App; - if (appFeature?.App != null && FilterDefinition.Weight > 0) + if (app != null && FilterDefinition.Weight > 0) { - var appId = appFeature.App.Id.ToString(); + var appId = app.Id.ToString(); using (Profiler.Trace("CheckUsage")) { - var plan = appPlansProvider.GetPlanForApp(appFeature.App); + var plan = appPlansProvider.GetPlanForApp(app); var usage = await usageTracker.GetMonthlyCallsAsync(appId, DateTime.Today); diff --git a/src/Squidex.Web/Pipeline/AppResolver.cs b/src/Squidex.Web/Pipeline/AppResolver.cs index 97e4e8f1e..ce265199e 100644 --- a/src/Squidex.Web/Pipeline/AppResolver.cs +++ b/src/Squidex.Web/Pipeline/AppResolver.cs @@ -23,16 +23,6 @@ namespace Squidex.Web.Pipeline { private readonly IAppProvider appProvider; - public class AppFeature : IAppFeature - { - public IAppEntity App { get; } - - public AppFeature(IAppEntity app) - { - App = app; - } - } - public AppResolver(IAppProvider appProvider) { this.appProvider = appProvider; @@ -76,15 +66,16 @@ namespace Squidex.Web.Pipeline } } - var set = user.Permissions(); + var permissionset = user.Permissions(); - if (!set.Includes(Permissions.ForApp(Permissions.App, appName))&& !AllowAnonymous(context)) + context.HttpContext.Context().App = app; + context.HttpContext.Context().Permissions = permissions; + + if (!permissionset.Includes(Permissions.ForApp(Permissions.App, appName)) && !AllowAnonymous(context)) { context.Result = new NotFoundResult(); return; } - - context.HttpContext.Features.Set(new AppFeature(app)); } await next(); diff --git a/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs b/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs index c2e3c7006..bb754447f 100644 --- a/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs +++ b/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs @@ -47,9 +47,9 @@ namespace Squidex.Web.Pipeline } } - private static void LogFilters(HttpContext context, IObjectWriter c) + private static void LogFilters(HttpContext httpContext, IObjectWriter c) { - var app = context.Features.Get()?.App; + var app = httpContext.Context().App; if (app != null) { @@ -57,21 +57,21 @@ namespace Squidex.Web.Pipeline c.WriteProperty("appName", app.Name); } - var userId = context.User.OpenIdSubject(); + var userId = httpContext.User.OpenIdSubject(); if (!string.IsNullOrWhiteSpace(userId)) { c.WriteProperty(nameof(userId), userId); } - var clientId = context.User.OpenIdClientId(); + var clientId = httpContext.User.OpenIdClientId(); if (!string.IsNullOrWhiteSpace(clientId)) { c.WriteProperty(nameof(clientId), clientId); } - var costs = context.Features.Get()?.Weight ?? 0; + var costs = httpContext.Features.Get()?.Weight ?? 0; c.WriteProperty(nameof(costs), costs); } diff --git a/src/Squidex.Web/Resource.cs b/src/Squidex.Web/Resource.cs index 31602a124..9719dfd1d 100644 --- a/src/Squidex.Web/Resource.cs +++ b/src/Squidex.Web/Resource.cs @@ -5,10 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; -using Squidex.Infrastructure; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Squidex.Infrastructure; namespace Squidex.Web { diff --git a/src/Squidex.Web/ResourceLink.cs b/src/Squidex.Web/ResourceLink.cs index 2d2b4c0c5..ef54bfa98 100644 --- a/src/Squidex.Web/ResourceLink.cs +++ b/src/Squidex.Web/ResourceLink.cs @@ -17,7 +17,7 @@ namespace Squidex.Web [Required] [Display(Description = "The link method.")] - public string Method { get; set; } + public string Method { get; set; } [Required] [Display(Description = "Additional data about the link.")] diff --git a/src/Squidex.Web/Squidex.Web.csproj b/src/Squidex.Web/Squidex.Web.csproj index bbe645f13..2c9404cb7 100644 --- a/src/Squidex.Web/Squidex.Web.csproj +++ b/src/Squidex.Web/Squidex.Web.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/Squidex.Web/UrlHelperExtensions.cs b/src/Squidex.Web/UrlHelperExtensions.cs index a4bc9280d..8d59cba5f 100644 --- a/src/Squidex.Web/UrlHelperExtensions.cs +++ b/src/Squidex.Web/UrlHelperExtensions.cs @@ -5,8 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.AspNetCore.Mvc; using System; +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable RECS0108 // Warns about static fields in generic types namespace Squidex.Web { @@ -22,7 +24,7 @@ namespace Squidex.Web var name = typeof(T).Name; - if (name.EndsWith(suffix)) + if (name.EndsWith(suffix, StringComparison.Ordinal)) { name = name.Substring(0, name.Length - suffix.Length); }