diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json
index e6f3e6530..342c3cd71 100644
--- a/backend/i18n/frontend_en.json
+++ b/backend/i18n/frontend_en.json
@@ -99,6 +99,7 @@
"assets.metadata": "Metadata",
"assets.metadataAdd": "Add Metadata",
"assets.move": "Move",
+ "assets.moved": "Asset has been moved.",
"assets.moveFailed": "Failed to move asset. Please reload.",
"assets.protected": "Protected",
"assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.",
diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json
index 7bc159889..37edffb5a 100644
--- a/backend/i18n/frontend_it.json
+++ b/backend/i18n/frontend_it.json
@@ -99,6 +99,7 @@
"assets.metadata": "Metadati",
"assets.metadataAdd": "Aggiungi un metadato",
"assets.move": "Move",
+ "assets.moved": "Asset has been moved.",
"assets.moveFailed": "Non è stato possibile spostare la risorsa. Per favore ricarica.",
"assets.protected": "Protetto",
"assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.",
diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json
index e4fc9ecdb..112673f52 100644
--- a/backend/i18n/frontend_nl.json
+++ b/backend/i18n/frontend_nl.json
@@ -99,6 +99,7 @@
"assets.metadata": "Metadata",
"assets.metadataAdd": "Metadata toevoegen",
"assets.move": "Move",
+ "assets.moved": "Asset has been moved.",
"assets.moveFailed": "Verplaatsen van item is mislukt. Laad opnieuw.",
"assets.protected": "Beschermd",
"assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.",
diff --git a/backend/i18n/frontend_pt.json b/backend/i18n/frontend_pt.json
index 129858483..8a691c0e8 100644
--- a/backend/i18n/frontend_pt.json
+++ b/backend/i18n/frontend_pt.json
@@ -99,6 +99,7 @@
"assets.metadata": "Metadados",
"assets.metadataAdd": "Adicionar metadados",
"assets.move": "Move",
+ "assets.moved": "Asset has been moved.",
"assets.moveFailed": "Falha ao mover ficheiro. Por favor, recarregue.",
"assets.protected": "Protegido",
"assets.protectedHint": "Os ativos são públicos por defeito. Todos com o link podem descarregar o ficheiro. Se fizer um ficheiro protegido, apenas utilizadores autenticados (normalmente um cliente) podem descarregar o ficheiro.",
diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json
index 29b6b4bd9..3a78f6b1e 100644
--- a/backend/i18n/frontend_zh.json
+++ b/backend/i18n/frontend_zh.json
@@ -99,6 +99,7 @@
"assets.metadata": "元数据",
"assets.metadataAdd": "添加元数据",
"assets.move": "Move",
+ "assets.moved": "Asset has been moved.",
"assets.moveFailed": "资源移动失败。请重新加载。",
"assets.protected": "受保护",
"assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.",
diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json
index e6f3e6530..342c3cd71 100644
--- a/backend/i18n/source/frontend_en.json
+++ b/backend/i18n/source/frontend_en.json
@@ -99,6 +99,7 @@
"assets.metadata": "Metadata",
"assets.metadataAdd": "Add Metadata",
"assets.move": "Move",
+ "assets.moved": "Asset has been moved.",
"assets.moveFailed": "Failed to move asset. Please reload.",
"assets.protected": "Protected",
"assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.",
diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs
index 4ae7775ba..fa5475f32 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs
@@ -7,10 +7,14 @@
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Billing;
+using Squidex.Domain.Apps.Entities.Teams;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation;
+using Squidex.Shared;
+using Squidex.Shared.Identity;
using Squidex.Text;
+using System.Security.Claims;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
@@ -136,7 +140,7 @@ public static class GuardApp
var team = await appProvider.GetTeamAsync(command.TeamId.Value, ct);
- if (team == null || !team.Contributors.ContainsKey(command.Actor.Identifier))
+ if (team == null || (!IsContributor(team, command.Actor) && !HasTransferPermission(command.User)))
{
e(T.Get("apps.transfer.teamNotFound"));
}
@@ -146,6 +150,23 @@ public static class GuardApp
e(T.Get("apps.transfer.planAssigned"));
}
});
+
+ static bool IsContributor(ITeamEntity team, RefToken actor)
+ {
+ return team.Contributors.ContainsKey(actor.Identifier);
+ }
+
+ static bool HasTransferPermission(ClaimsPrincipal? user)
+ {
+ var permissions = user?.Claims.Permissions();
+
+ if (permissions == null)
+ {
+ return false;
+ }
+
+ return permissions.Allows(PermissionIds.Transfer);
+ }
}
public static void CanChangePlan(ChangePlan command, IAppEntity app, IBillingPlans billingPlans)
diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs
index 8df0edf56..35d38d108 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs
@@ -30,9 +30,7 @@ public static class SecurityExtensions
throw new DomainForbiddenException(T.Get("common.errorNoPermission"));
}
- var permission = PermissionIds.ForApp(permissionId, context.App.Name, context.Schema.SchemaDef.Name);
-
- if (!permissions.Allows(permission))
+ if (!permissions.Allows(permissionId, context.App.Name, context.Schema.SchemaDef.Name))
{
throw new DomainForbiddenException(T.Get("common.errorNoPermission"));
}
diff --git a/backend/src/Squidex.Shared/PermissionIds.cs b/backend/src/Squidex.Shared/PermissionIds.cs
index 64d3e4e96..bd77a214b 100644
--- a/backend/src/Squidex.Shared/PermissionIds.cs
+++ b/backend/src/Squidex.Shared/PermissionIds.cs
@@ -42,6 +42,9 @@ namespace Squidex.Shared
// Team
public const string Team = "squidex.teams.{team}";
+ // Team Transfer
+ public const string Transfer = "squidex.transfer";
+
// Team General
public const string TeamAdmin = "squidex.teams.{team}.*";
public const string TeamUpdate = "squidex.teams.{team}.update";
diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj
index df2c37be5..d98684fc1 100644
--- a/backend/src/Squidex/Squidex.csproj
+++ b/backend/src/Squidex/Squidex.csproj
@@ -129,6 +129,7 @@
+
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
index 469fe7cd1..ac1fa46f2 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.TestHelpers;
@@ -47,6 +48,9 @@ public class AppDomainObjectTests : HandlerTestBase
{
user = UserMocks.User(contributorId);
+ A.CallTo(() => Team.Contributors)
+ .Returns(Contributors.Empty.Assign(User.Identifier, Role.Owner));
+
A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId, CancellationToken))
.Returns(user);
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs
index e28c6155f..7d8a4be02 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs
@@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Validation;
+using Squidex.Shared;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
@@ -22,7 +23,6 @@ public class GuardAppTests : GivenContext, IClassFixture
{
private readonly IUserResolver users = A.Fake();
private readonly IBillingPlans billingPlans = A.Fake();
- private readonly RefToken actor = RefToken.User("42");
public GuardAppTests()
{
@@ -34,6 +34,9 @@ public class GuardAppTests : GivenContext, IClassFixture
A.CallTo(() => billingPlans.GetPlan("basic"))
.Returns(new Plan());
+
+ A.CallTo(() => Team.Contributors)
+ .Returns(Contributors.Empty.Assign(User.Identifier, Role.Owner));
}
[Fact]
@@ -142,12 +145,22 @@ public class GuardAppTests : GivenContext, IClassFixture
await GuardApp.CanTransfer(command, App, AppProvider, default);
}
+ [Fact]
+ public async Task CanTransfer_should_not_throw_exception_if_user_has_transfer_permission()
+ {
+ var admin = Mocks.ApiUser(permission: PermissionIds.Transfer);
+
+ var command = new TransferToTeam { TeamId = TeamId, Actor = User, User = admin };
+
+ await GuardApp.CanTransfer(command, App, AppProvider, default);
+ }
+
[Fact]
public async Task CanTransfer_should_throw_exception_if_team_does_not_exist()
{
Team = null!;
- var command = new TransferToTeam { TeamId = TeamId, Actor = actor };
+ var command = new TransferToTeam { TeamId = TeamId, Actor = User };
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
new ValidationError("The team does not exist."));
@@ -156,7 +169,9 @@ public class GuardAppTests : GivenContext, IClassFixture
[Fact]
public async Task CanTransfer_should_throw_exception_if_actor_is_not_part_of_team()
{
- var command = new TransferToTeam { TeamId = TeamId, Actor = actor };
+ var nonContributor = RefToken.User("Other");
+
+ var command = new TransferToTeam { TeamId = TeamId, Actor = nonContributor };
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
new ValidationError("The team does not exist."));
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
index 50c6867a5..66ece57fc 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
+using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
@@ -38,7 +39,7 @@ public class DynamicContentWorkflowTests : GivenContext
new Dictionary
{
[Status.Archived] = WorkflowTransition.Always,
- [Status.Published] = WorkflowTransition.When("data.field.iv === 2", "Editor")
+ [Status.Published] = WorkflowTransition.When("data.field.iv === 2", Role.Editor)
}.ToReadonlyDictionary(),
StatusColors.Draft),
[Status.Published] =
@@ -48,7 +49,7 @@ public class DynamicContentWorkflowTests : GivenContext
[Status.Archived] = WorkflowTransition.Always,
[Status.Draft] = WorkflowTransition.Always
}.ToReadonlyDictionary(),
- StatusColors.Published, NoUpdate.When("data.field.iv === 2", "Owner", "Editor"))
+ StatusColors.Published, NoUpdate.When("data.field.iv === 2", Role.Owner, Role.Editor))
}.ToReadonlyDictionary());
public DynamicContentWorkflowTests()
@@ -120,7 +121,7 @@ public class DynamicContentWorkflowTests : GivenContext
[Fact]
public async Task Should_allow_publish_on_create()
{
- var actual = await sut.CanPublishInitialAsync(Schema, Mocks.FrontendUser("Editor"));
+ var actual = await sut.CanPublishInitialAsync(Schema, Mocks.FrontendUser(Role.Editor));
Assert.True(actual);
}
@@ -128,7 +129,7 @@ public class DynamicContentWorkflowTests : GivenContext
[Fact]
public async Task Should_not_allow_publish_on_create_if_role_not_allowed()
{
- var actual = await sut.CanPublishInitialAsync(Schema, Mocks.FrontendUser("Developer"));
+ var actual = await sut.CanPublishInitialAsync(Schema, Mocks.FrontendUser(Role.Developer));
Assert.False(actual);
}
@@ -138,7 +139,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Draft, 2);
- var actual = await sut.CanMoveToAsync(Schema, content.Status, Status.Published, content.Data, Mocks.FrontendUser("Editor"));
+ var actual = await sut.CanMoveToAsync(Schema, content.Status, Status.Published, content.Data, Mocks.FrontendUser(Role.Editor));
Assert.True(actual);
}
@@ -148,7 +149,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Draft, 2);
- var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser("Editor"));
+ var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser(Role.Editor));
Assert.True(actual);
}
@@ -158,7 +159,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Draft, 2);
- var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser("Developer"));
+ var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser(Role.Developer));
Assert.False(actual);
}
@@ -168,7 +169,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Draft, 2);
- var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser("Editor"));
+ var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser(Role.Editor));
Assert.True(actual);
}
@@ -178,7 +179,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Draft, 4);
- var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser("Editor"));
+ var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser(Role.Editor));
Assert.False(actual);
}
@@ -188,7 +189,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Published, 2);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Developer"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Developer));
Assert.True(actual);
}
@@ -198,7 +199,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Published, 2);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Developer"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Developer));
Assert.True(actual);
}
@@ -208,7 +209,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Archived, 2);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Developer"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Developer));
Assert.False(actual);
}
@@ -218,7 +219,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Published, 2);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Owner"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Owner));
Assert.False(actual);
}
@@ -228,7 +229,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Published, 1);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Owner"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Owner));
Assert.True(actual);
}
@@ -238,7 +239,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Published, 2);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Editor"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Editor));
Assert.False(actual);
}
@@ -248,7 +249,7 @@ public class DynamicContentWorkflowTests : GivenContext
{
var content = CreateContent(Status.Published, 1);
- var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser("Owner"));
+ var actual = await sut.CanUpdateAsync(content, content.Status, Mocks.FrontendUser(Role.Owner));
Assert.True(actual);
}
@@ -263,7 +264,7 @@ public class DynamicContentWorkflowTests : GivenContext
new StatusInfo(Status.Archived, StatusColors.Archived)
};
- var actual = await sut.GetNextAsync(content, content.Status, Mocks.FrontendUser("Developer"));
+ var actual = await sut.GetNextAsync(content, content.Status, Mocks.FrontendUser(Role.Developer));
actual.Should().BeEquivalentTo(expected);
}
@@ -278,7 +279,7 @@ public class DynamicContentWorkflowTests : GivenContext
new StatusInfo(Status.Archived, StatusColors.Archived)
};
- var actual = await sut.GetNextAsync(content, content.Status, Mocks.FrontendUser("Editor"));
+ var actual = await sut.GetNextAsync(content, content.Status, Mocks.FrontendUser(Role.Editor));
actual.Should().BeEquivalentTo(expected);
}
@@ -294,7 +295,7 @@ public class DynamicContentWorkflowTests : GivenContext
new StatusInfo(Status.Published, StatusColors.Published)
};
- var actual = await sut.GetNextAsync(content, content.Status, Mocks.FrontendUser("Editor"));
+ var actual = await sut.GetNextAsync(content, content.Status, Mocks.FrontendUser(Role.Editor));
actual.Should().BeEquivalentTo(expected);
}
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs
index dcbdfb04a..6e1d6e3c8 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs
@@ -75,7 +75,7 @@ public class TeamsIndexTests : GivenContext
private static ITeamEntity SetupTeam(long version)
{
- var team = Mocks.Team(DomainId.NewGuid());
+ var team = Mocks.Team(DomainId.NewGuid(), "my-team");
A.CallTo(() => team.Version)
.Returns(version);
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs
index 4715fe381..bf29112fd 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs
@@ -5,14 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System.Security.Claims;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Teams;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.Security;
-using Squidex.Shared;
-using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.TestHelpers;
@@ -27,6 +23,8 @@ public abstract class GivenContext
public DomainId TeamId { get; } = DomainId.NewGuid();
+ public string TeamName { get; } = "my-team";
+
public NamedId AppId { get; } = NamedId.Of(DomainId.NewGuid(), "my-app");
public NamedId SchemaId { get; } = NamedId.Of(DomainId.NewGuid(), "my-schema");
@@ -70,31 +68,27 @@ public abstract class GivenContext
protected GivenContext()
{
- App = Mocks.App(AppId, Language.EN, Language.DE);
+ Team = Mocks.Team(TeamId, TeamName);
- Team = Mocks.Team(TeamId, "my-team", User.Identifier);
+ App = Mocks.App(AppId,
+ Language.EN,
+ Language.DE);
Schema = Mocks.Schema(AppId, SchemaId);
}
- public Context CreateContext(bool isFrontend, params string[] permissions)
+ public Context CreateContext(params string[] permissions)
{
- var claimsIdentity = new ClaimsIdentity();
- var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
-
- claimsIdentity.AddClaim(new Claim(OpenIdClaims.Subject, User.Identifier));
+ var principal = Mocks.CreateUser(false, null, permissions);
- if (isFrontend)
- {
- claimsIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, DefaultClients.Frontend));
- }
+ return new Context(principal, App);
+ }
- foreach (var permission in permissions)
- {
- claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
- }
+ public Context CreateContext(bool isFrontend, params string[] permissions)
+ {
+ var principal = Mocks.CreateUser(isFrontend, null, permissions);
- return new Context(claimsPrincipal, App);
+ return new Context(principal, App);
}
private static IContextProvider CreateContextProvider(Context context)
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 51850694e..3e62b56c5 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs
@@ -54,43 +54,58 @@ public static class Mocks
return schema;
}
- public static ITeamEntity Team(DomainId teamId, string teamName = "my-team", string contributor = "user")
+ public static ITeamEntity Team(DomainId teamId, string teamName)
{
var team = A.Fake();
A.CallTo(() => team.Id).Returns(teamId);
A.CallTo(() => team.UniqueId).Returns(teamId);
A.CallTo(() => team.Name).Returns(teamName);
- A.CallTo(() => team.Contributors).Returns(Contributors.Empty.Assign(contributor, Role.Owner));
return team;
}
- public static ClaimsPrincipal ApiUser(string? role = null)
+ public static ClaimsPrincipal ApiUser(string? role = null, params string[] permissions)
{
- return CreateUser(role, "api");
+ return CreateUser(false, role, permissions);
+ }
+
+ public static ClaimsPrincipal ApiUser(string? role = null, string? permission = null)
+ {
+ return CreateUser(false, role, permission);
+ }
+
+ public static ClaimsPrincipal FrontendUser(string? role = null, params string[] permissions)
+ {
+ return CreateUser(true, role, permissions);
}
public static ClaimsPrincipal FrontendUser(string? role = null, string? permission = null)
{
- return CreateUser(role, DefaultClients.Frontend, permission);
+ return CreateUser(true, role, permission);
}
- private static ClaimsPrincipal CreateUser(string? role, string client, string? permission = null)
+ public static ClaimsPrincipal CreateUser(bool isFrontend, string? role, params string?[] permissions)
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
- claimsIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, client));
+ if (isFrontend)
+ {
+ claimsIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, DefaultClients.Frontend));
+ }
if (role != null)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
- if (permission != null)
+ foreach (var permission in permissions)
{
- claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
+ if (permission != null)
+ {
+ claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
+ }
}
return claimsPrincipal;
diff --git a/frontend/src/app/features/teams/pages/contributors/contributor-add-form.component.html b/frontend/src/app/features/teams/pages/contributors/contributor-add-form.component.html
index 800b733ff..660300e8e 100644
--- a/frontend/src/app/features/teams/pages/contributors/contributor-add-form.component.html
+++ b/frontend/src/app/features/teams/pages/contributors/contributor-add-form.component.html
@@ -24,7 +24,7 @@
diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts
index a5629de1d..5b02a7ac8 100644
--- a/frontend/src/environments/environment.ts
+++ b/frontend/src/environments/environment.ts
@@ -7,7 +7,7 @@ export const environment = {
production: false,
textLogger: true,
textResolver: () => {
- const culture = window['options']?.more?.culture || 'en';
+ const culture = window['options']?.culture || 'en';
return require(`./../../../backend/i18n/frontend_${culture}.json`);
},
diff --git a/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs
index d55f0750d..163eada50 100644
--- a/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs
+++ b/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs
@@ -1225,4 +1225,40 @@ public class ContentUpdateTests : IClassFixture
}
}
}
+
+ [Fact]
+ public async Task Should_create_content_with_custom_id_and_delete_it()
+ {
+ var id = "author-12345–fragment-text";
+
+ // STEP 1: Create a new item with a custom id.
+ var options = new ContentCreateOptions { Id = id, Publish = true };
+
+ var content = await _.Contents.CreateAsync(new TestEntityData
+ {
+ Number = 1
+ }, options);
+
+ Assert.Equal(id, content.Id);
+
+
+ // STEP 2: Delete with bulk update.
+ await _.Contents.BulkUpdateAsync(new BulkUpdate
+ {
+ Jobs = new List
+ {
+ new BulkUpdateJob
+ {
+ Type = BulkUpdateType.Delete,
+ Id = id
+ }
+ }
+ });
+
+
+ // STEP 3: Retrieve all items and ensure that the deleted item does not exist.
+ var updated = await _.Contents.GetAsync();
+
+ Assert.DoesNotContain(updated.Items, x => x.Id == id);
+ }
}