From 3d2e78e75c95cec8ce6ee75daaf70be493eca3be Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 4 May 2020 16:30:23 +0200 Subject: [PATCH] Feature/api permissions (#518) * Simplify api permissions and improve performance with simple caching. * Schema resolver tests. * IDentity server improvements. * Set base url. * Code cleanup and tests fixed. --- .../Apps/Role.cs | 6 +- backend/src/Squidex.Shared/Permissions.cs | 4 +- backend/src/Squidex.Web/ApiController.cs | 9 + .../src/Squidex.Web/ApiPermissionAttribute.cs | 22 +- backend/src/Squidex.Web/Constants.cs | 2 + backend/src/Squidex.Web/FodyWeavers.xml | 3 + backend/src/Squidex.Web/FodyWeavers.xsd | 26 ++ backend/src/Squidex.Web/ISchemaFeature.cs | 17 ++ .../src/Squidex.Web/PermissionExtensions.cs | 64 ----- .../Pipeline/AccessTokenQueryExtensions.cs | 19 ++ .../Pipeline/AccessTokenQueryMiddleware.cs | 35 +++ .../src/Squidex.Web/Pipeline/SchemaFeature.cs | 22 ++ .../Squidex.Web/Pipeline/SchemaResolver.cs | 66 +++++ backend/src/Squidex.Web/Resources.cs | 242 ++++++++++++++++++ backend/src/Squidex.Web/Squidex.Web.csproj | 5 + .../Config/IdentityServerPathMiddleware.cs | 36 +++ .../Controllers/Apps/AppClientsController.cs | 2 +- .../Apps/AppContributorsController.cs | 2 +- .../Apps/AppLanguagesController.cs | 2 +- .../Controllers/Apps/AppPatternsController.cs | 2 +- .../Controllers/Apps/AppRolesController.cs | 2 +- .../Apps/AppWorkflowsController.cs | 2 +- .../Api/Controllers/Apps/AppsController.cs | 13 +- .../Api/Controllers/Apps/Models/AppDto.cs | 102 ++++---- .../Controllers/Apps/Models/AppLanguageDto.cs | 11 +- .../Apps/Models/AppLanguagesDto.cs | 17 +- .../Api/Controllers/Apps/Models/ClientDto.cs | 11 +- .../Api/Controllers/Apps/Models/ClientsDto.cs | 15 +- .../Controllers/Apps/Models/ContributorDto.cs | 15 +- .../Apps/Models/ContributorsDto.cs | 15 +- .../Api/Controllers/Apps/Models/PatternDto.cs | 17 +- .../Controllers/Apps/Models/PatternsDto.cs | 17 +- .../Api/Controllers/Apps/Models/RoleDto.cs | 13 +- .../Api/Controllers/Apps/Models/RolesDto.cs | 19 +- .../Controllers/Apps/Models/WorkflowDto.cs | 13 +- .../Controllers/Apps/Models/WorkflowsDto.cs | 19 +- .../Assets/AssetContentController.cs | 3 +- .../Assets/AssetFoldersController.cs | 4 +- .../Controllers/Assets/AssetsController.cs | 10 +- .../Api/Controllers/Assets/Models/AssetDto.cs | 32 +-- .../Assets/Models/AssetFolderDto.cs | 21 +- .../Assets/Models/AssetFoldersDto.cs | 17 +- .../Controllers/Assets/Models/AssetsDto.cs | 19 +- .../Controllers/Backups/BackupsController.cs | 2 +- .../Backups/Models/BackupJobDto.cs | 15 +- .../Backups/Models/BackupJobsDto.cs | 17 +- .../Contents/ContentsController.cs | 32 +-- .../Controllers/Contents/Models/ContentDto.cs | 37 +-- .../Contents/Models/ContentsDto.cs | 19 +- .../EventConsumersController.cs | 8 +- .../EventConsumers/Models/EventConsumerDto.cs | 15 +- .../Models/EventConsumersDto.cs | 10 +- .../Api/Controllers/Rules/Models/RuleDto.cs | 34 ++- .../Api/Controllers/Rules/Models/RulesDto.cs | 23 +- .../Api/Controllers/Rules/RulesController.cs | 4 +- .../Controllers/Schemas/Models/FieldDto.cs | 29 ++- .../Schemas/Models/NestedFieldDto.cs | 21 +- .../Schemas/Models/SchemaDetailsDto.cs | 14 +- .../Controllers/Schemas/Models/SchemaDto.cs | 51 ++-- .../Controllers/Schemas/Models/SchemasDto.cs | 17 +- .../Schemas/SchemaFieldsController.cs | 42 +-- .../Controllers/Schemas/SchemasController.cs | 6 +- .../Controllers/Users/Models/ResourcesDto.cs | 21 +- .../Api/Controllers/Users/Models/UserDto.cs | 29 +-- .../Api/Controllers/Users/Models/UsersDto.cs | 15 +- .../Users/UserManagementController.cs | 12 +- .../Api/Controllers/Users/UsersController.cs | 6 +- backend/src/Squidex/Areas/Api/Startup.cs | 3 + .../Authentication/IdentityServerServices.cs | 32 +-- .../src/Squidex/Config/MyIdentityOptions.cs | 2 + backend/src/Squidex/Config/Web/WebServices.cs | 4 + backend/src/Squidex/appsettings.json | 6 +- .../ApiPermissionAttributeTests.cs | 29 ++- .../Pipeline/SchemaResolverTests.cs | 151 +++++++++++ 74 files changed, 1130 insertions(+), 569 deletions(-) create mode 100644 backend/src/Squidex.Web/FodyWeavers.xml create mode 100644 backend/src/Squidex.Web/FodyWeavers.xsd create mode 100644 backend/src/Squidex.Web/ISchemaFeature.cs delete mode 100644 backend/src/Squidex.Web/PermissionExtensions.cs create mode 100644 backend/src/Squidex.Web/Pipeline/AccessTokenQueryExtensions.cs create mode 100644 backend/src/Squidex.Web/Pipeline/AccessTokenQueryMiddleware.cs create mode 100644 backend/src/Squidex.Web/Pipeline/SchemaFeature.cs create mode 100644 backend/src/Squidex.Web/Pipeline/SchemaResolver.cs create mode 100644 backend/src/Squidex.Web/Resources.cs create mode 100644 backend/src/Squidex/Areas/Api/Config/IdentityServerPathMiddleware.cs create mode 100644 backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs index 2cabda6c0..fc63aae92 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs @@ -11,7 +11,7 @@ using System.Diagnostics.Contracts; using System.Linq; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; -using AllPermissions = Squidex.Shared.Permissions; +using P = Squidex.Shared.Permissions; namespace Squidex.Domain.Apps.Core.Apps { @@ -59,12 +59,12 @@ namespace Squidex.Domain.Apps.Core.Apps { var result = new HashSet { - AllPermissions.ForApp(AllPermissions.AppCommon, app) + P.ForApp(P.AppCommon, app) }; if (Permissions.Any()) { - var prefix = AllPermissions.ForApp(AllPermissions.App, app).Id; + var prefix = P.ForApp(P.App, app).Id; foreach (var permission in Permissions) { diff --git a/backend/src/Squidex.Shared/Permissions.cs b/backend/src/Squidex.Shared/Permissions.cs index a2045f761..421ff6ecf 100644 --- a/backend/src/Squidex.Shared/Permissions.cs +++ b/backend/src/Squidex.Shared/Permissions.cs @@ -114,8 +114,8 @@ namespace Squidex.Shared public const string AppRulesDisable = "squidex.apps.{app}.rules.disable"; public const string AppRulesDelete = "squidex.apps.{app}.rules.delete"; - public const string AppSchemas = "squidex.apps.{app}.schemas.{name}"; - public const string AppSchemasCreate = "squidex.apps.{app}.schemas.{name}.create"; + public const string AppSchemas = "squidex.apps.{app}.schemas"; + public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create"; public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{name}.update"; public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{name}.scripts"; public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{name}.publish"; diff --git a/backend/src/Squidex.Web/ApiController.cs b/backend/src/Squidex.Web/ApiController.cs index 035512bfe..1beda4cb6 100644 --- a/backend/src/Squidex.Web/ApiController.cs +++ b/backend/src/Squidex.Web/ApiController.cs @@ -21,6 +21,8 @@ namespace Squidex.Web [ApiModelValidation(false)] public abstract class ApiController : Controller { + private readonly Lazy resources; + protected ICommandBus CommandBus { get; } protected IAppEntity App @@ -38,6 +40,11 @@ namespace Squidex.Web } } + protected Resources Resources + { + get { return resources.Value; } + } + protected Context Context { get { return HttpContext.Context(); } @@ -53,6 +60,8 @@ namespace Squidex.Web Guard.NotNull(commandBus); CommandBus = commandBus; + + resources = new Lazy(() => new Resources(this)); } public override void OnActionExecuting(ActionExecutingContext context) diff --git a/backend/src/Squidex.Web/ApiPermissionAttribute.cs b/backend/src/Squidex.Web/ApiPermissionAttribute.cs index d983054da..edcee1427 100644 --- a/backend/src/Squidex.Web/ApiPermissionAttribute.cs +++ b/backend/src/Squidex.Web/ApiPermissionAttribute.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Squidex.Infrastructure.Security; +using Squidex.Shared; namespace Squidex.Web { @@ -25,7 +26,7 @@ namespace Squidex.Web public ApiPermissionAttribute(params string[] ids) { - AuthenticationSchemes = "Bearer"; + AuthenticationSchemes = Constants.ApiSecurityScheme; permissionIds = ids; } @@ -40,16 +41,25 @@ namespace Squidex.Web if (permissions != null) { - foreach (var permissionId in permissionIds) + foreach (var id in permissionIds) { - var id = permissionId; + var app = context.HttpContext.Features.Get()?.AppId.Name; - foreach (var (key, value) in context.RouteData.Values) + if (string.IsNullOrWhiteSpace(app)) { - id = id.Replace($"{{{key}}}", value?.ToString()); + app = Permission.Any; } - if (permissions.Allows(new Permission(id))) + var schema = context.HttpContext.Features.Get()?.SchemaId.Name; + + if (string.IsNullOrWhiteSpace(schema)) + { + schema = Permission.Any; + } + + var permission = Permissions.ForApp(id, app, schema); + + if (permissions.Allows(permission)) { hasPermission = true; break; diff --git a/backend/src/Squidex.Web/Constants.cs b/backend/src/Squidex.Web/Constants.cs index b27382684..708d49b37 100644 --- a/backend/src/Squidex.Web/Constants.cs +++ b/backend/src/Squidex.Web/Constants.cs @@ -18,6 +18,8 @@ namespace Squidex.Web public static readonly string ApiScope = "squidex-api"; + public static readonly string ApiSecurityScheme = "identity-server"; + public static readonly string OrleansClusterId = "squidex-v2"; public static readonly string OrleansPrefix = "/orleans"; diff --git a/backend/src/Squidex.Web/FodyWeavers.xml b/backend/src/Squidex.Web/FodyWeavers.xml new file mode 100644 index 000000000..6ef705866 --- /dev/null +++ b/backend/src/Squidex.Web/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/backend/src/Squidex.Web/FodyWeavers.xsd b/backend/src/Squidex.Web/FodyWeavers.xsd new file mode 100644 index 000000000..fe819e8ea --- /dev/null +++ b/backend/src/Squidex.Web/FodyWeavers.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/backend/src/Squidex.Web/ISchemaFeature.cs b/backend/src/Squidex.Web/ISchemaFeature.cs new file mode 100644 index 000000000..afa531680 --- /dev/null +++ b/backend/src/Squidex.Web/ISchemaFeature.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Web +{ + public interface ISchemaFeature + { + NamedId SchemaId { get; } + } +} diff --git a/backend/src/Squidex.Web/PermissionExtensions.cs b/backend/src/Squidex.Web/PermissionExtensions.cs deleted file mode 100644 index 59f2bf42a..000000000 --- a/backend/src/Squidex.Web/PermissionExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.AspNetCore.Http; -using Squidex.Infrastructure.Security; -using AllPermissions = Squidex.Shared.Permissions; - -namespace Squidex.Web -{ - public static class PermissionExtensions - { - public static PermissionSet Permissions(this HttpContext httpContext) - { - return httpContext.Context().Permissions; - } - - public static bool Includes(this HttpContext httpContext, Permission permission, PermissionSet? additional = null) - { - return httpContext.Permissions().Includes(permission) || additional?.Includes(permission) == true; - } - - public static bool Includes(this ApiController controller, Permission permission, PermissionSet? additional = null) - { - return controller.HttpContext.Includes(permission) || additional?.Includes(permission) == true; - } - - public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet? additional = null) - { - return httpContext.Permissions().Allows(permission) || additional?.Allows(permission) == true; - } - - public static bool HasPermission(this ApiController controller, Permission permission, PermissionSet? additional = null) - { - return controller.HttpContext.HasPermission(permission) || additional?.Allows(permission) == true; - } - - public static bool HasPermission(this ApiController controller, string id, string app = Permission.Any, string schema = Permission.Any, PermissionSet? additional = null) - { - if (app == Permission.Any) - { - if (controller.RouteData.Values.TryGetValue("app", out var value) && value is string s) - { - app = s; - } - } - - if (schema == Permission.Any) - { - if (controller.RouteData.Values.TryGetValue("name", out var value) && value is string s) - { - schema = s; - } - } - - var permission = AllPermissions.ForApp(id, app, schema); - - return controller.HasPermission(permission, additional); - } - } -} diff --git a/backend/src/Squidex.Web/Pipeline/AccessTokenQueryExtensions.cs b/backend/src/Squidex.Web/Pipeline/AccessTokenQueryExtensions.cs new file mode 100644 index 000000000..7caaf1c80 --- /dev/null +++ b/backend/src/Squidex.Web/Pipeline/AccessTokenQueryExtensions.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Builder; + +namespace Squidex.Web.Pipeline +{ + public static class AccessTokenQueryExtensions + { + public static void UseAccessTokenQueryString(this IApplicationBuilder app) + { + app.UseMiddleware(); + } + } +} diff --git a/backend/src/Squidex.Web/Pipeline/AccessTokenQueryMiddleware.cs b/backend/src/Squidex.Web/Pipeline/AccessTokenQueryMiddleware.cs new file mode 100644 index 000000000..51e09bf01 --- /dev/null +++ b/backend/src/Squidex.Web/Pipeline/AccessTokenQueryMiddleware.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +namespace Squidex.Web.Pipeline +{ + public sealed class AccessTokenQueryMiddleware + { + private readonly RequestDelegate next; + + public AccessTokenQueryMiddleware(RequestDelegate next) + { + this.next = next; + } + + public Task InvokeAsync(HttpContext context) + { + var request = context.Request; + + if (!string.IsNullOrWhiteSpace(request.Headers[HeaderNames.Authorization]) && request.Query.TryGetValue("access_token", out var token)) + { + request.Headers[HeaderNames.Authorization] = token; + } + + return next(context); + } + } +} diff --git a/backend/src/Squidex.Web/Pipeline/SchemaFeature.cs b/backend/src/Squidex.Web/Pipeline/SchemaFeature.cs new file mode 100644 index 000000000..22a786732 --- /dev/null +++ b/backend/src/Squidex.Web/Pipeline/SchemaFeature.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Web.Pipeline +{ + public sealed class SchemaFeature : ISchemaFeature + { + public NamedId SchemaId { get; } + + public SchemaFeature(NamedId schemaId) + { + SchemaId = schemaId; + } + } +} diff --git a/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs b/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs new file mode 100644 index 000000000..c723cb4a0 --- /dev/null +++ b/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Web.Pipeline +{ + public sealed class SchemaResolver : IAsyncActionFilter + { + private readonly IAppProvider appProvider; + + public SchemaResolver(IAppProvider appProvider) + { + Guard.NotNull(appProvider); + + this.appProvider = appProvider; + } + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var appId = context.HttpContext.Features.Get()?.AppId.Id ?? default; + + if (appId != default) + { + var schemaIdOrName = context.RouteData.Values["name"]?.ToString(); + + if (!string.IsNullOrWhiteSpace(schemaIdOrName)) + { + var schema = await GetSchemaAsync(appId, schemaIdOrName); + + if (schema == null) + { + context.Result = new NotFoundResult(); + return; + } + + context.HttpContext.Features.Set(new SchemaFeature(schema.NamedId())); + } + } + + await next(); + } + + private Task GetSchemaAsync(Guid appId, string schemaIdOrName) + { + if (Guid.TryParse(schemaIdOrName, out var id)) + { + return appProvider.GetSchemaAsync(appId, id); + } + else + { + return appProvider.GetSchemaAsync(appId, schemaIdOrName); + } + } + } +} diff --git a/backend/src/Squidex.Web/Resources.cs b/backend/src/Squidex.Web/Resources.cs new file mode 100644 index 000000000..902bde970 --- /dev/null +++ b/backend/src/Squidex.Web/Resources.cs @@ -0,0 +1,242 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Lazy; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Security; +using P = Squidex.Shared.Permissions; + +namespace Squidex.Web +{ + public sealed class Resources + { + private readonly Dictionary<(string, string), bool> schemaPermissions = new Dictionary<(string, string), bool>(); + + // Contents + public bool CanReadContent(string schema) => IsAllowedForSchema(P.AppContentsRead, schema); + + public bool CanCreateContent(string schema) => IsAllowedForSchema(P.AppContentsCreate, schema); + + public bool CanCreateContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionCreate, schema); + + public bool CanDeleteContent(string schema) => IsAllowedForSchema(P.AppContentsDelete, schema); + + public bool CanDeleteContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionDelete, schema); + + public bool CanUpdateContent(string schema) => IsAllowedForSchema(P.AppContentsUpdate, schema); + + public bool CanUpdateContentPartial(string schema) => IsAllowedForSchema(P.AppContentsUpdatePartial, schema); + + // Schemas + public bool CanUpdateSchema(string schema) => IsAllowedForSchema(P.AppSchemasDelete, schema); + + public bool CanUpdateSchemaScripts(string schema) => IsAllowedForSchema(P.AppSchemasScripts, schema); + + public bool CanPublishSchema(string schema) => IsAllowedForSchema(P.AppSchemasPublish, schema); + + public bool CanDeleteSchema(string schema) => IsAllowedForSchema(P.AppSchemasDelete, schema); + + [Lazy] + public bool CanCreateSchema => IsAllowed(P.AppSchemasUpdate); + + // Contributors + [Lazy] + public bool CanAssignContributor => IsAllowed(P.AppContributorsAssign); + + [Lazy] + public bool CanRevokeContributor => IsAllowed(P.AppContributorsRevoke); + + // Workflows + [Lazy] + public bool CanCreateWorkflow => IsAllowed(P.AppWorkflowsCreate); + + [Lazy] + public bool CanUpdateWorkflow => IsAllowed(P.AppWorkflowsUpdate); + + [Lazy] + public bool CanDeleteWorkflow => IsAllowed(P.AppWorkflowsDelete); + + // Roles + [Lazy] + public bool CanCreateRole => IsAllowed(P.AppRolesCreate); + + [Lazy] + public bool CanUpdateRole => IsAllowed(P.AppRolesUpdate); + + [Lazy] + public bool CanDeleteRole => IsAllowed(P.AppRolesDelete); + + // Languages + [Lazy] + public bool CanCreateLanguage => IsAllowed(P.AppLanguagesCreate); + + [Lazy] + public bool CanUpdateLanguage => IsAllowed(P.AppLanguagesUpdate); + + [Lazy] + public bool CanDeleteLanguage => IsAllowed(P.AppLanguagesDelete); + + // Patterns + [Lazy] + public bool CanCreatePattern => IsAllowed(P.AppClientsCreate); + + [Lazy] + public bool CanUpdatePattern => IsAllowed(P.AppPatternsUpdate); + + [Lazy] + public bool CanDeletePattern => IsAllowed(P.AppPatternsDelete); + + // Clients + [Lazy] + public bool CanCreateClient => IsAllowed(P.AppClientsCreate); + + [Lazy] + public bool CanUpdateClient => IsAllowed(P.AppClientsUpdate); + + [Lazy] + public bool CanDeleteClient => IsAllowed(P.AppClientsDelete); + + // Rules + [Lazy] + public bool CanDisableRule => IsAllowed(P.AppRulesDisable); + + [Lazy] + public bool CanCreateRule => IsAllowed(P.AppRulesCreate); + + [Lazy] + public bool CanUpdateRule => IsAllowed(P.AppRulesUpdate); + + [Lazy] + public bool CanDeleteRule => IsAllowed(P.AppRulesDelete); + + [Lazy] + public bool CanReadRuleEvents => IsAllowed(P.AppRulesEvents); + + // Users + [Lazy] + public bool CanReadUsers => IsAllowed(P.AdminUsersRead); + + [Lazy] + public bool CanCreateUser => IsAllowed(P.AdminUsersCreate); + + [Lazy] + public bool CanLockUser => IsAllowed(P.AdminUsersLock); + + [Lazy] + public bool CanUnlockUser => IsAllowed(P.AdminUsersUnlock); + + [Lazy] + public bool CanUpdateUser => IsAllowed(P.AdminUsersUpdate); + + // Assets + [Lazy] + public bool CanUploadAsset => IsAllowed(P.AppAssetsUpload); + + [Lazy] + public bool CanCreateAsset => IsAllowed(P.AppAssetsCreate); + + [Lazy] + public bool CanDeleteAsset => IsAllowed(P.AppAssetsDelete); + + [Lazy] + public bool CanUpdateAsset => IsAllowed(P.AppAssetsUpdate); + + [Lazy] + public bool CanReadAssets => IsAllowed(P.AppAssetsRead); + + // Events + [Lazy] + public bool CanReadEvents => IsAllowed(P.AdminEventsRead); + + [Lazy] + public bool CanManageEvents => IsAllowed(P.AdminEventsManage); + + // Orleans + [Lazy] + public bool CanReadOrleans => IsAllowed(P.AdminOrleans); + + // Backups + [Lazy] + public bool CanRestoreBackup => IsAllowed(P.AdminEventsRead); + + [Lazy] + public bool CanCreateBackup => IsAllowed(P.AppBackupsCreate); + + [Lazy] + public bool CanDeleteBackup => IsAllowed(P.AppBackupsDelete); + + [Lazy] + public string? App => GetAppName(); + + public ApiController Controller { get; } + + public PermissionSet Permissions { get; } + + public Resources(ApiController controller) + { + Controller = controller; + + Permissions = controller.HttpContext.Context().Permissions; + } + + public string Url(Func action, object? values = null) where T : ApiController + { + return Controller.Url(action, values); + } + + public bool IsUser(string userId) + { + var subject = Controller.User.OpenIdSubject(); + + return string.Equals(subject, userId, StringComparison.OrdinalIgnoreCase); + } + + public bool Includes(Permission permission, PermissionSet? additional = null) + { + return Permissions.Includes(permission) || additional?.Includes(permission) == true; + } + + public bool IsAllowedForSchema(string id, string schema) + { + return schemaPermissions.GetOrAdd((id, schema), k => IsAllowed(k.Item1, "*", k.Item2)); + } + + public bool IsAllowed(string id, string app = Permission.Any, string schema = Permission.Any, PermissionSet? additional = null) + { + if (app == Permission.Any) + { + var falback = App; + + if (!string.IsNullOrWhiteSpace(falback)) + { + app = falback; + } + } + + if (schema == Permission.Any) + { + var falback = Controller.HttpContext.Features.Get()?.SchemaId.Name; + + if (!string.IsNullOrWhiteSpace(falback)) + { + schema = falback; + } + } + + var permission = P.ForApp(id, app, schema); + + return Permissions.Allows(permission) || additional?.Allows(permission) == true; + } + + private string? GetAppName() + { + return Controller.HttpContext.Context().App?.Name; + } + } +} diff --git a/backend/src/Squidex.Web/Squidex.Web.csproj b/backend/src/Squidex.Web/Squidex.Web.csproj index 9f15fb053..25683ea7c 100644 --- a/backend/src/Squidex.Web/Squidex.Web.csproj +++ b/backend/src/Squidex.Web/Squidex.Web.csproj @@ -12,6 +12,11 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/backend/src/Squidex/Areas/Api/Config/IdentityServerPathMiddleware.cs b/backend/src/Squidex/Areas/Api/Config/IdentityServerPathMiddleware.cs new file mode 100644 index 000000000..345e06fce --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Config/IdentityServerPathMiddleware.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using IdentityServer4.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Squidex.Web; + +namespace Squidex.Areas.Api.Config +{ + public sealed class IdentityServerPathMiddleware + { + private readonly UrlsOptions urlsOptions; + private readonly RequestDelegate next; + + public IdentityServerPathMiddleware(IOptions urlsOptions, RequestDelegate next) + { + this.urlsOptions = urlsOptions.Value; + + this.next = next; + } + + public Task InvokeAsync(HttpContext context) + { + context.SetIdentityServerOrigin(urlsOptions.BaseUrl); + context.SetIdentityServerBasePath(Constants.IdentityServerPrefix); + + return next(context); + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index 3b3039687..d3efa2fcc 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -150,7 +150,7 @@ namespace Squidex.Areas.Api.Controllers.Apps private ClientsDto GetResponse(IAppEntity app) { - return ClientsDto.FromApp(app, this); + return ClientsDto.FromApp(app, Resources); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index edb158c53..f9699bee7 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -125,7 +125,7 @@ namespace Squidex.Areas.Api.Controllers.Apps private Task GetResponseAsync(IAppEntity app, bool invited) { - return ContributorsDto.FromAppAsync(app, this, userResolver, appPlansProvider, invited); + return ContributorsDto.FromAppAsync(app, Resources, userResolver, appPlansProvider, invited); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index 96e1b166a..2ce73190e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -141,7 +141,7 @@ namespace Squidex.Areas.Api.Controllers.Apps private AppLanguagesDto GetResponse(IAppEntity result) { - return AppLanguagesDto.FromApp(result, this); + return AppLanguagesDto.FromApp(result, Resources); } private static Language ParseLanguage(string language) diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 2102836c8..da84d6945 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -144,7 +144,7 @@ namespace Squidex.Areas.Api.Controllers.Apps private PatternsDto GetResponse(IAppEntity result) { - return PatternsDto.FromApp(result, this); + return PatternsDto.FromApp(result, Resources); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs index a40cee38e..d378a7fb5 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs @@ -167,7 +167,7 @@ namespace Squidex.Areas.Api.Controllers.Apps private RolesDto GetResponse(IAppEntity result) { - return RolesDto.FromApp(result, this); + return RolesDto.FromApp(result, Resources); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs index 09c8c93d1..5a5099d4e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs @@ -142,7 +142,7 @@ namespace Squidex.Areas.Api.Controllers.Apps private async Task GetResponse(IAppEntity result) { - return await WorkflowsDto.FromAppAsync(workflowsValidator, result, this); + return await WorkflowsDto.FromAppAsync(workflowsValidator, result, Resources); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 40f61c8e9..32392e1df 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -73,15 +73,13 @@ namespace Squidex.Areas.Api.Controllers.Apps public async Task GetApps() { var userOrClientId = HttpContext.User.UserOrClientId()!; - var userPermissions = HttpContext.Permissions(); + var userPermissions = Resources.Permissions; var apps = await appProvider.GetUserAppsAsync(userOrClientId, userPermissions); var response = Deferred.Response(() => { - var userPermissions = HttpContext.Permissions(); - - return apps.OrderBy(x => x.Name).Select(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider, this)).ToArray(); + return apps.OrderBy(x => x.Name).Select(a => AppDto.FromApp(a, userOrClientId, appPlansProvider, Resources)).ToArray(); }); Response.Headers[HeaderNames.ETag] = apps.ToEtag(); @@ -107,9 +105,9 @@ namespace Squidex.Areas.Api.Controllers.Apps var response = Deferred.Response(() => { var userOrClientId = HttpContext.User.UserOrClientId()!; - var userPermissions = HttpContext.Permissions(); + var userPermissions = Resources.Permissions; - return AppDto.FromApp(App, userOrClientId, userPermissions, appPlansProvider, this); + return AppDto.FromApp(App, userOrClientId, appPlansProvider, Resources); }); Response.Headers[HeaderNames.ETag] = App.ToEtag(); @@ -299,10 +297,9 @@ namespace Squidex.Areas.Api.Controllers.Apps var context = await CommandBus.PublishAsync(command); var userOrClientId = HttpContext.User.UserOrClientId()!; - var userPermissions = HttpContext.Permissions(); var result = context.Result(); - var response = AppDto.FromApp(result, userOrClientId, userPermissions, appPlansProvider, this); + var response = AppDto.FromApp(result, userOrClientId, appPlansProvider, Resources); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index d29966efb..2224b2e9f 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -19,9 +19,8 @@ using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; -using Squidex.Shared; using Squidex.Web; -using AllPermissions = Squidex.Shared.Permissions; +using P = Squidex.Shared.Permissions; #pragma warning disable RECS0033 // Convert 'if' to '||' expression @@ -91,31 +90,31 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public string? PlanUpgrade { get; set; } - public static AppDto FromApp(IAppEntity app, string userId, PermissionSet userPermissions, IAppPlansProvider plans, ApiController controller) + public static AppDto FromApp(IAppEntity app, string userId, IAppPlansProvider plans, Resources resources) { - var permissions = GetPermissions(app, userId, userPermissions); + var permissions = GetPermissions(app, userId); var result = SimpleMapper.Map(app, new AppDto()); result.Permissions = permissions.ToIds(); - if (controller.Includes(AllPermissions.ForApp(AllPermissions.AppApi, app.Name), permissions)) + if (resources.Includes(P.ForApp(P.AppApi, app.Name), permissions)) { result.CanAccessApi = true; } - if (controller.Includes(AllPermissions.ForApp(AllPermissions.AppContents, app.Name), permissions)) + if (resources.Includes(P.ForApp(P.AppContents, app.Name), permissions)) { result.CanAccessContent = true; } - result.SetPlan(app, plans, controller, permissions); - result.SetImage(app, controller); + result.SetPlan(app, plans, resources, permissions); + result.SetImage(app, resources); - return result.CreateLinks(controller, permissions); + return result.CreateLinks(resources, permissions); } - private static PermissionSet GetPermissions(IAppEntity app, string userId, PermissionSet userPermissions) + private static PermissionSet GetPermissions(IAppEntity app, string userId) { var permissions = new List(); @@ -124,17 +123,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models permissions.AddRange(role.Permissions); } - if (userPermissions != null) - { - permissions.AddRange(userPermissions.ToAppPermissions(app.Name)); - } - return new PermissionSet(permissions); } - private void SetPlan(IAppEntity app, IAppPlansProvider plans, ApiController controller, PermissionSet permissions) + private void SetPlan(IAppEntity app, IAppPlansProvider plans, Resources resources, PermissionSet permissions) { - if (controller.HasPermission(AllPermissions.AppPlansChange, app.Name, additional: permissions)) + if (resources.IsAllowed(P.AppPlansChange, app.Name, additional: permissions)) { PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; } @@ -142,100 +136,100 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models PlanName = plans.GetPlanForApp(app).Plan.Name; } - private void SetImage(IAppEntity app, ApiController controller) + private void SetImage(IAppEntity app, Resources resources) { if (app.Image != null) { - AddGetLink("image", controller.Url(x => nameof(x.GetImage), new { app = app.Name })); + AddGetLink("image", resources.Url(x => nameof(x.GetImage), new { app = app.Name })); } } - private AppDto CreateLinks(ApiController controller, PermissionSet permissions) + private AppDto CreateLinks(Resources resources, PermissionSet permissions) { var values = new { app = Name }; - AddGetLink("ping", controller.Url(x => nameof(x.GetAppPing), values)); + AddGetLink("ping", resources.Url(x => nameof(x.GetAppPing), values)); - if (controller.HasPermission(AllPermissions.AppDelete, Name, additional: permissions)) + if (resources.IsAllowed(P.AppDelete, Name, additional: permissions)) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteApp), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteApp), values)); } - if (controller.HasPermission(AllPermissions.AppUpdateGeneral, Name, additional: permissions)) + if (resources.IsAllowed(P.AppUpdateGeneral, Name, additional: permissions)) { - AddPutLink("update", controller.Url(x => nameof(x.UpdateApp), values)); + AddPutLink("update", resources.Url(x => nameof(x.UpdateApp), values)); } - if (controller.HasPermission(AllPermissions.AppUpdateImage, Name, additional: permissions)) + if (resources.IsAllowed(P.AppUpdateImage, Name, additional: permissions)) { - AddPostLink("image/upload", controller.Url(x => nameof(x.UploadImage), values)); + AddPostLink("image/upload", resources.Url(x => nameof(x.UploadImage), values)); - AddDeleteLink("image/delete", controller.Url(x => nameof(x.DeleteImage), values)); + AddDeleteLink("image/delete", resources.Url(x => nameof(x.DeleteImage), values)); } - if (controller.HasPermission(AllPermissions.AppAssetsRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppAssetsRead, Name, additional: permissions)) { - AddGetLink("assets", controller.Url(x => nameof(x.GetAssets), values)); + AddGetLink("assets", resources.Url(x => nameof(x.GetAssets), values)); } - if (controller.HasPermission(AllPermissions.AppBackupsRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppBackupsRead, Name, additional: permissions)) { - AddGetLink("backups", controller.Url(x => nameof(x.GetBackups), values)); + AddGetLink("backups", resources.Url(x => nameof(x.GetBackups), values)); } - if (controller.HasPermission(AllPermissions.AppClientsRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppClientsRead, Name, additional: permissions)) { - AddGetLink("clients", controller.Url(x => nameof(x.GetClients), values)); + AddGetLink("clients", resources.Url(x => nameof(x.GetClients), values)); } - if (controller.HasPermission(AllPermissions.AppContributorsRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppContributorsRead, Name, additional: permissions)) { - AddGetLink("contributors", controller.Url(x => nameof(x.GetContributors), values)); + AddGetLink("contributors", resources.Url(x => nameof(x.GetContributors), values)); } - if (controller.HasPermission(AllPermissions.AppCommon, Name, additional: permissions)) + if (resources.IsAllowed(P.AppCommon, Name, additional: permissions)) { - AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages), values)); + AddGetLink("languages", resources.Url(x => nameof(x.GetLanguages), values)); } - if (controller.HasPermission(AllPermissions.AppCommon, Name, additional: permissions)) + if (resources.IsAllowed(P.AppCommon, Name, additional: permissions)) { - AddGetLink("patterns", controller.Url(x => nameof(x.GetPatterns), values)); + AddGetLink("patterns", resources.Url(x => nameof(x.GetPatterns), values)); } - if (controller.HasPermission(AllPermissions.AppPlansRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppPlansRead, Name, additional: permissions)) { - AddGetLink("plans", controller.Url(x => nameof(x.GetPlans), values)); + AddGetLink("plans", resources.Url(x => nameof(x.GetPlans), values)); } - if (controller.HasPermission(AllPermissions.AppRolesRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppRolesRead, Name, additional: permissions)) { - AddGetLink("roles", controller.Url(x => nameof(x.GetRoles), values)); + AddGetLink("roles", resources.Url(x => nameof(x.GetRoles), values)); } - if (controller.HasPermission(AllPermissions.AppRulesRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppRulesRead, Name, additional: permissions)) { - AddGetLink("rules", controller.Url(x => nameof(x.GetRules), values)); + AddGetLink("rules", resources.Url(x => nameof(x.GetRules), values)); } - if (controller.HasPermission(AllPermissions.AppCommon, Name, additional: permissions)) + if (resources.IsAllowed(P.AppCommon, Name, additional: permissions)) { - AddGetLink("schemas", controller.Url(x => nameof(x.GetSchemas), values)); + AddGetLink("schemas", resources.Url(x => nameof(x.GetSchemas), values)); } - if (controller.HasPermission(AllPermissions.AppWorkflowsRead, Name, additional: permissions)) + if (resources.IsAllowed(P.AppWorkflowsRead, Name, additional: permissions)) { - AddGetLink("workflows", controller.Url(x => nameof(x.GetWorkflows), values)); + AddGetLink("workflows", resources.Url(x => nameof(x.GetWorkflows), values)); } - if (controller.HasPermission(AllPermissions.AppSchemasCreate, Name, additional: permissions)) + if (resources.IsAllowed(P.AppSchemasCreate, Name, additional: permissions)) { - AddPostLink("schemas/create", controller.Url(x => nameof(x.PostSchema), values)); + AddPostLink("schemas/create", resources.Url(x => nameof(x.PostSchema), values)); } - if (controller.HasPermission(AllPermissions.AppAssetsCreate, Name, additional: permissions)) + if (resources.IsAllowed(P.AppAssetsCreate, Name, additional: permissions)) { - AddPostLink("assets/create", controller.Url(x => nameof(x.PostSchema), values)); + AddPostLink("assets/create", resources.Url(x => nameof(x.PostSchema), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs index 58d1b6b03..694fddc97 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs @@ -10,7 +10,6 @@ using System.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -59,20 +58,20 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models return result; } - public AppLanguageDto WithLinks(ApiController controller, IAppEntity app) + public AppLanguageDto WithLinks(Resources resources, IAppEntity app) { var values = new { app = app.Name, language = Iso2Code }; if (!IsMaster) { - if (controller.HasPermission(Permissions.AppLanguagesUpdate, app.Name)) + if (resources.CanUpdateLanguage) { - AddPutLink("update", controller.Url(x => nameof(x.PutLanguage), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutLanguage), values)); } - if (controller.HasPermission(Permissions.AppLanguagesDelete, app.Name) && app.LanguagesConfig.Languages.Count > 1) + if (resources.CanDeleteLanguage && app.LanguagesConfig.Languages.Count > 1) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteLanguage), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteLanguage), values)); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs index 90f6c8fec..5a84af22d 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs @@ -8,7 +8,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -21,7 +20,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models [Required] public AppLanguageDto[] Items { get; set; } - public static AppLanguagesDto FromApp(IAppEntity app, ApiController controller) + public static AppLanguagesDto FromApp(IAppEntity app, Resources resources) { var config = app.LanguagesConfig; @@ -29,23 +28,23 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models { Items = config.Languages .Select(x => AppLanguageDto.FromLanguage(x.Key, x.Value, config)) - .Select(x => x.WithLinks(controller, app)) + .Select(x => x.WithLinks(resources, app)) .OrderByDescending(x => x.IsMaster).ThenBy(x => x.Iso2Code) .ToArray() }; - return result.CreateLinks(controller, app.Name); + return result.CreateLinks(resources); } - private AppLanguagesDto CreateLinks(ApiController controller, string app) + private AppLanguagesDto CreateLinks(Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetLanguages), values)); + AddSelfLink(resources.Url(x => nameof(x.GetLanguages), values)); - if (controller.HasPermission(Permissions.AppLanguagesCreate, app)) + if (resources.CanCreateLanguage) { - AddPostLink("create", controller.Url(x => nameof(x.PostLanguage), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostLanguage), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs index d00146290..26cbff699 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs @@ -8,7 +8,6 @@ using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -45,18 +44,18 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models return result; } - public ClientDto WithLinks(ApiController controller, string app) + public ClientDto WithLinks(Resources resources, string app) { var values = new { app, id = Id }; - if (controller.HasPermission(Permissions.AppClientsUpdate, app)) + if (resources.CanUpdateClient) { - AddPutLink("update", controller.Url(x => nameof(x.PutClient), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutClient), values)); } - if (controller.HasPermission(Permissions.AppClientsDelete, app)) + if (resources.CanDeleteClient) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteClient), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteClient), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs index 1d456251e..3c5c81b66 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs @@ -8,7 +8,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -21,7 +20,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models [Required] public ClientDto[] Items { get; set; } - public static ClientsDto FromApp(IAppEntity app, ApiController controller) + public static ClientsDto FromApp(IAppEntity app, Resources resources) { var appName = app.Name; @@ -30,22 +29,22 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models Items = app.Clients .Select(x => ClientDto.FromClient(x.Key, x.Value)) - .Select(x => x.WithLinks(controller, appName)) + .Select(x => x.WithLinks(resources, appName)) .ToArray() }; - return result.CreateLinks(controller, appName); + return result.CreateLinks(resources, appName); } - private ClientsDto CreateLinks(ApiController controller, string app) + private ClientsDto CreateLinks(Resources resources, string app) { var values = new { app }; - AddSelfLink(controller.Url(x => nameof(x.GetClients), values)); + AddSelfLink(resources.Url(x => nameof(x.GetClients), values)); - if (controller.HasPermission(Permissions.AppClientsCreate, app)) + if (resources.CanCreateClient) { - AddPostLink("create", controller.Url(x => nameof(x.PostClient), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostClient), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs index cfdaf7a04..56f4714a0 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Squidex.Shared; using Squidex.Shared.Users; using Squidex.Web; @@ -62,18 +61,20 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models return this; } - public ContributorDto WithLinks(ApiController controller, string app) + public ContributorDto WithLinks(Resources resources) { - if (!controller.IsUser(ContributorId)) + if (!resources.IsUser(ContributorId)) { - if (controller.HasPermission(Permissions.AppContributorsAssign, app)) + var app = resources.App; + + if (resources.CanAssignContributor) { - AddPostLink("update", controller.Url(x => nameof(x.PostContributor), new { app })); + AddPostLink("update", resources.Url(x => nameof(x.PostContributor), new { app })); } - if (controller.HasPermission(Permissions.AppContributorsRevoke, app)) + if (resources.CanRevokeContributor) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContributor), new { app, id = ContributorId })); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteContributor), new { app, id = ContributorId })); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs index 60f976021..938cea57d 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Plans; -using Squidex.Shared; using Squidex.Shared.Users; using Squidex.Web; @@ -36,7 +35,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models [JsonProperty("_meta")] public ContributorsMetadata Metadata { get; set; } - public static async Task FromAppAsync(IAppEntity app, ApiController controller, IUserResolver userResolver, IAppPlansProvider plans, bool invited) + public static async Task FromAppAsync(IAppEntity app, Resources resources, IUserResolver userResolver, IAppPlansProvider plans, bool invited) { var users = await userResolver.QueryManyAsync(app.Contributors.Keys.ToArray()); @@ -46,7 +45,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models app.Contributors .Select(x => ContributorDto.FromIdAndRole(x.Key, x.Value)) .Select(x => x.WithUser(users)) - .Select(x => x.WithLinks(controller, app.Name)) + .Select(x => x.WithLinks(resources)) .OrderBy(x => x.ContributorName) .ToArray() }; @@ -54,7 +53,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models result.WithInvited(invited); result.WithPlan(app, plans); - return result.CreateLinks(controller, app.Name); + return result.CreateLinks(resources, app.Name); } private void WithPlan(IAppEntity app, IAppPlansProvider plans) @@ -73,15 +72,15 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models } } - private ContributorsDto CreateLinks(ApiController controller, string app) + private ContributorsDto CreateLinks(Resources resources, string app) { var values = new { app }; - AddSelfLink(controller.Url(x => nameof(x.GetContributors), values)); + AddSelfLink(resources.Url(x => nameof(x.GetContributors), values)); - if (controller.HasPermission(Permissions.AppContributorsAssign, app) && (MaxContributors < 0 || Items.Length < MaxContributors)) + if (resources.CanAssignContributor && (MaxContributors < 0 || Items.Length < MaxContributors)) { - AddPostLink("create", controller.Url(x => nameof(x.PostContributor), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostContributor), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs index ae3ed6355..39761d261 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs @@ -9,7 +9,6 @@ using System; using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -38,25 +37,25 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public string? Message { get; set; } - public static PatternDto FromPattern(Guid id, AppPattern pattern, ApiController controller, string app) + public static PatternDto FromPattern(Guid id, AppPattern pattern, Resources resources) { var result = SimpleMapper.Map(pattern, new PatternDto { Id = id }); - return result.CreateLinks(controller, app); + return result.CreateLinks(resources); } - private PatternDto CreateLinks(ApiController controller, string app) + private PatternDto CreateLinks(Resources resources) { - var values = new { app, id = Id }; + var values = new { app = resources.App, id = Id }; - if (controller.HasPermission(Permissions.AppPatternsUpdate, app)) + if (resources.CanUpdatePattern) { - AddPutLink("update", controller.Url(x => nameof(x.PutPattern), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutPattern), values)); } - if (controller.HasPermission(Permissions.AppPatternsDelete, app)) + if (resources.CanDeletePattern) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeletePattern), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeletePattern), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs index ca9a2605a..e209aa4ac 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs @@ -8,7 +8,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -21,25 +20,25 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models [Required] public PatternDto[] Items { get; set; } - public static PatternsDto FromApp(IAppEntity app, ApiController controller) + public static PatternsDto FromApp(IAppEntity app, Resources resources) { var result = new PatternsDto { - Items = app.Patterns.Select(x => PatternDto.FromPattern(x.Key, x.Value, controller, app.Name)).ToArray() + Items = app.Patterns.Select(x => PatternDto.FromPattern(x.Key, x.Value, resources)).ToArray() }; - return result.CreateLinks(controller, app.Name); + return result.CreateLinks(resources); } - private PatternsDto CreateLinks(ApiController controller, string app) + private PatternsDto CreateLinks(Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetPatterns), values)); + AddSelfLink(resources.Url(x => nameof(x.GetPatterns), values)); - if (controller.HasPermission(Permissions.AppPatternsCreate, app)) + if (resources.CanCreatePattern) { - AddPostLink("create", controller.Url(x => nameof(x.PostPattern), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostPattern), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs index c8b5e1f06..ff7387f38 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs @@ -11,7 +11,6 @@ using System.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Web; -using AllPermissions = Squidex.Shared.Permissions; namespace Squidex.Areas.Api.Controllers.Apps.Models { @@ -70,20 +69,20 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models return app.Clients.Count(x => role.Equals(x.Value.Role)); } - public RoleDto WithLinks(ApiController controller, string app) + public RoleDto WithLinks(Resources resources) { - var values = new { app, name = Name }; + var values = new { app = resources.App, name = Name }; if (!IsDefaultRole) { - if (controller.HasPermission(AllPermissions.AppRolesUpdate, app)) + if (resources.CanUpdateRole) { - AddPutLink("update", controller.Url(x => nameof(x.PutRole), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutRole), values)); } - if (controller.HasPermission(AllPermissions.AppRolesDelete, app) && NumClients == 0 && NumContributors == 0) + if (resources.CanDeleteRole && NumClients == 0 && NumContributors == 0) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteRole), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteRole), values)); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs index a2a6cb3b2..72e813696 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs @@ -8,7 +8,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -21,32 +20,30 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models [Required] public RoleDto[] Items { get; set; } - public static RolesDto FromApp(IAppEntity app, ApiController controller) + public static RolesDto FromApp(IAppEntity app, Resources resources) { - var appName = app.Name; - var result = new RolesDto { Items = app.Roles.All .Select(x => RoleDto.FromRole(x, app)) - .Select(x => x.WithLinks(controller, appName)) + .Select(x => x.WithLinks(resources)) .OrderBy(x => x.Name) .ToArray() }; - return result.CreateLinks(controller, appName); + return result.CreateLinks(resources); } - private RolesDto CreateLinks(ApiController controller, string app) + private RolesDto CreateLinks(Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetRoles), values)); + AddSelfLink(resources.Url(x => nameof(x.GetRoles), values)); - if (controller.HasPermission(Permissions.AppRolesCreate, app)) + if (resources.CanCreateRole) { - AddPostLink("create", controller.Url(x => nameof(x.PostRole), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostRole), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowDto.cs index 72047e3e9..69351c835 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowDto.cs @@ -11,7 +11,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -57,18 +56,18 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models return result; } - public WorkflowDto WithLinks(ApiController controller, string app) + public WorkflowDto WithLinks(Resources resources) { - var values = new { app, id = Id }; + var values = new { app = resources.App, id = Id }; - if (controller.HasPermission(Permissions.AppWorkflowsUpdate, app)) + if (resources.CanUpdateWorkflow) { - AddPutLink("update", controller.Url(x => nameof(x.PutWorkflow), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutWorkflow), values)); } - if (controller.HasPermission(Permissions.AppWorkflowsDelete, app)) + if (resources.CanDeleteWorkflow) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteWorkflow), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteWorkflow), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs index d95aa9a3d..52e65188b 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -29,16 +28,14 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models [Required] public string[] Errors { get; set; } - public static async Task FromAppAsync(IWorkflowsValidator workflowsValidator, IAppEntity app, ApiController controller) + public static async Task FromAppAsync(IWorkflowsValidator workflowsValidator, IAppEntity app, Resources resources) { - var appName = app.Name; - var result = new WorkflowsDto { Items = app.Workflows .Select(x => WorkflowDto.FromWorkflow(x.Key, x.Value)) - .Select(x => x.WithLinks(controller, appName)) + .Select(x => x.WithLinks(resources)) .ToArray() }; @@ -46,18 +43,18 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models result.Errors = errors.ToArray(); - return result.CreateLinks(controller, appName); + return result.CreateLinks(resources); } - private WorkflowsDto CreateLinks(ApiController controller, string app) + private WorkflowsDto CreateLinks(Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetWorkflows), values)); + AddSelfLink(resources.Url(x => nameof(x.GetWorkflows), values)); - if (controller.HasPermission(Permissions.AppWorkflowsCreate, app)) + if (resources.CanCreateWorkflow) { - AddPostLink("create", controller.Url(x => nameof(x.PostWorkflow), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostWorkflow), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index 5673038c0..288f5fc4c 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -20,7 +20,6 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; -using Squidex.Shared; using Squidex.Web; #pragma warning disable 1573 @@ -119,7 +118,7 @@ namespace Squidex.Areas.Api.Controllers.Assets return NotFound(); } - if (asset.IsProtected && !this.HasPermission(Permissions.AppAssetsRead)) + if (asset.IsProtected && !Resources.CanReadEvents) { return StatusCode(403); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs index d73932936..91ad76841 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs @@ -55,7 +55,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var response = Deferred.Response(() => { - return AssetFoldersDto.FromAssets(assetFolders, this, app); + return AssetFoldersDto.FromAssets(assetFolders, Resources); }); Response.Headers[HeaderNames.ETag] = assetFolders.ToEtag(); @@ -162,7 +162,7 @@ namespace Squidex.Areas.Api.Controllers.Assets { var context = await CommandBus.PublishAsync(command); - return AssetFolderDto.FromAssetFolder(context.Result(), this, app); + return AssetFolderDto.FromAssetFolder(context.Result(), Resources); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index cfb6bccf3..a6312f470 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -100,7 +100,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var response = Deferred.Response(() => { - return AssetsDto.FromAssets(assets, this, app); + return AssetsDto.FromAssets(assets, Resources); }); return Ok(response); @@ -129,7 +129,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var response = Deferred.Response(() => { - return AssetsDto.FromAssets(assets, this, app); + return AssetsDto.FromAssets(assets, Resources); }); return Ok(response); @@ -160,7 +160,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var response = Deferred.Response(() => { - return AssetDto.FromAsset(asset, this, app); + return AssetDto.FromAsset(asset, Resources); }); return Ok(response); @@ -304,11 +304,11 @@ namespace Squidex.Areas.Api.Controllers.Assets if (context.PlainResult is AssetCreatedResult created) { - return AssetDto.FromAsset(created.Asset, this, app, created.IsDuplicate); + return AssetDto.FromAsset(created.Asset, Resources, created.IsDuplicate); } else { - return AssetDto.FromAsset(context.Result(), this, app); + return AssetDto.FromAsset(context.Result(), Resources); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index 552b3d40d..826727184 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs @@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models @@ -158,7 +157,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models get { return Metadata.GetPixelHeight(); } } - public static AssetDto FromAsset(IEnrichedAssetEntity asset, ApiController controller, string app, bool isDuplicate = false) + public static AssetDto FromAsset(IEnrichedAssetEntity asset, Resources resources, bool isDuplicate = false) { var response = SimpleMapper.Map(asset, new AssetDto()); @@ -174,42 +173,45 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models }; } - return CreateLinks(response, controller, app); + return CreateLinks(response, resources); } - private static AssetDto CreateLinks(AssetDto response, ApiController controller, string app) + private static AssetDto CreateLinks(AssetDto response, Resources resources) { + var app = resources.App; + var values = new { app, id = response.Id }; - response.AddSelfLink(controller.Url(x => nameof(x.GetAsset), values)); + response.AddSelfLink(resources.Url(x => nameof(x.GetAsset), values)); - if (controller.HasPermission(Permissions.AppAssetsUpdate)) + if (resources.CanUpdateAsset) { - response.AddPutLink("update", controller.Url(x => nameof(x.PutAsset), values)); + response.AddPutLink("update", resources.Url(x => nameof(x.PutAsset), values)); - response.AddPutLink("move", controller.Url(x => nameof(x.PutAssetParent), values)); + response.AddPutLink("move", resources.Url(x => nameof(x.PutAssetParent), values)); } - if (controller.HasPermission(Permissions.AppAssetsUpload)) + if (resources.CanUploadAsset) { - response.AddPutLink("upload", controller.Url(x => nameof(x.PutAssetContent), values)); + response.AddPutLink("upload", resources.Url(x => nameof(x.PutAssetContent), values)); } - if (controller.HasPermission(Permissions.AppAssetsDelete)) + if (resources.CanDeleteAsset) { - response.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteAsset), values)); + response.AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteAsset), values)); } var version = response.FileVersion; if (!string.IsNullOrWhiteSpace(response.Slug)) { - response.AddGetLink("content", controller.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Id, more = response.Slug })); - response.AddGetLink("content/slug", controller.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Slug })); + response.AddGetLink("content", resources.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Id, more = response.Slug })); + + response.AddGetLink("content/slug", resources.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Slug })); } else { - response.AddGetLink("content", controller.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Id })); + response.AddGetLink("content", resources.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Id })); } return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs index a9ea2f08d..0fadd5d39 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs @@ -9,7 +9,6 @@ using System; using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models @@ -37,29 +36,29 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models /// public long Version { get; set; } - public static AssetFolderDto FromAssetFolder(IAssetFolderEntity asset, ApiController controller, string app) + public static AssetFolderDto FromAssetFolder(IAssetFolderEntity asset, Resources resources) { var response = SimpleMapper.Map(asset, new AssetFolderDto()); - return CreateLinks(response, controller, app); + return CreateLinks(response, resources); } - private static AssetFolderDto CreateLinks(AssetFolderDto response, ApiController controller, string app) + private static AssetFolderDto CreateLinks(AssetFolderDto response, Resources resources) { - var values = new { app, id = response.Id }; + var values = new { app = resources.App, id = response.Id }; - response.AddSelfLink(controller.Url(x => nameof(x.GetAsset), values)); + response.AddSelfLink(resources.Url(x => nameof(x.GetAsset), values)); - if (controller.HasPermission(Permissions.AppAssetsUpdate)) + if (resources.CanUpdateAsset) { - response.AddPutLink("update", controller.Url(x => nameof(x.PutAssetFolder), values)); + response.AddPutLink("update", resources.Url(x => nameof(x.PutAssetFolder), values)); - response.AddPutLink("move", controller.Url(x => nameof(x.PutAssetFolderParent), values)); + response.AddPutLink("move", resources.Url(x => nameof(x.PutAssetFolderParent), values)); } - if (controller.HasPermission(Permissions.AppAssetsUpdate)) + if (resources.CanUpdateAsset) { - response.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteAssetFolder), values)); + response.AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteAssetFolder), values)); } return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs index 264296dd4..7c8de65fb 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs @@ -9,7 +9,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models @@ -27,26 +26,26 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models [Required] public AssetFolderDto[] Items { get; set; } - public static AssetFoldersDto FromAssets(IResultList assetFolders, ApiController controller, string app) + public static AssetFoldersDto FromAssets(IResultList assetFolders, Resources resources) { var response = new AssetFoldersDto { Total = assetFolders.Total, - Items = assetFolders.Select(x => AssetFolderDto.FromAssetFolder(x, controller, app)).ToArray() + Items = assetFolders.Select(x => AssetFolderDto.FromAssetFolder(x, resources)).ToArray() }; - return CreateLinks(response, controller, app); + return CreateLinks(response, resources); } - private static AssetFoldersDto CreateLinks(AssetFoldersDto response, ApiController controller, string app) + private static AssetFoldersDto CreateLinks(AssetFoldersDto response, Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - response.AddSelfLink(controller.Url(x => nameof(x.GetAssetFolders), values)); + response.AddSelfLink(resources.Url(x => nameof(x.GetAssetFolders), values)); - if (controller.HasPermission(Permissions.AppAssetsUpdate)) + if (resources.CanUpdateAsset) { - response.AddPostLink("create", controller.Url(x => nameof(x.PostAssetFolder), values)); + response.AddPostLink("create", resources.Url(x => nameof(x.PostAssetFolder), values)); } return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs index fbaa6dd46..0c9f2acfc 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs @@ -9,7 +9,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models @@ -27,29 +26,29 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models [Required] public AssetDto[] Items { get; set; } - public static AssetsDto FromAssets(IResultList assets, ApiController controller, string app) + public static AssetsDto FromAssets(IResultList assets, Resources resources) { var response = new AssetsDto { Total = assets.Total, - Items = assets.Select(x => AssetDto.FromAsset(x, controller, app)).ToArray() + Items = assets.Select(x => AssetDto.FromAsset(x, resources)).ToArray() }; - return CreateLinks(response, controller, app); + return CreateLinks(response, resources); } - private static AssetsDto CreateLinks(AssetsDto response, ApiController controller, string app) + private static AssetsDto CreateLinks(AssetsDto response, Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - response.AddSelfLink(controller.Url(x => nameof(x.GetAssets), values)); + response.AddSelfLink(resources.Url(x => nameof(x.GetAssets), values)); - if (controller.HasPermission(Permissions.AppAssetsCreate)) + if (resources.CanCreateAsset) { - response.AddPostLink("create", controller.Url(x => nameof(x.PostAsset), values)); + response.AddPostLink("create", resources.Url(x => nameof(x.PostAsset), values)); } - response.AddGetLink("tags", controller.Url(x => nameof(x.GetTags), values)); + response.AddGetLink("tags", resources.Url(x => nameof(x.GetTags), values)); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs index 46734be7a..d73e03c19 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs @@ -50,7 +50,7 @@ namespace Squidex.Areas.Api.Controllers.Backups { var jobs = await backupService.GetBackupsAsync(AppId); - var response = BackupJobsDto.FromBackups(jobs, this, app); + var response = BackupJobsDto.FromBackups(jobs, Resources); return Ok(response); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs index 5e0163380..0f64a06df 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs @@ -9,7 +9,6 @@ using System; using NodaTime; using Squidex.Domain.Apps.Entities.Backup; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Backups.Models @@ -46,23 +45,23 @@ namespace Squidex.Areas.Api.Controllers.Backups.Models /// public JobStatus Status { get; set; } - public static BackupJobDto FromBackup(IBackupJob backup, ApiController controller, string app) + public static BackupJobDto FromBackup(IBackupJob backup, Resources resources) { var result = SimpleMapper.Map(backup, new BackupJobDto()); - return result.CreateLinks(controller, app); + return result.CreateLinks(resources); } - private BackupJobDto CreateLinks(ApiController controller, string app) + private BackupJobDto CreateLinks(Resources resources) { - var values = new { app, id = Id }; + var values = new { app = resources.App, id = Id }; - if (controller.HasPermission(Permissions.AppBackupsDelete, app)) + if (resources.CanDeleteBackup) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteBackup), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteBackup), values)); } - AddGetLink("download", controller.Url(x => nameof(x.GetBackupContent), values)); + AddGetLink("download", resources.Url(x => nameof(x.GetBackupContent), values)); return this; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs index d533a5030..6cb57905f 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Backup; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Backups.Models @@ -22,25 +21,25 @@ namespace Squidex.Areas.Api.Controllers.Backups.Models [Required] public BackupJobDto[] Items { get; set; } - public static BackupJobsDto FromBackups(IEnumerable backups, ApiController controller, string app) + public static BackupJobsDto FromBackups(IEnumerable backups, Resources resources) { var result = new BackupJobsDto { - Items = backups.Select(x => BackupJobDto.FromBackup(x, controller, app)).ToArray() + Items = backups.Select(x => BackupJobDto.FromBackup(x, resources)).ToArray() }; - return result.CreateLinks(controller, app); + return result.CreateLinks(resources); } - private BackupJobsDto CreateLinks(ApiController controller, string app) + private BackupJobsDto CreateLinks(Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetBackups), values)); + AddSelfLink(resources.Url(x => nameof(x.GetBackups), values)); - if (controller.HasPermission(Permissions.AppBackupsCreate, app)) + if (resources.CanCreateBackup) { - AddPostLink("create", controller.Url(x => nameof(x.PostBackup), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostBackup), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 5e2e86b65..2231c3f5e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -124,7 +124,7 @@ namespace Squidex.Areas.Api.Controllers.Contents var response = Deferred.AsyncResponse(() => { - return ContentsDto.FromContentsAsync(contents, Context, this, null, contentWorkflow); + return ContentsDto.FromContentsAsync(contents, Context, Resources, null, contentWorkflow); }); return Ok(response); @@ -153,7 +153,7 @@ namespace Squidex.Areas.Api.Controllers.Contents var response = Deferred.AsyncResponse(() => { - return ContentsDto.FromContentsAsync(contents, Context, this, null, contentWorkflow); + return ContentsDto.FromContentsAsync(contents, Context, Resources, null, contentWorkflow); }); return Ok(response); @@ -186,7 +186,7 @@ namespace Squidex.Areas.Api.Controllers.Contents var response = Deferred.AsyncResponse(async () => { - return await ContentsDto.FromContentsAsync(contents, Context, this, schema, contentWorkflow); + return await ContentsDto.FromContentsAsync(contents, Context, Resources, schema, contentWorkflow); }); return Ok(response); @@ -218,7 +218,7 @@ namespace Squidex.Areas.Api.Controllers.Contents var response = Deferred.AsyncResponse(async () => { - return await ContentsDto.FromContentsAsync(contents, Context, this, schema, contentWorkflow); + return await ContentsDto.FromContentsAsync(contents, Context, Resources, schema, contentWorkflow); }); return Ok(response); @@ -246,7 +246,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { var content = await contentQuery.FindContentAsync(Context, name, id); - var response = ContentDto.FromContent(Context, content, this); + var response = ContentDto.FromContent(Context, content, Resources); return Ok(response); } @@ -274,7 +274,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { var content = await contentQuery.FindContentAsync(Context, name, id, version); - var response = ContentDto.FromContent(Context, content, this); + var response = ContentDto.FromContent(Context, content, Resources); return Ok(response.Data); } @@ -301,8 +301,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish }; var response = await InvokeCommandAsync(command); @@ -331,8 +329,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(5)] public async Task PostContents(string app, string name, [FromBody] ImportContentsDto request) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = request.ToCommand(); var context = await CommandBus.PublishAsync(command); @@ -364,8 +360,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(5)] public async Task BulkContents(string app, string name, [FromBody] BulkUpdateDto request) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = request.ToCommand(); var context = await CommandBus.PublishAsync(command); @@ -398,8 +392,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PutContent(string app, string name, Guid id, [FromBody] NamedContentData request) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = new UpdateContent { ContentId = id, Data = request.ToCleaned() }; var response = await InvokeCommandAsync(command); @@ -429,8 +421,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = new PatchContent { ContentId = id, Data = request.ToCleaned() }; var response = await InvokeCommandAsync(command); @@ -460,8 +450,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PutContentStatus(string app, string name, Guid id, ChangeStatusDto request) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = request.ToCommand(id); var response = await InvokeCommandAsync(command); @@ -489,8 +477,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task CreateDraft(string app, string name, Guid id) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = new CreateContentDraft { ContentId = id }; var response = await InvokeCommandAsync(command); @@ -518,8 +504,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DeleteVersion(string app, string name, Guid id) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = new DeleteContentDraft { ContentId = id }; var response = await InvokeCommandAsync(command); @@ -546,8 +530,6 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DeleteContent(string app, string name, Guid id) { - await contentQuery.GetSchemaOrThrowAsync(Context, name); - var command = new DeleteContent { ContentId = id }; await CommandBus.PublishAsync(command); @@ -560,7 +542,7 @@ namespace Squidex.Areas.Api.Controllers.Contents var context = await CommandBus.PublishAsync(command); var result = context.Result(); - var response = ContentDto.FromContent(Context, result, this); + var response = ContentDto.FromContent(Context, result, Resources); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 9a5db5c51..fbe8555c6 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Contents.Models @@ -106,7 +105,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models /// public long Version { get; set; } - public static ContentDto FromContent(Context context, IEnrichedContentEntity content, ApiController controller) + public static ContentDto FromContent(Context context, IEnrichedContentEntity content, Resources resources) { var response = SimpleMapper.Map(content, new ContentDto()); @@ -134,60 +133,62 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models SimpleMapper.Map(content.ScheduleJob, response.ScheduleJob); } - return response.CreateLinksAsync(content, controller, content.AppId.Name, content.SchemaId.Name); + return response.CreateLinksAsync(content, resources, content.SchemaId.Name); } - private ContentDto CreateLinksAsync(IEnrichedContentEntity content, ApiController controller, string app, string schema) + private ContentDto CreateLinksAsync(IEnrichedContentEntity content, Resources resources, string schema) { + var app = resources.App; + var values = new { app, name = schema, id = Id }; - AddSelfLink(controller.Url(x => nameof(x.GetContent), values)); + AddSelfLink(resources.Url(x => nameof(x.GetContent), values)); if (Version > 0) { var versioned = new { app, name = schema, id = Id, version = Version - 1 }; - AddGetLink("previous", controller.Url(x => nameof(x.GetContentVersion), versioned)); + AddGetLink("previous", resources.Url(x => nameof(x.GetContentVersion), versioned)); } if (NewStatus.HasValue) { - if (controller.HasPermission(Permissions.AppContentsVersionDelete, app, schema)) + if (resources.CanDeleteContentVersion(schema)) { - AddDeleteLink("draft/delete", controller.Url(x => nameof(x.DeleteVersion), values)); + AddDeleteLink("draft/delete", resources.Url(x => nameof(x.DeleteVersion), values)); } } else if (Status == Status.Published) { - if (controller.HasPermission(Permissions.AppContentsVersionCreate, app, schema)) + if (resources.CanCreateContentVersion(schema)) { - AddPostLink("draft/create", controller.Url(x => nameof(x.CreateDraft), values)); + AddPostLink("draft/create", resources.Url(x => nameof(x.CreateDraft), values)); } } - if (controller.HasPermission(Permissions.AppContentsUpdate, app, schema) && content.NextStatuses != null) + if (content.NextStatuses != null && resources.CanUpdateContent(schema)) { foreach (var next in content.NextStatuses) { - AddPutLink($"status/{next.Status}", controller.Url(x => nameof(x.PutContentStatus), values), next.Color); + AddPutLink($"status/{next.Status}", resources.Url(x => nameof(x.PutContentStatus), values), next.Color); } } - if (content.IsSingleton == false && controller.HasPermission(Permissions.AppContentsDelete, app, schema)) + if (content.IsSingleton == false && resources.CanDeleteContent(schema)) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteContent), values)); } if (content.CanUpdate) { - if (controller.HasPermission(Permissions.AppContentsUpdate, app, schema)) + if (resources.CanUpdateContent(schema)) { - AddPutLink("update", controller.Url(x => nameof(x.PutContent), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutContent), values)); } - if (controller.HasPermission(Permissions.AppContentsUpdatePartial, app, schema)) + if (resources.CanUpdateContentPartial(schema)) { - AddPatchLink("patch", controller.Url(x => nameof(x.PatchContent), values)); + AddPatchLink("patch", resources.Url(x => nameof(x.PatchContent), values)); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index cdcf9c6e5..eec6b3ae9 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs @@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Contents.Models @@ -36,20 +35,20 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models [Required] public StatusInfoDto[] Statuses { get; set; } - public static async Task FromContentsAsync(IResultList contents, Context context, ApiController controller, + public static async Task FromContentsAsync(IResultList contents, Context context, Resources resources, ISchemaEntity? schema, IContentWorkflow workflow) { var result = new ContentsDto { Total = contents.Total, - Items = contents.Select(x => ContentDto.FromContent(context, x, controller)).ToArray() + Items = contents.Select(x => ContentDto.FromContent(context, x, resources)).ToArray() }; if (schema != null) { await result.AssignStatusesAsync(workflow, schema); - result.CreateLinks(controller, schema.AppId.Name, schema.SchemaDef.Name); + result.CreateLinks(resources, schema.SchemaDef.Name); } return result; @@ -62,17 +61,17 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models Statuses = allStatuses.Select(StatusInfoDto.FromStatusInfo).ToArray(); } - private void CreateLinks(ApiController controller, string app, string schema) + private void CreateLinks(Resources resources, string schema) { - var values = new { app, name = schema }; + var values = new { app = resources.App, name = schema }; - AddSelfLink(controller.Url(x => nameof(x.GetContents), values)); + AddSelfLink(resources.Url(x => nameof(x.GetContents), values)); - if (controller.HasPermission(Permissions.AppContentsCreate, app, schema)) + if (resources.CanCreateContent(schema)) { - AddPostLink("create", controller.Url(x => nameof(x.PostContent), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostContent), values)); - AddPostLink("create/publish", controller.Url(x => nameof(x.PostContent), values) + "?publish=true"); + AddPostLink("create/publish", resources.Url(x => nameof(x.PostContent), values) + "?publish=true"); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs b/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs index 47977f2c5..3e8ea2e53 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs @@ -35,7 +35,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers { var eventConsumers = await GetGrain().GetConsumersAsync(); - var response = EventConsumersDto.FromResults(eventConsumers.Value, this); + var response = EventConsumersDto.FromResults(eventConsumers.Value, Resources); return Ok(response); } @@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers { var eventConsumer = await GetGrain().StartAsync(name); - var response = EventConsumerDto.FromEventConsumerInfo(eventConsumer.Value, this); + var response = EventConsumerDto.FromEventConsumerInfo(eventConsumer.Value, Resources); return Ok(response); } @@ -61,7 +61,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers { var eventConsumer = await GetGrain().StopAsync(name); - var response = EventConsumerDto.FromEventConsumerInfo(eventConsumer.Value, this); + var response = EventConsumerDto.FromEventConsumerInfo(eventConsumer.Value, Resources); return Ok(response); } @@ -74,7 +74,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers { var eventConsumer = await GetGrain().ResetAsync(name); - var response = EventConsumerDto.FromEventConsumerInfo(eventConsumer.Value, this); + var response = EventConsumerDto.FromEventConsumerInfo(eventConsumer.Value, Resources); return Ok(response); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs b/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs index a3a443410..42ff70d9c 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs @@ -7,7 +7,6 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.EventConsumers.Models @@ -24,31 +23,31 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models public string? Position { get; set; } - public static EventConsumerDto FromEventConsumerInfo(EventConsumerInfo eventConsumerInfo, ApiController controller) + public static EventConsumerDto FromEventConsumerInfo(EventConsumerInfo eventConsumerInfo, Resources resources) { var result = SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto()); - return result.CreateLinks(controller); + return result.CreateLinks(resources); } - private EventConsumerDto CreateLinks(ApiController controller) + private EventConsumerDto CreateLinks(Resources resources) { - if (controller.HasPermission(Permissions.AdminEventsManage)) + if (resources.CanManageEvents) { var values = new { name = Name }; if (!IsResetting) { - AddPutLink("reset", controller.Url(x => nameof(x.ResetEventConsumer), values)); + AddPutLink("reset", resources.Url(x => nameof(x.ResetEventConsumer), values)); } if (IsStopped) { - AddPutLink("start", controller.Url(x => nameof(x.StartEventConsumer), values)); + AddPutLink("start", resources.Url(x => nameof(x.StartEventConsumer), values)); } else { - AddPutLink("stop", controller.Url(x => nameof(x.StopEventConsumer), values)); + AddPutLink("stop", resources.Url(x => nameof(x.StopEventConsumer), values)); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs b/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs index 65f11e289..b685ad136 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs @@ -19,19 +19,19 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models /// public EventConsumerDto[] Items { get; set; } - public static EventConsumersDto FromResults(IEnumerable items, ApiController controller) + public static EventConsumersDto FromResults(IEnumerable items, Resources resources) { var result = new EventConsumersDto { - Items = items.Select(x => EventConsumerDto.FromEventConsumerInfo(x, controller)).ToArray() + Items = items.Select(x => EventConsumerDto.FromEventConsumerInfo(x, resources)).ToArray() }; - return result.CreateLinks(controller); + return result.CreateLinks(resources); } - private EventConsumersDto CreateLinks(ApiController controller) + private EventConsumersDto CreateLinks(Resources resources) { - AddSelfLink(controller.Url(c => nameof(c.GetEventConsumers))); + AddSelfLink(resources.Url(c => nameof(c.GetEventConsumers))); return this; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs index abfd42c0d..a844c6f19 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs @@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Rules.Models @@ -91,7 +90,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models /// public Instant? LastExecuted { get; set; } - public static RuleDto FromRule(IEnrichedRuleEntity rule, Guid? runningRuleId, ApiController controller, string app) + public static RuleDto FromRule(IEnrichedRuleEntity rule, Guid? runningRuleId, Resources resources) { var result = new RuleDto(); @@ -103,48 +102,45 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models result.Trigger = RuleTriggerDtoFactory.Create(rule.RuleDef.Trigger); } - return result.CreateLinks(controller, runningRuleId, app); + return result.CreateLinks(resources, runningRuleId); } - private RuleDto CreateLinks(ApiController controller, Guid? runningRuleId, string app) + private RuleDto CreateLinks(Resources resources, Guid? runningRuleId) { - var values = new { app, id = Id }; + var values = new { app = resources.App, id = Id }; - if (controller.HasPermission(Permissions.AppRulesDisable, app)) + if (resources.CanDisableRule) { if (IsEnabled) { - AddPutLink("disable", controller.Url(x => nameof(x.DisableRule), values)); + AddPutLink("disable", resources.Url(x => nameof(x.DisableRule), values)); } else { - AddPutLink("enable", controller.Url(x => nameof(x.EnableRule), values)); + AddPutLink("enable", resources.Url(x => nameof(x.EnableRule), values)); } } - if (controller.HasPermission(Permissions.AppRulesUpdate)) + if (resources.CanUpdateRule) { - AddPutLink("update", controller.Url(x => nameof(x.PutRule), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutRule), values)); } - if (controller.HasPermission(Permissions.AppRulesEvents)) + if (resources.CanReadRuleEvents) { - AddPutLink("trigger", controller.Url(x => nameof(x.TriggerRule), values)); + AddPutLink("trigger", resources.Url(x => nameof(x.TriggerRule), values)); if (runningRuleId == null) { - AddPutLink("run", controller.Url(x => nameof(x.PutRuleRun), values)); + AddPutLink("run", resources.Url(x => nameof(x.PutRuleRun), values)); } - } - if (controller.HasPermission(Permissions.AppRulesDelete)) - { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteRule), values)); + AddGetLink("logs", resources.Url(x => nameof(x.GetEvents), values)); } - if (controller.HasPermission(Permissions.AppRulesEvents)) + if (resources.CanDeleteRule) { - AddGetLink("logs", controller.Url(x => nameof(x.GetEvents), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteRule), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs index 7c22a0e45..c46cf07e7 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Rules; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Rules.Models @@ -28,36 +27,36 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models /// public Guid? RunningRuleId { get; set; } - public static RulesDto FromRules(IEnumerable items, Guid? runningRuleId, ApiController controller, string app) + public static RulesDto FromRules(IEnumerable items, Guid? runningRuleId, Resources resources) { var result = new RulesDto { - Items = items.Select(x => RuleDto.FromRule(x, runningRuleId, controller, app)).ToArray() + Items = items.Select(x => RuleDto.FromRule(x, runningRuleId, resources)).ToArray() }; result.RunningRuleId = runningRuleId; - return result.CreateLinks(controller, runningRuleId, app); + return result.CreateLinks(resources, runningRuleId); } - private RulesDto CreateLinks(ApiController controller, Guid? runningRuleId, string app) + private RulesDto CreateLinks(Resources resources, Guid? runningRuleId) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetRules), values)); + AddSelfLink(resources.Url(x => nameof(x.GetRules), values)); - if (controller.HasPermission(Permissions.AppRulesCreate, app)) + if (resources.CanCreateRule) { - AddPostLink("create", controller.Url(x => nameof(x.PostRule), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostRule), values)); } - if (controller.HasPermission(Permissions.AppRulesEvents, app)) + if (resources.CanReadRuleEvents) { - AddGetLink("events", controller.Url(x => nameof(x.GetEvents), values)); + AddGetLink("events", resources.Url(x => nameof(x.GetEvents), values)); if (runningRuleId != null) { - AddDeleteLink("run/cancel", controller.Url(x => nameof(x.DeleteRuleRun), values)); + AddDeleteLink("run/cancel", resources.Url(x => nameof(x.DeleteRuleRun), values)); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 74b254e0a..bcc01d537 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -95,7 +95,7 @@ namespace Squidex.Areas.Api.Controllers.Rules var response = Deferred.Response(() => { - return RulesDto.FromRules(rules, runningRuleId, this, app); + return RulesDto.FromRules(rules, runningRuleId, Resources); }); return Ok(response); @@ -363,7 +363,7 @@ namespace Squidex.Areas.Api.Controllers.Rules var runningRuleId = await ruleRunnerService.GetRunningRuleIdAsync(Context.App.Id); var result = context.Result(); - var response = RuleDto.FromRule(result, runningRuleId, this, app); + var response = RuleDto.FromRule(result, runningRuleId, Resources); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs index 6a361104b..17f7e79b8 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs @@ -102,53 +102,56 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models return result; } - public void CreateLinks(ApiController controller, string app, string schema, bool allowUpdate) + public void CreateLinks(Resources resources, string schema, bool allowUpdate) { allowUpdate = allowUpdate && !IsLocked; if (allowUpdate) { - var values = new { app, name = schema, id = FieldId }; + var values = new { app = resources.App, name = schema, id = FieldId }; - AddPutLink("update", controller.Url(x => nameof(x.PutField), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutField), values)); if (IsHidden) { - AddPutLink("show", controller.Url(x => nameof(x.ShowField), values)); + AddPutLink("show", resources.Url(x => nameof(x.ShowField), values)); } else { - AddPutLink("hide", controller.Url(x => nameof(x.HideField), values)); + AddPutLink("hide", resources.Url(x => nameof(x.HideField), values)); } if (IsDisabled) { - AddPutLink("enable", controller.Url(x => nameof(x.EnableField), values)); + AddPutLink("enable", resources.Url(x => nameof(x.EnableField), values)); } else { - AddPutLink("disable", controller.Url(x => nameof(x.DisableField), values)); + AddPutLink("disable", resources.Url(x => nameof(x.DisableField), values)); } if (Properties is ArrayFieldPropertiesDto) { - var parentValues = new { app, name = schema, parentId = FieldId }; + var parentValues = new { app = resources.App, name = schema, parentId = FieldId }; - AddPostLink("fields/add", controller.Url(x => nameof(x.PostNestedField), parentValues)); + AddPostLink("fields/add", resources.Url(x => nameof(x.PostNestedField), parentValues)); - AddPutLink("fields/order", controller.Url(x => nameof(x.PutNestedFieldOrdering), parentValues)); + AddPutLink("fields/order", resources.Url(x => nameof(x.PutNestedFieldOrdering), parentValues)); } - AddPutLink("lock", controller.Url(x => nameof(x.LockField), values)); + if (!IsLocked) + { + AddPutLink("lock", resources.Url(x => nameof(x.LockField), values)); + } - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteField), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteField), values)); } if (Nested != null) { foreach (var nested in Nested) { - nested.CreateLinks(controller, app, schema, FieldId, allowUpdate); + nested.CreateLinks(resources, schema, FieldId, allowUpdate); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs index 7c93c30dc..236a2876d 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs @@ -45,37 +45,40 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [Required] public FieldPropertiesDto Properties { get; set; } - public void CreateLinks(ApiController controller, string app, string schema, long parentId, bool allowUpdate) + public void CreateLinks(Resources resources, string schema, long parentId, bool allowUpdate) { allowUpdate = allowUpdate && !IsLocked; if (allowUpdate) { - var values = new { app, name = schema, parentId, id = FieldId }; + var values = new { app = resources.App, name = schema, parentId, id = FieldId }; - AddPutLink("update", controller.Url(x => nameof(x.PutNestedField), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutNestedField), values)); if (IsHidden) { - AddPutLink("show", controller.Url(x => nameof(x.ShowNestedField), values)); + AddPutLink("show", resources.Url(x => nameof(x.ShowNestedField), values)); } else { - AddPutLink("hide", controller.Url(x => nameof(x.HideNestedField), values)); + AddPutLink("hide", resources.Url(x => nameof(x.HideNestedField), values)); } if (IsDisabled) { - AddPutLink("enable", controller.Url(x => nameof(x.EnableNestedField), values)); + AddPutLink("enable", resources.Url(x => nameof(x.EnableNestedField), values)); } else { - AddPutLink("disable", controller.Url(x => nameof(x.DisableNestedField), values)); + AddPutLink("disable", resources.Url(x => nameof(x.DisableNestedField), values)); } - AddPutLink("lock", controller.Url(x => nameof(x.LockNestedField), values)); + if (!IsLocked) + { + AddPutLink("lock", resources.Url(x => nameof(x.LockNestedField), values)); + } - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteNestedField), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteNestedField), values)); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index c834b2a2d..dc9439fa3 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -10,7 +10,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models @@ -49,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [Required] public List Fields { get; set; } - public static SchemaDetailsDto FromSchemaWithDetails(ISchemaEntity schema, ApiController controller, string app) + public static SchemaDetailsDto FromSchemaWithDetails(ISchemaEntity schema, Resources resources) { var result = new SchemaDetailsDto(); @@ -59,6 +58,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models SimpleMapper.Map(schema.SchemaDef.Properties, result.Properties); result.FieldsInLists = schema.SchemaDef.FieldsInLists.ToList(); + result.FieldsInReferences = schema.SchemaDef.FieldsInReferences.ToList(); if (schema.SchemaDef.PreviewUrls.Count > 0) @@ -73,22 +73,22 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models result.Fields.Add(FieldDto.FromField(field)); } - result.CreateLinks(controller, app); + result.CreateLinks(resources); return result; } - protected override void CreateLinks(ApiController controller, string app) + protected override void CreateLinks(Resources resources) { - base.CreateLinks(controller, app); + base.CreateLinks(resources); - var allowUpdate = controller.HasPermission(Permissions.AppSchemasUpdate, app, Name); + var allowUpdate = resources.CanUpdateSchema(Name); if (Fields != null) { foreach (var nested in Fields) { - nested.CreateLinks(controller, app, Name, allowUpdate); + nested.CreateLinks(resources, Name, allowUpdate); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index bf8b9a43f..5dd4cd33c 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -12,7 +12,6 @@ using Squidex.Areas.Api.Controllers.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models @@ -79,7 +78,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public long Version { get; set; } - public static SchemaDto FromSchema(ISchemaEntity schema, ApiController controller, string app) + public static SchemaDto FromSchema(ISchemaEntity schema, Resources controller) { var result = new SchemaDto(); @@ -87,64 +86,64 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models SimpleMapper.Map(schema.SchemaDef, result); SimpleMapper.Map(schema.SchemaDef.Properties, result.Properties); - result.CreateLinks(controller, app); + result.CreateLinks(controller); return result; } - protected virtual void CreateLinks(ApiController controller, string app) + protected virtual void CreateLinks(Resources resources) { - var values = new { app, name = Name }; + var values = new { app = resources.App, name = Name }; - var allowUpdate = controller.HasPermission(Permissions.AppSchemasUpdate, app, Name); + var allowUpdate = resources.CanUpdateSchema(Name); - AddSelfLink(controller.Url(x => nameof(x.GetSchema), values)); + AddSelfLink(resources.Url(x => nameof(x.GetSchema), values)); - if (controller.HasPermission(Permissions.AppContentsRead, app, Name)) + if (resources.CanReadContent(Name)) { - AddGetLink("contents", controller.Url(x => nameof(x.GetContents), values)); + AddGetLink("contents", resources.Url(x => nameof(x.GetContents), values)); } - if (controller.HasPermission(Permissions.AppContentsCreate, app, Name)) + if (resources.CanCreateContent(Name)) { - AddPostLink("contents/create", controller.Url(x => nameof(x.PostContent), values)); + AddPostLink("contents/create", resources.Url(x => nameof(x.PostContent), values)); - AddPostLink("contents/create/publish", controller.Url(x => nameof(x.PostContent), values) + "?publish=true"); + AddPostLink("contents/create/publish", resources.Url(x => nameof(x.PostContent), values) + "?publish=true"); } - if (controller.HasPermission(Permissions.AppSchemasPublish, app, Name)) + if (resources.CanPublishSchema(Name)) { if (IsPublished) { - AddPutLink("unpublish", controller.Url(x => nameof(x.UnpublishSchema), values)); + AddPutLink("unpublish", resources.Url(x => nameof(x.UnpublishSchema), values)); } else { - AddPutLink("publish", controller.Url(x => nameof(x.PublishSchema), values)); + AddPutLink("publish", resources.Url(x => nameof(x.PublishSchema), values)); } } if (allowUpdate) { - AddPostLink("fields/add", controller.Url(x => nameof(x.PostField), values)); + AddPostLink("fields/add", resources.Url(x => nameof(x.PostField), values)); - AddPutLink("fields/order", controller.Url(x => nameof(x.PutSchemaFieldOrdering), values)); - AddPutLink("fields/ui", controller.Url(x => nameof(x.PutSchemaUIFields), values)); + AddPutLink("fields/ui", resources.Url(x => nameof(x.PutSchemaUIFields), values)); + AddPutLink("fields/order", resources.Url(x => nameof(x.PutSchemaFieldOrdering), values)); - AddPutLink("update", controller.Url(x => nameof(x.PutSchema), values)); - AddPutLink("update/category", controller.Url(x => nameof(x.PutCategory), values)); - AddPutLink("update/sync", controller.Url(x => nameof(x.PutSchemaSync), values)); - AddPutLink("update/urls", controller.Url(x => nameof(x.PutPreviewUrls), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutSchema), values)); + AddPutLink("update/sync", resources.Url(x => nameof(x.PutSchemaSync), values)); + AddPutLink("update/urls", resources.Url(x => nameof(x.PutPreviewUrls), values)); + AddPutLink("update/category", resources.Url(x => nameof(x.PutCategory), values)); } - if (controller.HasPermission(Permissions.AppSchemasScripts, app, Name)) + if (resources.CanUpdateSchemaScripts(Name)) { - AddPutLink("update/scripts", controller.Url(x => nameof(x.PutScripts), values)); + AddPutLink("update/scripts", resources.Url(x => nameof(x.PutScripts), values)); } - if (controller.HasPermission(Permissions.AppSchemasDelete, app, Name)) + if (resources.CanDeleteSchema(Name)) { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteSchema), values)); + AddDeleteLink("delete", resources.Url(x => nameof(x.DeleteSchema), values)); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs index de939823c..dc8e694ff 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models @@ -20,25 +19,25 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public SchemaDto[] Items { get; set; } - public static SchemasDto FromSchemas(IList schemas, ApiController controller, string app) + public static SchemasDto FromSchemas(IList schemas, Resources resources) { var result = new SchemasDto { - Items = schemas.Select(x => SchemaDto.FromSchema(x, controller, app)).ToArray() + Items = schemas.Select(x => SchemaDto.FromSchema(x, resources)).ToArray() }; - return result.CreateLinks(controller, app); + return result.CreateLinks(resources); } - private SchemasDto CreateLinks(ApiController controller, string app) + private SchemasDto CreateLinks(Resources resources) { - var values = new { app }; + var values = new { app = resources.App }; - AddSelfLink(controller.Url(x => nameof(x.GetSchemas), values)); + AddSelfLink(resources.Url(x => nameof(x.GetSchemas), values)); - if (controller.HasPermission(Permissions.AppSchemasCreate, app)) + if (resources.CanCreateSchema) { - AddPostLink("create", controller.Url(x => nameof(x.PostSchema), values)); + AddPostLink("create", resources.Url(x => nameof(x.PostSchema), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index e11a0cbd7..458701778 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return CreatedAtAction(nameof(SchemasController.GetSchema), "Schemas", new { app, name }, response); } @@ -75,7 +75,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(parentId); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return CreatedAtAction(nameof(SchemasController.GetSchema), "Schemas", new { app, name }, response); } @@ -100,7 +100,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -125,7 +125,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -151,7 +151,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(parentId); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -177,7 +177,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(id); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -204,7 +204,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = request.ToCommand(id, parentId); - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -231,7 +231,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new LockField { FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -259,7 +259,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new LockField { ParentFieldId = parentId, FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -286,7 +286,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new HideField { FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -314,7 +314,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new HideField { ParentFieldId = parentId, FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -341,7 +341,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new ShowField { FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -369,7 +369,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new ShowField { ParentFieldId = parentId, FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -396,7 +396,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new EnableField { FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -424,7 +424,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new EnableField { ParentFieldId = parentId, FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -451,7 +451,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new DisableField { FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -479,7 +479,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new DisableField { ParentFieldId = parentId, FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -504,7 +504,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new DeleteField { FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } @@ -530,17 +530,17 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var command = new DeleteField { ParentFieldId = parentId, FieldId = id }; - var response = await InvokeCommandAsync(app, command); + var response = await InvokeCommandAsync(command); return Ok(response); } - private async Task InvokeCommandAsync(string app, ICommand command) + private async Task InvokeCommandAsync(ICommand command) { var context = await CommandBus.PublishAsync(command); var result = context.Result(); - var response = SchemaDetailsDto.FromSchemaWithDetails(result, this, app); + var response = SchemaDetailsDto.FromSchemaWithDetails(result, Resources); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 758cd584c..eb49eb3f5 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -52,7 +52,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas var response = Deferred.Response(() => { - return SchemasDto.FromSchemas(schemas, this, app); + return SchemasDto.FromSchemas(schemas, Resources); }); Response.Headers[HeaderNames.ETag] = schemas.ToEtag(); @@ -94,7 +94,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas var response = Deferred.Response(() => { - return SchemaDetailsDto.FromSchemaWithDetails(schema, this, app); + return SchemaDetailsDto.FromSchemaWithDetails(schema, Resources); }); Response.Headers[HeaderNames.ETag] = schema.ToEtag(); @@ -320,7 +320,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas var context = await CommandBus.PublishAsync(command); var result = context.Result(); - var response = SchemaDetailsDto.FromSchemaWithDetails(result, this, app); + var response = SchemaDetailsDto.FromSchemaWithDetails(result, Resources); return response; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs index a0e6cf19d..d41383921 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs @@ -9,40 +9,39 @@ using Squidex.Areas.Api.Controllers.Backups; using Squidex.Areas.Api.Controllers.EventConsumers; using Squidex.Areas.Api.Controllers.Languages; using Squidex.Areas.Api.Controllers.Ping; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Users.Models { public sealed class ResourcesDto : Resource { - public static ResourcesDto FromController(ApiController controller) + public static ResourcesDto FromResources(Resources resources) { var result = new ResourcesDto(); - result.AddGetLink("ping", controller.Url(x => nameof(x.GetPing))); + result.AddGetLink("ping", resources.Url(x => nameof(x.GetPing))); - if (controller.HasPermission(Permissions.AdminEventsRead)) + if (resources.CanReadEvents) { - result.AddGetLink("admin/events", controller.Url(x => nameof(x.GetEventConsumers))); + result.AddGetLink("admin/events", resources.Url(x => nameof(x.GetEventConsumers))); } - if (controller.HasPermission(Permissions.AdminRestore)) + if (resources.CanRestoreBackup) { - result.AddGetLink("admin/restore", controller.Url(x => nameof(x.GetRestoreJob))); + result.AddGetLink("admin/restore", resources.Url(x => nameof(x.GetRestoreJob))); } - if (controller.HasPermission(Permissions.AdminUsersRead)) + if (resources.CanReadUsers) { - result.AddGetLink("admin/users", controller.Url(x => nameof(x.GetUsers))); + result.AddGetLink("admin/users", resources.Url(x => nameof(x.GetUsers))); } - if (controller.HasPermission(Permissions.AdminOrleans)) + if (resources.CanReadOrleans) { result.AddGetLink("admin/orleans", "/orleans"); } - result.AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages))); + result.AddGetLink("languages", resources.Url(x => nameof(x.GetLanguages))); return result; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs index dbc08c87d..d95fccd7e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs @@ -10,7 +10,6 @@ using System.ComponentModel.DataAnnotations; using Squidex.Infrastructure.Reflection; using Squidex.Shared.Users; using Squidex.Web; -using AllPermissions = Squidex.Shared.Permissions; namespace Squidex.Areas.Api.Controllers.Users.Models { @@ -46,48 +45,48 @@ namespace Squidex.Areas.Api.Controllers.Users.Models [Required] public IEnumerable Permissions { get; set; } - public static UserDto FromUser(IUser user, ApiController controller) + public static UserDto FromUser(IUser user, Resources resources) { var userPermssions = user.Permissions().ToIds(); var userName = user.DisplayName()!; var result = SimpleMapper.Map(user, new UserDto { DisplayName = userName, Permissions = userPermssions }); - return result.CreateLinks(controller); + return result.CreateLinks(resources); } - private UserDto CreateLinks(ApiController controller) + private UserDto CreateLinks(Resources resources) { var values = new { id = Id }; - if (controller is UserManagementController) + if (resources.Controller is UserManagementController) { - AddSelfLink(controller.Url(c => nameof(c.GetUser), values)); + AddSelfLink(resources.Url(c => nameof(c.GetUser), values)); } else { - AddSelfLink(controller.Url(c => nameof(c.GetUser), values)); + AddSelfLink(resources.Url(c => nameof(c.GetUser), values)); } - if (!controller.IsUser(Id)) + if (!resources.Controller.IsUser(Id)) { - if (controller.HasPermission(AllPermissions.AdminUsersLock) && !IsLocked) + if (resources.CanLockUser && !IsLocked) { - AddPutLink("lock", controller.Url(c => nameof(c.LockUser), values)); + AddPutLink("lock", resources.Url(c => nameof(c.LockUser), values)); } - if (controller.HasPermission(AllPermissions.AdminUsersUnlock) && IsLocked) + if (resources.CanUnlockUser && IsLocked) { - AddPutLink("unlock", controller.Url(c => nameof(c.UnlockUser), values)); + AddPutLink("unlock", resources.Url(c => nameof(c.UnlockUser), values)); } } - if (controller.HasPermission(AllPermissions.AdminUsersUpdate)) + if (resources.CanUpdateUser) { - AddPutLink("update", controller.Url(c => nameof(c.PutUser), values)); + AddPutLink("update", resources.Url(c => nameof(c.PutUser), values)); } - AddGetLink("picture", controller.Url(c => nameof(c.GetUserPicture), values)); + AddGetLink("picture", resources.Url(c => nameof(c.GetUserPicture), values)); return this; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs index c8a2937b1..1468dba3e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Users; -using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Users.Models @@ -27,24 +26,24 @@ namespace Squidex.Areas.Api.Controllers.Users.Models [Required] public UserDto[] Items { get; set; } - public static UsersDto FromResults(IEnumerable items, long total, ApiController controller) + public static UsersDto FromResults(IEnumerable items, long total, Resources resources) { var result = new UsersDto { Total = total, - Items = items.Select(x => UserDto.FromUser(x, controller)).ToArray() + Items = items.Select(x => UserDto.FromUser(x, resources)).ToArray() }; - return result.CreateLinks(controller); + return result.CreateLinks(resources); } - private UsersDto CreateLinks(ApiController controller) + private UsersDto CreateLinks(Resources context) { - AddSelfLink(controller.Url(c => nameof(c.GetUsers))); + AddSelfLink(context.Url(c => nameof(c.GetUsers))); - if (controller.HasPermission(Permissions.AdminUsersCreate)) + if (context.CanCreateUser) { - AddPostLink("create", controller.Url(c => nameof(c.PostUser))); + AddPostLink("create", context.Url(c => nameof(c.PostUser))); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs index 20638bb74..888727701 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs @@ -41,7 +41,7 @@ namespace Squidex.Areas.Api.Controllers.Users await Task.WhenAll(taskForItems, taskForCount); - var response = UsersDto.FromResults(taskForItems.Result, taskForCount.Result, this); + var response = UsersDto.FromResults(taskForItems.Result, taskForCount.Result, Resources); return Ok(response); } @@ -59,7 +59,7 @@ namespace Squidex.Areas.Api.Controllers.Users return NotFound(); } - var response = UserDto.FromUser(user, this); + var response = UserDto.FromUser(user, Resources); return Ok(response); } @@ -72,7 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Users { var user = await userManager.CreateAsync(userFactory, request.ToValues()); - var response = UserDto.FromUser(user, this); + var response = UserDto.FromUser(user, Resources); return Ok(response); } @@ -85,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Users { var user = await userManager.UpdateAsync(id, request.ToValues()); - var response = UserDto.FromUser(user, this); + var response = UserDto.FromUser(user, Resources); return Ok(response); } @@ -103,7 +103,7 @@ namespace Squidex.Areas.Api.Controllers.Users var user = await userManager.LockAsync(id); - var response = UserDto.FromUser(user, this); + var response = UserDto.FromUser(user, Resources); return Ok(response); } @@ -121,7 +121,7 @@ namespace Squidex.Areas.Api.Controllers.Users var user = await userManager.UnlockAsync(id); - var response = UserDto.FromUser(user, this); + var response = UserDto.FromUser(user, Resources); return Ok(response); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs index 82dfb8dbe..da5d84b6a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs @@ -72,7 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Users [ApiPermission] public IActionResult GetUserResources() { - var response = ResourcesDto.FromController(this); + var response = ResourcesDto.FromResources(Resources); return Ok(response); } @@ -97,7 +97,7 @@ namespace Squidex.Areas.Api.Controllers.Users { var users = await userResolver.QueryByEmailAsync(query); - var response = users.Where(x => !x.IsHidden()).Select(x => UserDto.FromUser(x, this)).ToArray(); + var response = users.Where(x => !x.IsHidden()).Select(x => UserDto.FromUser(x, Resources)).ToArray(); return Ok(response); } @@ -131,7 +131,7 @@ namespace Squidex.Areas.Api.Controllers.Users if (entity != null) { - var response = UserDto.FromUser(entity, this); + var response = UserDto.FromUser(entity, Resources); return Ok(response); } diff --git a/backend/src/Squidex/Areas/Api/Startup.cs b/backend/src/Squidex/Areas/Api/Startup.cs index e8fab3e7b..9c2cb90ee 100644 --- a/backend/src/Squidex/Areas/Api/Startup.cs +++ b/backend/src/Squidex/Areas/Api/Startup.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.AspNetCore.Builder; +using Squidex.Areas.Api.Config; using Squidex.Areas.Api.Config.OpenApi; using Squidex.Web; @@ -17,6 +18,8 @@ namespace Squidex.Areas.Api { app.Map(Constants.ApiPrefix, appApi => { + appApi.UseMiddleware(); + appApi.UseRouting(); appApi.UseAuthentication(); diff --git a/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs b/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs index 111f36705..8702948ae 100644 --- a/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs +++ b/backend/src/Squidex/Config/Authentication/IdentityServerServices.cs @@ -38,24 +38,24 @@ namespace Squidex.Config.Authentication apiAuthorityUrl = urlsOptions.BuildUrl(Constants.IdentityServerPrefix); } - authBuilder.AddIdentityServerAuthentication(options => + if (identityOptions.LocalApi) { - options.Authority = apiAuthorityUrl; - options.ApiName = apiScope; - options.ApiSecret = null; - options.RequireHttpsMetadata = identityOptions.RequiresHttps; - options.SupportedTokens = SupportedTokens.Jwt; - - var fromHeader = TokenRetrieval.FromAuthorizationHeader(); - var fromQuery = TokenRetrieval.FromQueryString(); - - options.TokenRetriever = request => + authBuilder.AddLocalApi(Constants.ApiSecurityScheme, options => { - var result = fromHeader(request) ?? fromQuery(request); - - return result; - }; - }); + options.ExpectedScope = apiScope; + }); + } + else + { + authBuilder.AddIdentityServerAuthentication(Constants.ApiSecurityScheme, options => + { + options.Authority = apiAuthorityUrl; + options.ApiName = apiScope; + options.ApiSecret = null; + options.RequireHttpsMetadata = identityOptions.RequiresHttps; + options.SupportedTokens = SupportedTokens.Jwt; + }); + } authBuilder.AddOpenIdConnect(options => { diff --git a/backend/src/Squidex/Config/MyIdentityOptions.cs b/backend/src/Squidex/Config/MyIdentityOptions.cs index a89555413..29e90d9b9 100644 --- a/backend/src/Squidex/Config/MyIdentityOptions.cs +++ b/backend/src/Squidex/Config/MyIdentityOptions.cs @@ -55,6 +55,8 @@ namespace Squidex.Config public bool AllowPasswordAuth { get; set; } + public bool LocalApi { get; set; } = true; + public bool LockAutomatically { get; set; } public bool NoConsent { get; set; } diff --git a/backend/src/Squidex/Config/Web/WebServices.cs b/backend/src/Squidex/Config/Web/WebServices.cs index 132584cd5..026d84af5 100644 --- a/backend/src/Squidex/Config/Web/WebServices.cs +++ b/backend/src/Squidex/Config/Web/WebServices.cs @@ -37,6 +37,9 @@ namespace Squidex.Config.Web services.AddSingletonAs() .AsSelf(); + services.AddSingletonAs() + .AsSelf(); + services.AddSingletonAs() .AsSelf(); @@ -77,6 +80,7 @@ namespace Squidex.Config.Web options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); + options.Filters.Add(); options.Filters.Add(); }) .AddRazorRuntimeCompilation() diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index d8398e527..c66f13f84 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -504,11 +504,15 @@ /* * Set to true to show PII (Personally Identifiable Information) in the logs. */ - "showPII": false, + "showPII": true, /* * Enable password auth. Set this to false if you want to disable local login, leaving only 3rd party login options. */ "allowPasswordAuth": true, + /* + * Uses local API authentication. + */ + "localApi": true, /* * Initial admin user. */ diff --git a/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs b/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs index b647d275d..42e6d4fc3 100644 --- a/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs +++ b/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -13,8 +14,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; +using Squidex.Infrastructure; using Squidex.Shared; using Squidex.Shared.Identity; +using Squidex.Web.Pipeline; using Xunit; #pragma warning disable IDE0017 // Simplify object initialization @@ -49,17 +52,17 @@ namespace Squidex.Web } [Fact] - public void Should_use_bearer_schemes() + public void Should_use_custom_authorization_scheme() { var sut = new ApiPermissionAttribute(); - Assert.Equal("Bearer", sut.AuthenticationSchemes); + Assert.Equal(Constants.ApiSecurityScheme, sut.AuthenticationSchemes); } [Fact] - public async Task Should_call_next_when_user_has_correct_permission() + public async Task Should_make_permission_check_with_app_feature() { - actionExecutingContext.RouteData.Values["app"] = "my-app"; + actionExecutingContext.HttpContext.Features.Set(new AppFeature(NamedId.Of(Guid.NewGuid(), "my-app"))); user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.my-app")); @@ -71,10 +74,26 @@ namespace Squidex.Web Assert.True(isNextCalled); } + [Fact] + public async Task Should_make_permission_check_with_schema_feature() + { + actionExecutingContext.HttpContext.Features.Set(new AppFeature(NamedId.Of(Guid.NewGuid(), "my-app"))); + actionExecutingContext.HttpContext.Features.Set(new SchemaFeature(NamedId.Of(Guid.NewGuid(), "my-schema"))); + + user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.my-app.schemas.my-schema")); + + var sut = new ApiPermissionAttribute(Permissions.AppSchemasUpdate); + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.Null(actionExecutingContext.Result); + Assert.True(isNextCalled); + } + [Fact] public async Task Should_return_forbidden_when_user_has_wrong_permission() { - actionExecutingContext.RouteData.Values["app"] = "my-app"; + actionExecutingContext.HttpContext.Features.Set(new AppFeature(NamedId.Of(Guid.NewGuid(), "my-app"))); user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.other-app")); diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs new file mode 100644 index 000000000..30db5353e --- /dev/null +++ b/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs @@ -0,0 +1,151 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using FakeItEasy; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Xunit; + +#pragma warning disable IDE0017 // Simplify object initialization + +namespace Squidex.Web.Pipeline +{ + public class SchemaResolverTests + { + private readonly IAppProvider appProvider = A.Fake(); + private readonly HttpContext httpContext = new DefaultHttpContext(); + private readonly ActionContext actionContext; + private readonly ActionExecutingContext actionExecutingContext; + private readonly ActionExecutionDelegate next; + private readonly ClaimsIdentity user = new ClaimsIdentity(); + private readonly NamedId schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); + private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); + private readonly SchemaResolver sut; + private bool isNextCalled; + + public SchemaResolverTests() + { + actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor + { + EndpointMetadata = new List() + }); + + actionExecutingContext = new ActionExecutingContext(actionContext, new List(), new Dictionary(), this); + actionExecutingContext.HttpContext = httpContext; + actionExecutingContext.HttpContext.User = new ClaimsPrincipal(user); + + next = () => + { + isNextCalled = true; + + return Task.FromResult(null); + }; + + sut = new SchemaResolver(appProvider); + } + + [Fact] + public async Task Should_return_not_found_if_schema_not_found() + { + actionExecutingContext.HttpContext.Features.Set(new AppFeature(appId)); + actionContext.RouteData.Values["name"] = schemaId.Id.ToString(); + + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false)) + .Returns(Task.FromResult(null)); + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.IsType(actionExecutingContext.Result); + Assert.False(isNextCalled); + } + + [Fact] + public async Task Should_resolve_schema_from_id() + { + actionExecutingContext.HttpContext.Features.Set(new AppFeature(appId)); + actionContext.RouteData.Values["name"] = schemaId.Id.ToString(); + + var schema = CreateSchema(); + + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false)) + .Returns(schema); + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.Equal(schemaId, actionContext.HttpContext.Features.Get().SchemaId); + Assert.True(isNextCalled); + } + + [Fact] + public async Task Should_resolve_schema_from_name() + { + actionExecutingContext.HttpContext.Features.Set(new AppFeature(appId)); + actionContext.RouteData.Values["name"] = schemaId.Name; + + var schema = CreateSchema(); + + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name)) + .Returns(schema); + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.Equal(schemaId, actionContext.HttpContext.Features.Get().SchemaId); + Assert.True(isNextCalled); + } + + [Fact] + public async Task Should_do_nothing_if_app_feature_not_set() + { + actionExecutingContext.RouteData.Values["name"] = schemaId.Name; + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.True(isNextCalled); + + A.CallTo(() => appProvider.GetAppAsync(A._)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_do_nothing_if_parameter_not_set() + { + actionExecutingContext.HttpContext.Features.Set(new AppFeature(appId)); + actionExecutingContext.RouteData.Values.Remove("name"); + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.True(isNextCalled); + + A.CallTo(() => appProvider.GetAppAsync(A._)) + .MustNotHaveHappened(); + } + + private ISchemaEntity CreateSchema() + { + var schemaEntity = A.Fake(); + + A.CallTo(() => schemaEntity.SchemaDef) + .Returns(new Schema(schemaId.Name)); + + A.CallTo(() => schemaEntity.Id) + .Returns(schemaId.Id); + + return schemaEntity; + } + } +}