From 382ef7f35783bff41dcc7e1cf88769d6bbc97040 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 31 Jan 2021 20:27:38 +0100 Subject: [PATCH] Feature/own content (#632) * Return contents with permission read.own Co-authored-by: Dmitriy Borowskiy --- backend/i18n/source/backend_en.json | 1 + .../Contents/Operations/QueryByQuery.cs | 12 +- .../Contents/BulkUpdateCommandMiddleware.cs | 10 +- .../Contents/ContentsSearchSource.cs | 2 +- .../DomainObject/ContentDomainObject.cs | 2 + .../DomainObject/Guards/GuardContent.cs | 36 ++++++ .../Contents/Queries/ContentQueryService.cs | 14 ++- backend/src/Squidex.Domain.Apps.Entities/Q.cs | 2 + .../Schemas/SchemasSearchSource.cs | 2 +- backend/src/Squidex.Shared/Permissions.cs | 7 ++ backend/src/Squidex.Shared/Texts.it.resx | 3 + backend/src/Squidex.Shared/Texts.nl.resx | 3 + backend/src/Squidex.Shared/Texts.resx | 3 + backend/src/Squidex.Web/Resources.cs | 10 +- .../Contents/ContentsController.cs | 20 +-- .../Generator/SchemaOpenApiGenerator.cs | 12 +- .../Controllers/Contents/Models/ContentDto.cs | 1 - .../BulkUpdateCommandMiddlewareTests.cs | 28 ++--- .../Contents/ContentsSearchSourceTests.cs | 2 +- .../DomainObject/Guards/GuardContentTests.cs | 119 +++++++++++++----- .../Queries/ContentQueryServiceTests.cs | 30 ++++- .../Schemas/SchemasSearchSourceTests.cs | 4 +- .../TestHelpers/Mocks.cs | 12 +- 23 files changed, 242 insertions(+), 93 deletions(-) diff --git a/backend/i18n/source/backend_en.json b/backend/i18n/source/backend_en.json index 8eb1fde46..0753d8c45 100644 --- a/backend/i18n/source/backend_en.json +++ b/backend/i18n/source/backend_en.json @@ -51,6 +51,7 @@ "common.displayName": "Display name", "common.editor": "Editor", "common.email": "Email", + "common.errorNoPermission": "You do not have the necessary permission.", "common.field": "Field", "common.fieldIds": "Field IDs", "common.fieldName": "Field name", diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs index e6939cc85..afc8017c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs @@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { var query = q.Query.AdjustToModel(app.Id); - var filter = CreateFilter(app.Id, schemas.Select(x => x.Id), query, q.Reference); + var filter = CreateFilter(app.Id, schemas.Select(x => x.Id), query, q.Reference, q.CreatedBy); var contentEntities = await FindContentsAsync(query, filter); var contentTotal = (long)contentEntities.Count; @@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { var query = q.Query.AdjustToModel(app.Id); - var filter = CreateFilter(schema.AppId.Id, Enumerable.Repeat(schema.Id, 1), query, q.Reference); + var filter = CreateFilter(schema.AppId.Id, Enumerable.Repeat(schema.Id, 1), query, q.Reference, q.CreatedBy); var contentEntities = await FindContentsAsync(query, filter); var contentTotal = (long)contentEntities.Count; @@ -216,7 +216,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations return Filter.And(filters); } - private static FilterDefinition CreateFilter(DomainId appId, IEnumerable schemaIds, ClrQuery? query, DomainId referenced) + private static FilterDefinition CreateFilter(DomainId appId, IEnumerable schemaIds, ClrQuery? query, + DomainId referenced, RefToken? createdBy) { var filters = new List> { @@ -235,6 +236,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations filters.Add(Filter.AnyEq(x => x.ReferencedIds, referenced)); } + if (createdBy != null) + { + filters.Add(Filter.Eq(x => x.CreatedBy, createdBy)); + } + return Filter.And(filters); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs index 9458644be..aff2bef53 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs @@ -200,7 +200,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var command = new UpdateContent { Data = job.Data! }; - await EnrichAsync(id, task, command, Permissions.AppContentsUpdate); + await EnrichAsync(id, task, command, Permissions.AppContentsUpdateOwn); return command; } @@ -216,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var command = new PatchContent { Data = job.Data! }; - await EnrichAsync(id, task, command, Permissions.AppContentsUpdate); + await EnrichAsync(id, task, command, Permissions.AppContentsUpdateOwn); return command; } @@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var command = new ValidateContent(); - await EnrichAsync(id, task, command, Permissions.AppContentsRead); + await EnrichAsync(id, task, command, Permissions.AppContentsReadOwn); return command; } @@ -232,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var command = new ChangeContentStatus { Status = job.Status, DueTime = job.DueTime }; - await EnrichAsync(id, task, command, Permissions.AppContentsUpdate); + await EnrichAsync(id, task, command, Permissions.AppContentsUpdateOwn); return command; } @@ -240,7 +240,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var command = new DeleteContent(); - await EnrichAsync(id, task, command, Permissions.AppContentsDelete); + await EnrichAsync(id, task, command, Permissions.AppContentsDeleteOwn); return command; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs index f3b5fdd9d..9fee30ee7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs @@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private static bool HasPermission(Context context, string schemaName) { - var permission = Permissions.ForApp(Permissions.AppContentsRead, context.App.Name, schemaName); + var permission = Permissions.ForApp(Permissions.AppContentsReadOwn, context.App.Name, schemaName); return context.Permissions.Allows(permission); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs index 18a72698f..6d4bda942 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs @@ -129,6 +129,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject { await LoadContext(c); + GuardContent.CanValidate(c, Snapshot); + await context.ValidateContentAndInputAsync(Snapshot.Data); return true; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/GuardContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/GuardContent.cs index b160876b3..5796817f6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/GuardContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/GuardContent.cs @@ -15,6 +15,8 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; +using Squidex.Shared; +using Squidex.Shared.Identity; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards { @@ -46,6 +48,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards { Guard.NotNull(command, nameof(command)); + CheckPermission(content, command, Permissions.AppContentsUpdate); + Validate.It(e => { ValidateData(command, e); @@ -60,6 +64,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards { Guard.NotNull(command, nameof(command)); + CheckPermission(content, command, Permissions.AppContentsUpdate); + Validate.It(e => { ValidateData(command, e); @@ -68,10 +74,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards await ValidateCanUpdate(content, contentWorkflow, command.User); } + public static void CanValidate(ValidateContent command, IContentEntity content) + { + Guard.NotNull(command, nameof(command)); + + CheckPermission(content, command, Permissions.AppContentsRead); + } + public static void CanDeleteDraft(DeleteContentDraft command, IContentEntity content) { Guard.NotNull(command, nameof(command)); + CheckPermission(content, command, Permissions.AppContentsVersionDelete); + if (content.NewStatus == null) { throw new DomainException(T.Get("contents.draftToDeleteNotFound")); @@ -82,6 +97,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards { Guard.NotNull(command, nameof(command)); + CheckPermission(content, command, Permissions.AppContentsVersionCreate); + if (content.Status != Status.Published) { throw new DomainException(T.Get("contents.draftNotCreateForUnpublished")); @@ -96,6 +113,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards { Guard.NotNull(command, nameof(command)); + CheckPermission(content, command, Permissions.AppContentsChangeStatus); + if (schema.SchemaDef.IsSingleton) { if (content.NewStatus == null || command.Status != Status.Published) @@ -141,6 +160,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards { Guard.NotNull(command, nameof(command)); + CheckPermission(content, command, Permissions.AppContentsDeleteOwn); + if (schema.SchemaDef.IsSingleton) { throw new DomainException(T.Get("contents.singletonNotDeletable")); @@ -174,5 +195,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status })); } } + + public static void CheckPermission(IContentEntity content, ContentCommand command, string permission) + { + if (content.CreatedBy?.Equals(command.Actor) == true) + { + return; + } + + var requiredPermission = Permissions.ForApp(permission, content.AppId.Name, content.SchemaId.Name); + + if (!command.User.Claims.Permissions().Allows(requiredPermission)) + { + throw new DomainForbiddenException(T.Get("common.errorNoPermission")); + } + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 92d6c6ec1..e9131c019 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Translations; using Squidex.Log; using Squidex.Shared; @@ -92,6 +93,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName); + if (!HasPermission(context, schema, Permissions.AppContentsRead)) + { + q.CreatedBy = context.User.Token(); + } + using (Profiler.TraceMethod()) { q = await queryParser.ParseAsync(context, q, schema); @@ -193,7 +199,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries schema = await appProvider.GetSchemaAsync(context.App.Id, schemaIdOrName, canCache); } - if (schema != null && !HasPermission(context, schema)) + if (schema != null && !HasPermission(context, schema, Permissions.AppContentsReadOwn)) { throw new DomainForbiddenException(T.Get("schemas.noPermission")); } @@ -205,12 +211,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var schemas = await appProvider.GetSchemasAsync(context.App.Id); - return schemas.Where(x => HasPermission(context, x)).ToList(); + return schemas.Where(x => HasPermission(context, x, Permissions.AppContentsReadOwn)).ToList(); } - private static bool HasPermission(Context context, ISchemaEntity schema) + private static bool HasPermission(Context context, ISchemaEntity schema, string permissionId) { - var permission = Permissions.ForApp(Permissions.AppContentsRead, context.App.Name, schema.SchemaDef.Name); + var permission = Permissions.ForApp(permissionId, context.App.Name, schema.SchemaDef.Name); return context.Permissions.Allows(permission); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Q.cs b/backend/src/Squidex.Domain.Apps.Entities/Q.cs index e4e7e0dcb..e1909156d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Q.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Q.cs @@ -31,6 +31,8 @@ namespace Squidex.Domain.Apps.Entities public ClrQuery Query { get; init; } = new ClrQuery(); + public RefToken? CreatedBy { get; set; } + private Q() { } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs index ab6fd0373..bfb5beb67 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs @@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas private static bool HasPermission(Context context, NamedId schemaId) { - var permission = Permissions.ForApp(Permissions.AppContentsRead, context.App.Name, schemaId.Name); + var permission = Permissions.ForApp(Permissions.AppContentsReadOwn, context.App.Name, schemaId.Name); return context.Permissions.Allows(permission); } diff --git a/backend/src/Squidex.Shared/Permissions.cs b/backend/src/Squidex.Shared/Permissions.cs index 1865b56cf..336f2cfdb 100644 --- a/backend/src/Squidex.Shared/Permissions.cs +++ b/backend/src/Squidex.Shared/Permissions.cs @@ -137,12 +137,19 @@ 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 AppContentsReadOwn = "squidex.apps.{app}.contents.{name}.read.own"; public const string AppContentsCreate = "squidex.apps.{app}.contents.{name}.create"; public const string AppContentsUpdate = "squidex.apps.{app}.contents.{name}.update"; + public const string AppContentsUpdateOwn = "squidex.apps.{app}.contents.{name}.update.own"; + public const string AppContentsChangeStatus = "squidex.apps.{app}.contents.{name}.changestatus"; + public const string AppContentsChangeStatusOwn = "squidex.apps.{app}.contents.{name}.changestatus.own"; public const string AppContentsUpsert = "squidex.apps.{app}.contents.{name}.upsert"; public const string AppContentsVersionCreate = "squidex.apps.{app}.contents.{name}.version.create"; + public const string AppContentsVersionCreateOwn = "squidex.apps.{app}.contents.{name}.version.create.own"; public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{name}.version.delete"; + public const string AppContentsVersionDeleteOwn = "squidex.apps.{app}.contents.{name}.version.delete.own"; public const string AppContentsDelete = "squidex.apps.{app}.contents.{name}.delete"; + public const string AppContentsDeleteOwn = "squidex.apps.{app}.contents.{name}.delete.own"; static Permissions() { diff --git a/backend/src/Squidex.Shared/Texts.it.resx b/backend/src/Squidex.Shared/Texts.it.resx index 1c5181353..dc1c7c93c 100644 --- a/backend/src/Squidex.Shared/Texts.it.resx +++ b/backend/src/Squidex.Shared/Texts.it.resx @@ -238,6 +238,9 @@ Email + + You do not have the necessary permission. + Campo diff --git a/backend/src/Squidex.Shared/Texts.nl.resx b/backend/src/Squidex.Shared/Texts.nl.resx index 5cc43d694..c09135f08 100644 --- a/backend/src/Squidex.Shared/Texts.nl.resx +++ b/backend/src/Squidex.Shared/Texts.nl.resx @@ -238,6 +238,9 @@ E-mail + + You do not have the necessary permission. + Veld diff --git a/backend/src/Squidex.Shared/Texts.resx b/backend/src/Squidex.Shared/Texts.resx index dff65e73c..ec883bd09 100644 --- a/backend/src/Squidex.Shared/Texts.resx +++ b/backend/src/Squidex.Shared/Texts.resx @@ -238,6 +238,9 @@ Email + + You do not have the necessary permission. + Field diff --git a/backend/src/Squidex.Web/Resources.cs b/backend/src/Squidex.Web/Resources.cs index 1d7a81d2a..3239fa710 100644 --- a/backend/src/Squidex.Web/Resources.cs +++ b/backend/src/Squidex.Web/Resources.cs @@ -20,17 +20,17 @@ namespace Squidex.Web private readonly Dictionary<(string, string), bool> schemaPermissions = new Dictionary<(string, string), bool>(); // Contents - public bool CanReadContent(string schema) => IsAllowedForSchema(P.AppContentsRead, schema); + public bool CanReadContent(string schema) => IsAllowedForSchema(P.AppContentsReadOwn, schema); public bool CanCreateContent(string schema) => IsAllowedForSchema(P.AppContentsCreate, schema); - public bool CanCreateContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionCreate, schema); + public bool CanCreateContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionCreateOwn, schema); - public bool CanDeleteContent(string schema) => IsAllowedForSchema(P.AppContentsDelete, schema); + public bool CanDeleteContent(string schema) => IsAllowedForSchema(P.AppContentsDeleteOwn, schema); - public bool CanDeleteContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionDelete, schema); + public bool CanDeleteContentVersion(string schema) => IsAllowedForSchema(P.AppContentsVersionDeleteOwn, schema); - public bool CanUpdateContent(string schema) => IsAllowedForSchema(P.AppContentsUpdate, schema); + public bool CanUpdateContent(string schema) => IsAllowedForSchema(P.AppContentsUpdateOwn, schema); // Schemas public bool CanUpdateSchema(string schema) => IsAllowedForSchema(P.AppSchemasDelete, schema); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 05785cb50..1aed08ba3 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -54,7 +54,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpGet] [Route("content/{app}/graphql/")] - [ApiPermissionOrAnonymous] + [ApiPermissionOrAnonymous(Permissions.AppContents)] [ApiCosts(2)] public async Task GetGraphQL(string app, [FromQuery] GraphQLGetDto? queries = null) { @@ -86,7 +86,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPost] [Route("content/{app}/graphql/")] - [ApiPermissionOrAnonymous] + [ApiPermissionOrAnonymous(Permissions.AppContents)] [ApiCosts(2)] public async Task PostGraphQL(string app, [FromBody] GraphQLPostDto query) { @@ -118,7 +118,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPost] [Route("content/{app}/graphql/batch")] - [ApiPermissionOrAnonymous] + [ApiPermissionOrAnonymous(Permissions.AppContents)] [ApiCosts(2)] public async Task PostGraphQLBatch(string app, [FromBody] GraphQLPostDto[] batch) { @@ -396,7 +396,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpGet] [Route("content/{app}/{name}/{id}/{version}/")] - [ApiPermissionOrAnonymous(Permissions.AppContentsRead)] + [ApiPermissionOrAnonymous(Permissions.AppContentsReadOwn)] [ApiCosts(1)] public async Task GetContentVersion(string app, string name, DomainId id, int version) { @@ -557,7 +557,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/")] [ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppContentsUpdate)] + [ApiPermissionOrAnonymous(Permissions.AppContentsUpdateOwn)] [ApiCosts(1)] public async Task PutContent(string app, string name, DomainId id, [FromBody] ContentData request) { @@ -586,7 +586,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPatch] [Route("content/{app}/{name}/{id}/")] [ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppContentsUpdate)] + [ApiPermissionOrAnonymous(Permissions.AppContentsUpdateOwn)] [ApiCosts(1)] public async Task PatchContent(string app, string name, DomainId id, [FromBody] ContentData request) { @@ -615,7 +615,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/status/")] [ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppContentsUpdate)] + [ApiPermissionOrAnonymous(Permissions.AppContentsChangeStatusOwn)] [ApiCosts(1)] public async Task PutContentStatus(string app, string name, DomainId id, ChangeStatusDto request) { @@ -642,7 +642,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPost] [Route("content/{app}/{name}/{id}/draft/")] [ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppContentsVersionCreate)] + [ApiPermissionOrAnonymous(Permissions.AppContentsVersionCreateOwn)] [ApiCosts(1)] public async Task CreateDraft(string app, string name, DomainId id) { @@ -669,7 +669,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpDelete] [Route("content/{app}/{name}/{id}/draft/")] [ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)] - [ApiPermissionOrAnonymous(Permissions.AppContentsDelete)] + [ApiPermissionOrAnonymous(Permissions.AppContentsDeleteOwn)] [ApiCosts(1)] public async Task DeleteVersion(string app, string name, DomainId id) { @@ -697,7 +697,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpDelete] [Route("content/{app}/{name}/{id}/")] - [ApiPermissionOrAnonymous(Permissions.AppContentsDelete)] + [ApiPermissionOrAnonymous(Permissions.AppContentsDeleteOwn)] [ApiCosts(1)] public async Task DeleteContent(string app, string name, DomainId id, [FromQuery] bool checkReferrers = false) { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs index 6b143676f..6c6f15dfa 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs @@ -85,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private OpenApiPathItem GenerateSchemaGetsOperation() { - return Add(OpenApiOperationMethod.Get, Permissions.AppContentsRead, "/", + return Add(OpenApiOperationMethod.Get, Permissions.AppContentsReadOwn, "/", operation => { operation.OperationId = $"Query{schemaType}Contents"; @@ -103,7 +103,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private OpenApiPathItem GenerateSchemaGetOperation() { - return Add(OpenApiOperationMethod.Get, Permissions.AppContentsRead, "/{id}", operation => + return Add(OpenApiOperationMethod.Get, Permissions.AppContentsReadOwn, "/{id}", operation => { operation.OperationId = $"Get{schemaType}Content"; @@ -133,7 +133,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private OpenApiPathItem GenerateSchemaUpdateOperation() { - return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdate, "/{id}", + return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdateOwn, "/{id}", operation => { operation.OperationId = $"Update{schemaType}Content"; @@ -149,7 +149,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private OpenApiPathItem GenerateSchemaUpdatePatchOperation() { - return Add(OpenApiOperationMethod.Patch, Permissions.AppContentsUpdate, "/{id}", + return Add(OpenApiOperationMethod.Patch, Permissions.AppContentsUpdateOwn, "/{id}", operation => { operation.OperationId = $"Path{schemaType}Content"; @@ -165,7 +165,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private OpenApiPathItem GenerateSchemaStatusOperation() { - return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdate, "/{id}/status", + return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdateOwn, "/{id}/status", operation => { operation.OperationId = $"Change{schemaType}ContentStatus"; @@ -181,7 +181,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private OpenApiPathItem GenerateSchemaDeleteOperation() { - return Add(OpenApiOperationMethod.Delete, Permissions.AppContentsDelete, "/{id}", + return Add(OpenApiOperationMethod.Delete, Permissions.AppContentsDeleteOwn, "/{id}", operation => { operation.OperationId = $"Delete{schemaType}Content"; 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 f7e1d15e3..bd20b5dc3 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -180,7 +180,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models if (content.CanUpdate && resources.CanUpdateContent(schema)) { AddPutLink("update", resources.Url(x => nameof(x.PutContent), values)); - AddPatchLink("patch", resources.Url(x => nameof(x.PatchContent), values)); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs index 34d789ada..731e7a0dd 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_exception_when_content_cannot_be_resolved() { - SetupContext(Permissions.AppContentsUpdate); + SetupContext(Permissions.AppContentsUpdateOwn); var (_, _, query) = CreateTestData(true); @@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_exception_when_query_resolves_multiple_contents() { - var requestContext = SetupContext(Permissions.AppContentsUpdate); + var requestContext = SetupContext(Permissions.AppContentsUpdateOwn); var (id, data, query) = CreateTestData(true); @@ -240,7 +240,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_security_exception_when_user_has_no_permission_for_creating() { - SetupContext(Permissions.AppContentsRead); + SetupContext(Permissions.AppContentsReadOwn); var (id, data, _) = CreateTestData(false); @@ -257,7 +257,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_update_content() { - SetupContext(Permissions.AppContentsUpdate); + SetupContext(Permissions.AppContentsUpdateOwn); var (id, data, _) = CreateTestData(false); @@ -275,7 +275,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_security_exception_when_user_has_no_permission_for_updating() { - SetupContext(Permissions.AppContentsRead); + SetupContext(Permissions.AppContentsReadOwn); var (id, data, _) = CreateTestData(false); @@ -292,7 +292,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_patch_content() { - SetupContext(Permissions.AppContentsUpdate); + SetupContext(Permissions.AppContentsUpdateOwn); var (id, data, _) = CreateTestData(false); @@ -310,7 +310,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_security_exception_when_user_has_no_permission_for_patching() { - SetupContext(Permissions.AppContentsRead); + SetupContext(Permissions.AppContentsReadOwn); var (id, data, _) = CreateTestData(false); @@ -327,7 +327,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_change_content_status() { - SetupContext(Permissions.AppContentsUpdate); + SetupContext(Permissions.AppContentsUpdateOwn); var (id, _, _) = CreateTestData(false); @@ -344,7 +344,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_change_content_status_with_due_time() { - SetupContext(Permissions.AppContentsUpdate); + SetupContext(Permissions.AppContentsUpdateOwn); var time = Instant.FromDateTimeUtc(DateTime.UtcNow); @@ -363,7 +363,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_security_exception_when_user_has_no_permission_for_changing_status() { - SetupContext(Permissions.AppContentsRead); + SetupContext(Permissions.AppContentsReadOwn); var (id, _, _) = CreateTestData(false); @@ -380,7 +380,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_validate_content() { - SetupContext(Permissions.AppContentsRead); + SetupContext(Permissions.AppContentsReadOwn); var (id, _, _) = CreateTestData(false); @@ -398,7 +398,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_security_exception_when_user_has_no_permission_for_validation() { - SetupContext(Permissions.AppContentsDelete); + SetupContext(Permissions.AppContentsDeleteOwn); var (id, _, _) = CreateTestData(false); @@ -415,7 +415,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_delete_content() { - SetupContext(Permissions.AppContentsDelete); + SetupContext(Permissions.AppContentsDeleteOwn); var (id, _, _) = CreateTestData(false); @@ -433,7 +433,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_security_exception_when_user_has_no_permission_for_deletion() { - SetupContext(Permissions.AppContentsRead); + SetupContext(Permissions.AppContentsReadOwn); var (id, _, _) = CreateTestData(false); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs index 822511fa6..b61b114c6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs @@ -211,7 +211,7 @@ namespace Squidex.Domain.Apps.Entities.Contents foreach (var schemaId in allowedSchemas) { - var permission = Permissions.ForApp(Permissions.AppContentsRead, appId.Name, schemaId.Name).Id; + var permission = Permissions.ForApp(Permissions.AppContentsReadOwn, appId.Name, schemaId.Name).Id; claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission)); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs index c5c6b70ac..e2c81b349 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Validation; +using Squidex.Shared; using Xunit; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards @@ -27,8 +28,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards private readonly IContentWorkflow contentWorkflow = A.Fake(); private readonly IContentRepository contentRepository = A.Fake(); private readonly NamedId appId = NamedId.Of(DomainId.NewGuid(), "my-app"); + private readonly NamedId schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly ClaimsPrincipal user = Mocks.FrontendUser(); private readonly Instant dueTimeInPast = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1)); + private readonly RefToken actor = new RefToken(RefTokenType.Subject, "123"); [Fact] public async Task CanCreate_should_throw_exception_if_data_is_null() @@ -101,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards SetupCanUpdate(true); var content = CreateContent(Status.Draft); - var command = new UpdateContent(); + var command = CreateCommand(new UpdateContent()); await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, contentWorkflow), new ValidationError("Data is required.", "Data")); @@ -113,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards SetupCanUpdate(false); var content = CreateContent(Status.Draft); - var command = new UpdateContent { Data = new ContentData() }; + var command = CreateCommand(new UpdateContent { Data = new ContentData() }); await Assert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, contentWorkflow)); } @@ -124,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards SetupCanUpdate(true); var content = CreateContent(Status.Draft); - var command = new UpdateContent { Data = new ContentData(), User = user }; + var command = CreateCommand(new UpdateContent { Data = new ContentData() }); await GuardContent.CanUpdate(command, content, contentWorkflow); } @@ -135,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards SetupCanUpdate(true); var content = CreateContent(Status.Draft); - var command = new PatchContent(); + var command = CreateCommand(new PatchContent()); await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(command, content, contentWorkflow), new ValidationError("Data is required.", "Data")); @@ -147,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards SetupCanUpdate(false); var content = CreateContent(Status.Draft); - var command = new PatchContent { Data = new ContentData() }; + var command = CreateCommand(new PatchContent { Data = new ContentData() }); await Assert.ThrowsAsync(() => GuardContent.CanPatch(command, content, contentWorkflow)); } @@ -158,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards SetupCanUpdate(true); var content = CreateContent(Status.Draft); - var command = new PatchContent { Data = new ContentData(), User = user }; + var command = CreateCommand(new PatchContent { Data = new ContentData() }); await GuardContent.CanPatch(command, content, contentWorkflow); } @@ -169,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(true); var content = CreateContent(Status.Published); - var command = new ChangeContentStatus { Status = Status.Draft }; + var command = CreateCommand(new ChangeContentStatus { Status = Status.Draft }); await Assert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema)); } @@ -180,7 +183,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(false); var content = CreateContent(Status.Draft); - var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast, User = user }; + var command = CreateCommand(new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast }); A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) .Returns(true); @@ -195,7 +198,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(false); var content = CreateContent(Status.Draft); - var command = new ChangeContentStatus { Status = Status.Published, User = user }; + var command = CreateCommand(new ChangeContentStatus { Status = Status.Published }); A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) .Returns(false); @@ -210,7 +213,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(true); var content = CreateContent(Status.Published); - var command = new ChangeContentStatus { Status = Status.Draft, User = user }; + var command = CreateCommand(new ChangeContentStatus { Status = Status.Draft }); A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.Published)) .Returns(true); @@ -224,7 +227,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(true); var content = CreateDraftContent(Status.Draft); - var command = new ChangeContentStatus { Status = Status.Published }; + var command = CreateCommand(new ChangeContentStatus { Status = Status.Published }); await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema); } @@ -235,7 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(false); var content = CreateContent(Status.Draft); - var command = new ChangeContentStatus { Status = Status.Published, User = user }; + var command = CreateCommand(new ChangeContentStatus { Status = Status.Published }); A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) .Returns(true); @@ -246,10 +249,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards [Fact] public void CreateDraft_should_throw_exception_if_not_published() { - CreateSchema(false); - var content = CreateContent(Status.Draft); - var command = new CreateContentDraft(); + var command = CreateCommand(new CreateContentDraft()); Assert.Throws(() => GuardContent.CanCreateDraft(command, content)); } @@ -258,7 +259,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards public void CreateDraft_should_not_throw_exception() { var content = CreateContent(Status.Published); - var command = new CreateContentDraft(); + var command = CreateCommand(new CreateContentDraft()); GuardContent.CanCreateDraft(command, content); } @@ -266,10 +267,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards [Fact] public void CanDeleteDraft_should_throw_exception_if_no_draft_found() { - CreateSchema(false); + var schema = CreateSchema(false); var content = CreateContent(Status.Published); - var command = new DeleteContentDraft(); + var command = CreateCommand(new DeleteContentDraft()); Assert.Throws(() => GuardContent.CanDeleteDraft(command, content)); } @@ -278,7 +279,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards public void CanDeleteDraft_should_not_throw_exception() { var content = CreateDraftContent(Status.Draft); - var command = new DeleteContentDraft(); + var command = CreateCommand(new DeleteContentDraft()); GuardContent.CanDeleteDraft(command, content); } @@ -289,7 +290,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(true); var content = CreateContent(Status.Published); - var command = new DeleteContent(); + var command = CreateCommand(new DeleteContent()); await Assert.ThrowsAsync(() => GuardContent.CanDelete(command, content, contentRepository, schema)); } @@ -300,7 +301,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(true); var content = CreateContent(Status.Published); - var command = new DeleteContent(); + var command = CreateCommand(new DeleteContent()); A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.All)) .Returns(true); @@ -314,11 +315,45 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards var schema = CreateSchema(false); var content = CreateContent(Status.Published); - var command = new DeleteContent(); + var command = CreateCommand(new DeleteContent()); await GuardContent.CanDelete(command, content, contentRepository, schema); } + [Fact] + public void CheckPermission_should_not_throw_exception_if_content_is_from_current_user() + { + var content = CreateContent(status: Status.Published); + var command = CreateCommand(new DeleteContent()); + + GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete); + } + + [Fact] + public void CheckPermission_should_not_throw_exception_if_content_is_from_another_user_but_user_has_permission() + { + var permission = Permissions.ForApp(Permissions.AppContentsDelete, appId.Name, schemaId.Name).Id; + + var otherUser = Mocks.FrontendUser(permission: permission); + var otherActor = new RefToken(RefTokenType.Subject, "456"); + + var content = CreateContent(Status.Published); + var command = CreateCommand(new DeleteContent { Actor = otherActor, User = otherUser }); + + GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete); + } + + [Fact] + public void CheckPermission_should_exception_if_content_is_from_another_user_and_user_has_no_permission() + { + var otherActor = new RefToken(RefTokenType.Subject, "456"); + + var content = CreateContent(Status.Published); + var command = CreateCommand(new DeleteContent { Actor = otherActor }); + + Assert.Throws(() => GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete)); + } + private void SetupCanUpdate(bool canUpdate) { A.CallTo(() => contentWorkflow.CanUpdateAsync(A._, A._, user)) @@ -336,24 +371,40 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards return Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "my-schema"), new Schema("schema", isSingleton: isSingleton)); } - private IContentEntity CreateDraftContent(Status status) + private T CreateCommand(T command) where T : ContentCommand { - return new ContentEntity + if (command.Actor == null) { - Id = DomainId.NewGuid(), - NewStatus = status, - AppId = appId - }; + command.Actor = actor; + } + + if (command.User == null) + { + command.User = user; + } + + return command; + } + + private IContentEntity CreateDraftContent(Status status) + { + return CreateContentCore(new ContentEntity { NewStatus = status }); } private IContentEntity CreateContent(Status status) { - return new ContentEntity - { - Id = DomainId.NewGuid(), - Status = status, - AppId = appId - }; + return CreateContentCore(new ContentEntity { Status = status }); + } + + private IContentEntity CreateContentCore(ContentEntity content) + { + content.Id = DomainId.NewGuid(); + content.AppId = appId; + content.Created = default; + content.CreatedBy = actor; + content.SchemaId = schemaId; + + return content; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs index e8118b457..417ab0443 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs @@ -218,6 +218,30 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries Assert.Empty(result); } + [Fact] + public async Task QueryAll_should_only_query_only_users_contents_if_no_permission() + { + var ctx = + CreateContext(true, true, Permissions.AppContentsReadOwn); + + var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty); + + A.CallTo(() => contentRepository.QueryAsync(ctx.App, schema, A.That.Matches(x => x.CreatedBy!.Equals(ctx.User.Token())), SearchScope.All)) + .MustHaveHappened(); + } + + [Fact] + public async Task QueryAll_should_query_all_contents_if_user_has_permission() + { + var ctx = + CreateContext(true, true, Permissions.AppContentsRead); + + var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty); + + A.CallTo(() => contentRepository.QueryAsync(ctx.App, schema, A.That.Matches(x => x.CreatedBy == null), SearchScope.All)) + .MustHaveHappened(); + } + [Theory] [InlineData(1, 0, SearchScope.All)] [InlineData(1, 1, SearchScope.All)] @@ -252,7 +276,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries }); } - private Context CreateContext(bool isFrontend, bool allowSchema) + private Context CreateContext(bool isFrontend, bool allowSchema, string permissionId = Permissions.AppContentsRead) { var claimsIdentity = new ClaimsIdentity(); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); @@ -264,9 +288,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries if (allowSchema) { - var permission = Permissions.ForApp(Permissions.AppContentsRead, appId.Name, schemaId.Name).Id; + var concretePermission = Permissions.ForApp(permissionId, appId.Name, schemaId.Name).Id; - claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission)); + claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, concretePermission)); } return new Context(claimsPrincipal, Mocks.App(appId)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs index 2018c32d5..6421e1a76 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas [Fact] public async Task Should_return_result_to_schema_and_contents_if_matching_and_permission_given() { - var permission = Permissions.ForApp(Permissions.AppContentsRead, appId.Name, "schemaA2"); + var permission = Permissions.ForApp(Permissions.AppContentsReadOwn, appId.Name, "schemaA2"); var ctx = ContextWithPermission(permission.Id); @@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas [Fact] public async Task Should_return_result_to_schema_and_contents_if_schema_is_singleton() { - var permission = Permissions.ForApp(Permissions.AppContentsRead, appId.Name, "schemaA1"); + var permission = Permissions.ForApp(Permissions.AppContentsReadOwn, appId.Name, "schemaA1"); var ctx = ContextWithPermission(permission.Id); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs index 2e9520a2b..f077854b4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Shared; +using Squidex.Shared.Identity; namespace Squidex.Domain.Apps.Entities.TestHelpers { @@ -55,12 +56,12 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers return CreateUser(role, "api"); } - public static ClaimsPrincipal FrontendUser(string? role = null) + public static ClaimsPrincipal FrontendUser(string? role = null, string? permission = null) { - return CreateUser(role, DefaultClients.Frontend); + return CreateUser(role, DefaultClients.Frontend, permission); } - private static ClaimsPrincipal CreateUser(string? role, string client) + private static ClaimsPrincipal CreateUser(string? role, string client, string? permission = null) { var claimsIdentity = new ClaimsIdentity(); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); @@ -72,6 +73,11 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role)); } + if (permission != null) + { + claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission)); + } + return claimsPrincipal; } }