From 242df48c18e66466980b5a0ba6ba0de03ccb9e37 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 8 Jun 2019 21:10:30 +0200 Subject: [PATCH] Some progress. --- src/Squidex.Shared/Permissions.cs | 2 - src/Squidex.Web/PermissionExtensions.cs | 18 +-- .../Controllers/Apps/AppPatternsController.cs | 2 +- .../Api/Controllers/Apps/AppsController.cs | 4 +- .../Api/Controllers/Apps/Models/AppDto.cs | 91 ++++++++++- .../Controllers/Plans/AppPlansController.cs | 2 +- .../Schemas/Models/SchemaDetailsDto.cs | 70 +-------- .../Controllers/Schemas/Models/SchemaDto.cs | 20 ++- .../Controllers/Schemas/SchemasController.cs | 4 +- .../Controllers/Users/Models/ResourcesDto.cs | 39 +++++ .../Api/Controllers/Users/UsersController.cs | 17 ++ .../administration-area.component.html | 8 +- .../administration-area.component.ts | 6 + .../pages/dashboard-page.component.html | 2 +- .../pages/dashboard-page.component.ts | 5 +- .../settings/settings-area.component.html | 18 +-- .../framework/angular/http/hateos.pipes.ts | 20 +-- src/Squidex/app/framework/internal.ts | 1 - src/Squidex/app/framework/module.ts | 3 - .../app/framework/utils/permission.spec.ts | 145 ------------------ src/Squidex/app/framework/utils/permission.ts | 116 -------------- .../components/asset-uploader.component.html | 2 +- .../shared/components/permission.directive.ts | 133 ---------------- .../components/schema-category.component.html | 2 +- src/Squidex/app/shared/declarations.ts | 1 - src/Squidex/app/shared/module.ts | 3 - .../app/shared/services/apps.service.spec.ts | 7 +- .../app/shared/services/apps.service.ts | 37 +++-- .../app/shared/services/auth.service.ts | 21 +-- .../app/shared/services/users.service.spec.ts | 28 ++++ .../app/shared/services/users.service.ts | 21 ++- .../app/shared/state/apps.state.spec.ts | 9 +- src/Squidex/app/shared/state/apps.state.ts | 9 +- src/Squidex/app/shared/state/ui.state.spec.ts | 23 ++- src/Squidex/app/shared/state/ui.state.ts | 37 +++-- .../shell/pages/app/left-menu.component.html | 14 +- .../shell/pages/app/left-menu.component.ts | 3 +- .../pages/internal/apps-menu.component.html | 24 ++- .../internal/profile-menu.component.html | 8 +- .../pages/internal/profile-menu.component.ts | 4 +- 40 files changed, 369 insertions(+), 610 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs delete mode 100644 src/Squidex/app/framework/utils/permission.spec.ts delete mode 100644 src/Squidex/app/framework/utils/permission.ts delete mode 100644 src/Squidex/app/shared/components/permission.directive.ts diff --git a/src/Squidex.Shared/Permissions.cs b/src/Squidex.Shared/Permissions.cs index 964925efb..ee1e20cf4 100644 --- a/src/Squidex.Shared/Permissions.cs +++ b/src/Squidex.Shared/Permissions.cs @@ -79,7 +79,6 @@ namespace Squidex.Shared public const string AppRolesDelete = "squidex.apps.{app}.roles.delete"; public const string AppPatterns = "squidex.apps.{app}.patterns"; - public const string AppPatternsRead = "squidex.apps.{app}.patterns.read"; public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create"; public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update"; public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete"; @@ -117,7 +116,6 @@ namespace Squidex.Shared public const string AppContents = "squidex.apps.{app}.contents.{name}"; public const string AppContentsRead = "squidex.apps.{app}.contents.{name}.read"; - public const string AppContentsGraphQL = "squidex.apps.{app}.contents.{name}.graphql"; public const string AppContentsCreate = "squidex.apps.{app}.contents.{name}.create"; public const string AppContentsUpdate = "squidex.apps.{app}.contents.{name}.update"; public const string AppContentsDiscard = "squidex.apps.{app}.contents.{name}.discard"; diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs index 6dc7d0610..a6751e9e9 100644 --- a/src/Squidex.Web/PermissionExtensions.cs +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -24,7 +24,7 @@ namespace Squidex.Web } } - public static PermissionSet GetPermissions(this HttpContext httpContext) + public static PermissionSet Permissions(this HttpContext httpContext) { var feature = httpContext.Features.Get(); @@ -38,22 +38,22 @@ namespace Squidex.Web return feature.Permissions; } - public static bool HasPermission(this HttpContext httpContext, Permission permission) + public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null) { - return httpContext.GetPermissions().Includes(permission); + return httpContext.Permissions().Includes(permission) || permission?.Includes(permission) == true; } - public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*") + public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*", PermissionSet permissions = null) { - return httpContext.GetPermissions().Includes(Permissions.ForApp(id, app, schema)); + return httpContext.HasPermission(Shared.Permissions.ForApp(id, app, schema), permissions); } - public static bool HasPermission(this ApiController controller, Permission permission) + public static bool HasPermission(this ApiController controller, Permission permission, PermissionSet permissions = null) { - return controller.HttpContext.GetPermissions().Includes(permission); + return controller.HttpContext.HasPermission(permission, permissions); } - public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*") + public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*", PermissionSet permissions = null) { if (app == "*") { @@ -71,7 +71,7 @@ namespace Squidex.Web } } - return controller.HttpContext.GetPermissions().Includes(Permissions.ForApp(id, app, schema)); + return controller.HasPermission(Shared.Permissions.ForApp(id, app, schema), permissions); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index fd4fac1c5..fef02c374 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -43,7 +43,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/{app}/patterns/")] [ProducesResponseType(typeof(AppPatternDto[]), 200)] - [ApiPermission(Permissions.AppPatternsRead)] + [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public IActionResult GetPatterns(string app) { diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 4219f42dd..36f336ced 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -58,11 +58,11 @@ namespace Squidex.Areas.Api.Controllers.Apps public async Task GetApps() { var userOrClientId = HttpContext.User.UserOrClientId(); - var userPermissions = HttpContext.User.Permissions(); + var userPermissions = HttpContext.Permissions(); var entities = await appProvider.GetUserApps(userOrClientId, userPermissions); - var response = entities.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider)); + var response = entities.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider, this)); Response.Headers[HeaderNames.ETag] = response.ToManyEtag(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index 7fe96d7bc..dbb2f4ded 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -9,6 +9,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using NodaTime; +using Squidex.Areas.Api.Controllers.Assets; +using Squidex.Areas.Api.Controllers.Backups; +using Squidex.Areas.Api.Controllers.Plans; +using Squidex.Areas.Api.Controllers.Rules; +using Squidex.Areas.Api.Controllers.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; @@ -16,10 +21,11 @@ using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Web; +using AllPermissions = Squidex.Shared.Permissions; namespace Squidex.Areas.Api.Controllers.Apps.Models { - public sealed class AppDto : IGenerateETag + public sealed class AppDto : Resource, IGenerateETag { /// /// The name of the app. @@ -63,7 +69,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public string PlanUpgrade { get; set; } - public static AppDto FromApp(IAppEntity app, string userId, PermissionSet userPermissions, IAppPlansProvider plans) + public static AppDto FromApp(IAppEntity app, string userId, PermissionSet userPermissions, IAppPlansProvider plans, ApiController controller) { var permissions = new List(); @@ -77,13 +83,84 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models permissions.AddRange(userPermissions.ToAppPermissions(app.Name)); } - var response = SimpleMapper.Map(app, new AppDto()); + var result = SimpleMapper.Map(app, new AppDto()); - response.Permissions = permissions.ToArray(x => x.Id); - response.PlanName = plans.GetPlanForApp(app)?.Name; - response.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; + result.Permissions = permissions.ToArray(x => x.Id); + result.PlanName = plans.GetPlanForApp(app)?.Name; - return response; + if (controller.HasPermission(AllPermissions.AppPlansChange, app.Name)) + { + result.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; + } + + return CreateLinks(result, controller, new PermissionSet(permissions)); + } + + private static AppDto CreateLinks(AppDto result, ApiController controller, PermissionSet permissions) + { + var values = new { app = result.Name }; + + if (controller.HasPermission(AllPermissions.AppDelete, result.Name, permissions: permissions)) + { + result.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteApp), values)); + } + + if (controller.HasPermission(AllPermissions.AppAssetsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("assets", controller.Url(x => nameof(x.GetAssets), values)); + } + + if (controller.HasPermission(AllPermissions.AppBackupsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("backups", controller.Url(x => nameof(x.GetJobs), values)); + } + + if (controller.HasPermission(AllPermissions.AppClientsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("clients", controller.Url(x => nameof(x.GetClients), values)); + } + + if (controller.HasPermission(AllPermissions.AppContributorsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("contributors", controller.Url(x => nameof(x.GetContributors), values)); + } + + if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions)) + { + result.AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages), values)); + } + + if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions)) + { + result.AddGetLink("patterns", controller.Url(x => nameof(x.GetPatterns), values)); + } + + if (controller.HasPermission(AllPermissions.AppPlansRead, result.Name, permissions: permissions)) + { + result.AddGetLink("plans", controller.Url(x => nameof(x.GetPlans), values)); + } + + if (controller.HasPermission(AllPermissions.AppRolesRead, result.Name, permissions: permissions)) + { + result.AddGetLink("roles", controller.Url(x => nameof(x.GetRoles), values)); + } + + if (controller.HasPermission(AllPermissions.AppRulesRead, result.Name, permissions: permissions)) + { + result.AddGetLink("rules", controller.Url(x => nameof(x.GetRules), values)); + } + + if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions)) + { + result.AddGetLink("schemas", controller.Url(x => nameof(x.GetSchemas), values)); + } + + if (controller.HasPermission(AllPermissions.AppSchemasCreate, result.Name, permissions: permissions)) + { + result.AddPostLink("schemas/create", controller.Url(x => nameof(x.PostSchema), values)); + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index 9490a4f48..5611ac7ac 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -74,7 +74,7 @@ namespace Squidex.Areas.Api.Controllers.Plans [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPlansChange)] [ApiCosts(0)] - public async Task ChangePlanAsync(string app, [FromBody] ChangePlanDto request) + public async Task PutPlan(string app, [FromBody] ChangePlanDto request) { var context = await CommandBus.PublishAsync(request.ToCommand()); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index a6ed2b46b..41494e0c1 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -5,49 +5,20 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using NodaTime; using Squidex.Areas.Api.Controllers.Schemas.Models.Converters; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class SchemaDetailsDto + public sealed class SchemaDetailsDto : SchemaDto { private static readonly Dictionary EmptyPreviewUrls = new Dictionary(); - /// - /// The id of the schema. - /// - public Guid Id { get; set; } - - /// - /// The name of the schema. Unique within the app. - /// - [Required] - [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] - public string Name { get; set; } - - /// - /// The name of the category. - /// - public string Category { get; set; } - - /// - /// Indicates if the schema is a singleton. - /// - public bool IsSingleton { get; set; } - - /// - /// Indicates if the schema is published. - /// - public bool IsPublished { get; set; } - /// /// The scripts. /// @@ -64,40 +35,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [Required] public List Fields { get; set; } - /// - /// The schema properties. - /// - [Required] - public SchemaPropertiesDto Properties { get; set; } = new SchemaPropertiesDto(); - - /// - /// The user that has created the schema. - /// - [Required] - public RefToken CreatedBy { get; set; } - - /// - /// The user that has updated the schema. - /// - [Required] - public RefToken LastModifiedBy { get; set; } - - /// - /// The date and time when the schema has been created. - /// - public Instant Created { get; set; } - - /// - /// The date and time when the schema has been modified last. - /// - public Instant LastModified { get; set; } - - /// - /// The version of the schema. - /// - public long Version { get; set; } - - public static SchemaDetailsDto FromSchema(ISchemaEntity schema) + public static SchemaDetailsDto FromSchemaWithDetails(ISchemaEntity schema, ApiController controller, string app) { var response = new SchemaDetailsDto(); @@ -147,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models response.Fields.Add(fieldDto); } - return response; + return CreateLinks(response, controller, app); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index f66d64d23..4bb86680f 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -8,14 +8,16 @@ using System; using System.ComponentModel.DataAnnotations; using NodaTime; +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 { - public sealed class SchemaDto : IGenerateETag + public class SchemaDto : Resource, IGenerateETag { /// /// The id of the schema. @@ -77,7 +79,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public long Version { get; set; } - public static SchemaDto FromSchema(ISchemaEntity schema) + public static SchemaDto FromSchema(ISchemaEntity schema, ApiController controller, string app) { var response = new SchemaDto { Properties = new SchemaPropertiesDto() }; @@ -85,6 +87,20 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models SimpleMapper.Map(schema.SchemaDef, response); SimpleMapper.Map(schema.SchemaDef.Properties, response.Properties); + return CreateLinks(response, controller, app); + } + + protected static T CreateLinks(T response, ApiController controller, string app) where T : SchemaDto + { + var values = new { app, name = response.Name }; + + response.AddSelfLink(controller.Url(x => nameof(x.GetSchema), values)); + + if (controller.HasPermission(Permissions.AppContentsRead, app, response.Name)) + { + response.AddGetLink("contents", controller.Url(x => nameof(x.GetContents), values)); + } + return response; } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 1fb64ec83..7320e4484 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -51,7 +51,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var schemas = await appProvider.GetSchemasAsync(AppId); - var response = schemas.ToArray(SchemaDto.FromSchema); + var response = schemas.ToArray(x => SchemaDto.FromSchema(x, this, app)); Response.Headers[HeaderNames.ETag] = response.ToManyEtag(); @@ -90,7 +90,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas return NotFound(); } - var response = SchemaDetailsDto.FromSchema(entity); + var response = SchemaDetailsDto.FromSchemaWithDetails(entity, this, app); Response.Headers[HeaderNames.ETag] = entity.Version.ToString(); diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs new file mode 100644 index 000000000..13570a1bf --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Areas.Api.Controllers.Backups; +using Squidex.Areas.Api.Controllers.EventConsumers; +using Squidex.Shared; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Users.Models +{ + public sealed class ResourcesDto : Resource + { + public static ResourcesDto FromController(ApiController controller) + { + var result = new ResourcesDto(); + + if (controller.HasPermission(Permissions.AdminEventsRead)) + { + result.AddGetLink("admin/eventConsumers", controller.Url(x => nameof(x.GetEventConsumers))); + } + + if (controller.HasPermission(Permissions.AdminRestoreRead)) + { + result.AddGetLink("admin/restore", controller.Url(x => nameof(x.GetJob))); + } + + if (controller.HasPermission(Permissions.AdminUsersRead)) + { + result.AddGetLink("admin/users", controller.Url(x => nameof(x.GetUsers))); + } + + return result; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs index a5cf31c74..0ab2944c5 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs @@ -56,6 +56,23 @@ namespace Squidex.Areas.Api.Controllers.Users this.log = log; } + /// + /// Get the user resources. + /// + /// + /// 200 => User resources returned. + /// + [HttpGet] + [Route("user/resources/")] + [ProducesResponseType(typeof(ResourcesDto), 200)] + [ApiPermission] + public IActionResult GetUserResources() + { + var response = ResourcesDto.FromController(this); + + return Ok(response); + } + /// /// Get users by query. /// diff --git a/src/Squidex/app/features/administration/administration-area.component.html b/src/Squidex/app/features/administration/administration-area.component.html index c3b623cdb..3f2e2c3d0 100644 --- a/src/Squidex/app/features/administration/administration-area.component.html +++ b/src/Squidex/app/features/administration/administration-area.component.html @@ -1,18 +1,18 @@ -