diff --git a/src/Squidex.Domain.Apps.Core/Apps/AppClientPermission.cs b/src/Squidex.Domain.Apps.Core/Apps/AppClientPermission.cs new file mode 100644 index 000000000..cbc4cdaf2 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core/Apps/AppClientPermission.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// AppClientPermission.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Apps +{ + public enum AppClientPermission + { + Developer, + Editor, + Reader + } +} diff --git a/src/Squidex.Domain.Apps.Core/Apps/AppContributorPermission.cs b/src/Squidex.Domain.Apps.Core/Apps/AppContributorPermission.cs new file mode 100644 index 000000000..c05d7527a --- /dev/null +++ b/src/Squidex.Domain.Apps.Core/Apps/AppContributorPermission.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// AppContributorPermission.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Apps +{ + public enum AppContributorPermission + { + Owner, + Developer, + Editor + } +} diff --git a/src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs b/src/Squidex.Domain.Apps.Core/Apps/AppPermission.cs similarity index 88% rename from src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs rename to src/Squidex.Domain.Apps.Core/Apps/AppPermission.cs index 1d91075f1..e9bf142a6 100644 --- a/src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs +++ b/src/Squidex.Domain.Apps.Core/Apps/AppPermission.cs @@ -1,5 +1,5 @@ // ========================================================================== -// PermissionLevel.cs +// AppPermission.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -8,7 +8,7 @@ namespace Squidex.Domain.Apps.Core.Apps { - public enum PermissionLevel + public enum AppPermission { Owner, Developer, diff --git a/src/Squidex.Domain.Apps.Core/Apps/RoleExtension.cs b/src/Squidex.Domain.Apps.Core/Apps/RoleExtension.cs new file mode 100644 index 000000000..40245615f --- /dev/null +++ b/src/Squidex.Domain.Apps.Core/Apps/RoleExtension.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// RoleExtension.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Apps +{ + public static class RoleExtension + { + public static AppPermission ToAppPermission(this AppClientPermission clientPermission) + { + Guard.Enum(clientPermission, nameof(clientPermission)); + + return (AppPermission)Enum.Parse(typeof(AppPermission), clientPermission.ToString()); + } + + public static AppPermission ToAppPermission(this AppContributorPermission contributorPermission) + { + Guard.Enum(contributorPermission, nameof(contributorPermission)); + + return (AppPermission)Enum.Parse(typeof(AppPermission), contributorPermission.ToString()); + } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppClientChanged.cs b/src/Squidex.Domain.Apps.Events/Apps/AppClientUpdated.cs similarity index 71% rename from src/Squidex.Domain.Apps.Events/Apps/AppClientChanged.cs rename to src/Squidex.Domain.Apps.Events/Apps/AppClientUpdated.cs index 89aeb2b7a..2289d594e 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppClientChanged.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppClientUpdated.cs @@ -6,15 +6,16 @@ // All rights reserved. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Domain.Apps.Events.Apps { - [EventType(nameof(AppClientChanged))] - public sealed class AppClientChanged : AppEvent + [EventType(nameof(AppClientUpdated))] + public sealed class AppClientUpdated : AppEvent { public string Id { get; set; } - public bool IsReader { get; set; } + public AppClientPermission Permission { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs b/src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs index 3924c2ef1..d4eb3bcb1 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs @@ -16,6 +16,6 @@ namespace Squidex.Domain.Apps.Events.Apps { public string ContributorId { get; set; } - public PermissionLevel Permission { get; set; } + public AppContributorPermission Permission { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Apps/Old/AppClientChanged.cs b/src/Squidex.Domain.Apps.Events/Apps/Old/AppClientChanged.cs new file mode 100644 index 000000000..6370c47d7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Apps/Old/AppClientChanged.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// AppClientChanged.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Events.Apps.Old +{ + [EventType(nameof(AppClientChanged))] + public sealed class AppClientChanged : AppEvent, IMigratedEvent + { + public string Id { get; set; } + + public bool IsReader { get; set; } + + public IEvent Migrate() + { + var permission = IsReader ? AppClientPermission.Reader : AppClientPermission.Editor; + + return SimpleMapper.Map(this, new AppClientUpdated { Permission = permission }); + } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityClient.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityClient.cs index ef3305607..fde8176cf 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityClient.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityClient.cs @@ -7,6 +7,7 @@ // ========================================================================== using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Read.Apps; namespace Squidex.Domain.Apps.Read.MongoDb.Apps @@ -25,13 +26,18 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps [BsonElement] public string Name { get; set; } - [BsonIgnoreIfDefault] + [BsonRequired] [BsonElement] - public bool IsReader { get; set; } + public AppClientPermission Permission { get; set; } string IAppClientEntity.Name { get { return !string.IsNullOrWhiteSpace(Name) ? Name : Id; } } + + public MongoAppEntityClient() + { + Permission = AppClientPermission.Editor; + } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityContributor.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityContributor.cs index 7959e2252..e3630c67c 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityContributor.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityContributor.cs @@ -20,6 +20,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps [BsonRequired] [BsonElement] - public PermissionLevel Permission { get; set; } + public AppContributorPermission Permission { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs index edf161961..86f005e06 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs @@ -66,11 +66,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps }); } - protected Task On(AppClientChanged @event, EnvelopeHeaders headers) + protected Task On(AppClientUpdated @event, EnvelopeHeaders headers) { return Collection.UpdateAsync(@event, headers, a => { - a.Clients[@event.Id].IsReader = @event.IsReader; + a.Clients[@event.Id].Permission = @event.Permission; }); } diff --git a/src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs index c9b1fac4f..6c8839b5a 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs @@ -32,8 +32,11 @@ namespace Squidex.Domain.Apps.Read.Apps AddEventMessage( "revoked client {[Id]}"); + AddEventMessage( + "updated client {[Id]}"); + AddEventMessage( - "named client {[Id]} as {[Name]}"); + "renamed client {[Id]} to {[Name]}"); AddEventMessage( "added language {[Language]}"); diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs b/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs index c3a5ff8e2..5f0cc794e 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs @@ -6,6 +6,8 @@ // All rights reserved. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; + namespace Squidex.Domain.Apps.Read.Apps { public interface IAppClientEntity @@ -16,6 +18,6 @@ namespace Squidex.Domain.Apps.Read.Apps string Secret { get; } - bool IsReader { get; } + AppClientPermission Permission { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs b/src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs index d682a40e2..8cc14131d 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs @@ -14,6 +14,6 @@ namespace Squidex.Domain.Apps.Read.Apps { string ContributorId { get; } - PermissionLevel Permission { get; } + AppContributorPermission Permission { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs index 7360cffdf..731e4de3c 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations } if (@event.Payload is AppClientAttached || - @event.Payload is AppClientChanged || + @event.Payload is AppClientUpdated || @event.Payload is AppClientRenamed || @event.Payload is AppClientRevoked || @event.Payload is AppPlanChanged || diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppClient.cs b/src/Squidex.Domain.Apps.Write/Apps/AppClient.cs index ea97ee153..b39b4838c 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppClient.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppClient.cs @@ -7,6 +7,7 @@ // ========================================================================== using System; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Write.Apps @@ -15,28 +16,29 @@ namespace Squidex.Domain.Apps.Write.Apps { private readonly string name; private readonly string secret; - private readonly bool isReader; + private readonly AppClientPermission permission; - public AppClient(string secret, string name, bool isReader) + public AppClient(string secret, string name, AppClientPermission permission) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(secret, nameof(secret)); + Guard.Enum(permission, nameof(permission)); this.name = name; this.secret = secret; - this.isReader = isReader; + this.permission = permission; } - public AppClient Change(bool newIsReader, Func message) + public AppClient Update(AppClientPermission newPermission, Func message) { - if (isReader == newIsReader) + if (permission == newPermission) { - var error = new ValidationError("Client has already the reader state.", "IsReader"); + var error = new ValidationError("Client has already the permission.", "IsReader"); throw new ValidationException(message(), error); } - return new AppClient(secret, name, newIsReader); + return new AppClient(secret, name, newPermission); } public AppClient Rename(string newName, Func message) @@ -48,7 +50,7 @@ namespace Squidex.Domain.Apps.Write.Apps throw new ValidationException(message(), error); } - return new AppClient(secret, newName, isReader); + return new AppClient(secret, newName, permission); } } } diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppClients.cs b/src/Squidex.Domain.Apps.Write/Apps/AppClients.cs index fc7be6fc0..c5704c298 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppClients.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppClients.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Write.Apps @@ -25,7 +26,7 @@ namespace Squidex.Domain.Apps.Write.Apps { ThrowIfFound(id, () => "Cannot add client"); - clients[id] = new AppClient(secret, id, false); + clients[id] = new AppClient(secret, id, AppClientPermission.Editor); } public void Rename(string clientId, string name) @@ -35,11 +36,11 @@ namespace Squidex.Domain.Apps.Write.Apps clients[clientId] = clients[clientId].Rename(name, () => "Cannot rename client"); } - public void Change(string clientId, bool isReader) + public void Update(string clientId, AppClientPermission permission) { ThrowIfNotFound(clientId); - clients[clientId] = clients[clientId].Change(isReader, () => "Cannot change client"); + clients[clientId] = clients[clientId].Update(permission, () => "Cannot update client"); } public void Revoke(string clientId) diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppContributors.cs b/src/Squidex.Domain.Apps.Write/Apps/AppContributors.cs index 94db67c9a..fd12e6cc4 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppContributors.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppContributors.cs @@ -16,14 +16,14 @@ namespace Squidex.Domain.Apps.Write.Apps { public class AppContributors { - private readonly Dictionary contributors = new Dictionary(); + private readonly Dictionary contributors = new Dictionary(); public int Count { get { return contributors.Count; } } - public void Assign(string contributorId, PermissionLevel permission) + public void Assign(string contributorId, AppContributorPermission permission) { string Message() => "Cannot assign contributor"; @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Write.Apps } } - private void ThrowIfFound(string contributorId, PermissionLevel permission, Func message) + private void ThrowIfFound(string contributorId, AppContributorPermission permission, Func message) { if (contributors.TryGetValue(contributorId, out var currentPermission) && currentPermission == permission) { @@ -61,13 +61,13 @@ namespace Squidex.Domain.Apps.Write.Apps } } - private void ThrowIfNoOwner(Action> change, Func message) + private void ThrowIfNoOwner(Action> change, Func message) { - var contributorsCopy = new Dictionary(contributors); + var contributorsCopy = new Dictionary(contributors); change(contributorsCopy); - if (contributorsCopy.All(x => x.Value != PermissionLevel.Owner)) + if (contributorsCopy.All(x => x.Value != AppContributorPermission.Owner)) { var error = new ValidationError("Contributor is the last owner", "ContributorId"); diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs index cdc629406..4c217c29c 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs @@ -70,9 +70,9 @@ namespace Squidex.Domain.Apps.Write.Apps clients.Add(@event.Id, @event.Secret); } - protected void On(AppClientChanged @event) + protected void On(AppClientUpdated @event) { - clients.Change(@event.Id, @event.IsReader); + clients.Update(@event.Id, @event.Permission); } protected void On(AppClientRenamed @event) @@ -139,9 +139,9 @@ namespace Squidex.Domain.Apps.Write.Apps RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); } - if (command.IsReader.HasValue) + if (command.Permission.HasValue) { - RaiseEvent(SimpleMapper.Map(command, new AppClientChanged { IsReader = command.IsReader.Value })); + RaiseEvent(SimpleMapper.Map(command, new AppClientUpdated { Permission = command.Permission.Value })); } return this; @@ -253,7 +253,7 @@ namespace Squidex.Domain.Apps.Write.Apps private static AppContributorAssigned CreateInitialOwner(NamedId id, SquidexCommand command) { - return new AppContributorAssigned { AppId = id, ContributorId = command.Actor.Identifier, Permission = PermissionLevel.Owner }; + return new AppContributorAssigned { AppId = id, ContributorId = command.Actor.Identifier, Permission = AppContributorPermission.Owner }; } private void ThrowIfOtherUser(ChangePlan command) diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs b/src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs index 0ba37d8e6..c6127045b 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs @@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Commands { public string ContributorId { get; set; } - public PermissionLevel Permission { get; set; } + public AppContributorPermission Permission { get; set; } public void Validate(IList errors) { @@ -24,6 +24,11 @@ namespace Squidex.Domain.Apps.Write.Apps.Commands { errors.Add(new ValidationError("Contributor id not assigned", nameof(ContributorId))); } + + if (!Permission.IsEnumValue()) + { + errors.Add(new ValidationError("Permission is not valid", nameof(Permission))); + } } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs b/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs index 2f954143a..6c5412bfa 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Write.Apps.Commands @@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Commands public string Name { get; set; } - public bool? IsReader { get; set; } + public AppClientPermission? Permission { get; set; } public void Validate(IList errors) { @@ -26,9 +27,14 @@ namespace Squidex.Domain.Apps.Write.Apps.Commands errors.Add(new ValidationError("Client id must be a valid slug", nameof(Id))); } - if (string.IsNullOrWhiteSpace(Name) && IsReader == null) + if (string.IsNullOrWhiteSpace(Name) && Permission == null) { - errors.Add(new ValidationError("Either name or reader state must be defined.", nameof(Name), nameof(IsReader))); + errors.Add(new ValidationError("Either name or permission must be defined.", nameof(Name), nameof(Permission))); + } + + if (Permission.HasValue && !Permission.Value.IsEnumValue()) + { + errors.Add(new ValidationError("Permission is not valid.", nameof(Permission))); } } } diff --git a/src/Squidex.Infrastructure/ValidationException.cs b/src/Squidex.Infrastructure/ValidationException.cs index 7f9adf286..637021487 100644 --- a/src/Squidex.Infrastructure/ValidationException.cs +++ b/src/Squidex.Infrastructure/ValidationException.cs @@ -45,5 +45,10 @@ namespace Squidex.Infrastructure { this.errors = errors ?? FallbackErrors; } + + public override string ToString() + { + return string.Join(" ", Enumerable.Repeat(Message, 1).Union(Errors.Select(x => x.Message))); + } } } diff --git a/src/Squidex/Controllers/Api/Apps/Models/AppDto.cs b/src/Squidex/Controllers/Api/Apps/Models/AppDto.cs index 0a3d6a935..f0eddfd0e 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/AppDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/AppDto.cs @@ -48,6 +48,6 @@ namespace Squidex.Controllers.Api.Apps.Models /// The permission level of the user. /// [JsonConverter(typeof(StringEnumConverter))] - public PermissionLevel Permission { get; set; } + public AppContributorPermission Permission { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Apps/Models/AssignAppContributorDto.cs b/src/Squidex/Controllers/Api/Apps/Models/AssignAppContributorDto.cs index 723102960..d200b687e 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/AssignAppContributorDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/AssignAppContributorDto.cs @@ -25,6 +25,6 @@ namespace Squidex.Controllers.Api.Apps.Models /// The permission level as a contributor. /// [JsonConverter(typeof(StringEnumConverter))] - public PermissionLevel Permission { get; set; } + public AppPermission Permission { get; set; } } } \ No newline at end of file diff --git a/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs b/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs index efe437699..90f74e701 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs @@ -7,6 +7,9 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Controllers.Api.Apps.Models { @@ -31,9 +34,10 @@ namespace Squidex.Controllers.Api.Apps.Models public string Name { get; set; } /// - /// Determines if the client is a reader. + /// The permissions of the client. /// [Required] - public bool IsReader { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public AppClientPermission Permission { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Apps/Models/ContributorDto.cs b/src/Squidex/Controllers/Api/Apps/Models/ContributorDto.cs index f275f9bdd..dd5bed79b 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/ContributorDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/ContributorDto.cs @@ -25,6 +25,6 @@ namespace Squidex.Controllers.Api.Apps.Models /// The permission level as a contributor. /// [JsonConverter(typeof(StringEnumConverter))] - public PermissionLevel Permission { get; set; } + public AppContributorPermission Permission { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs b/src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs index 13d20bf25..2140f52ef 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs @@ -7,6 +7,9 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Controllers.Api.Apps.Models { @@ -19,8 +22,9 @@ namespace Squidex.Controllers.Api.Apps.Models public string Name { get; set; } /// - /// Determines if the client is a reader. + /// The permissions of the client. /// - public bool? IsReader { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public AppClientPermission? Permission { get; set; } } } diff --git a/src/Squidex/Pipeline/AppApiFilter.cs b/src/Squidex/Pipeline/AppApiFilter.cs index 96eb967c8..dd93aa41e 100644 --- a/src/Squidex/Pipeline/AppApiFilter.cs +++ b/src/Squidex/Pipeline/AppApiFilter.cs @@ -91,22 +91,22 @@ namespace Squidex.Pipeline switch (permission.Value) { - case PermissionLevel.Owner: + case AppPermission.Owner: defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppOwner)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppDeveloper)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader)); break; - case PermissionLevel.Developer: + case AppPermission.Developer: defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppDeveloper)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader)); break; - case PermissionLevel.Editor: + case AppPermission.Editor: defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader)); break; - case PermissionLevel.Reader: + case AppPermission.Reader: defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader)); break; } @@ -115,7 +115,7 @@ namespace Squidex.Pipeline } } - private static PermissionLevel? FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user) + private static AppPermission? FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user) { var client = app.Clients.FirstOrDefault(x => string.Equals(x.Id, user.GetClientId(), StringComparison.OrdinalIgnoreCase)); @@ -124,10 +124,10 @@ namespace Squidex.Pipeline return null; } - return client.IsReader ? PermissionLevel.Reader : PermissionLevel.Editor; + return client.Permission.ToAppPermission(); } - private static PermissionLevel? FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user) + private static AppPermission? FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user) { var subject = user.FindFirst(OpenIdClaims.Subject)?.Value; @@ -138,7 +138,7 @@ namespace Squidex.Pipeline var contributor = app.Contributors.FirstOrDefault(x => string.Equals(x.ContributorId, subject, StringComparison.OrdinalIgnoreCase)); - return contributor?.Permission; + return contributor?.Permission.ToAppPermission(); } } } diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.html b/src/Squidex/app/features/settings/pages/clients/client.component.html index fc6e57d12..c6bb7a5b7 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.html +++ b/src/Squidex/app/features/settings/pages/clients/client.component.html @@ -70,13 +70,11 @@ - + Permission -
- -
+ diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.ts b/src/Squidex/app/features/settings/pages/clients/client.component.ts index 5b3a16778..8a9526d79 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.ts +++ b/src/Squidex/app/features/settings/pages/clients/client.component.ts @@ -36,7 +36,7 @@ export class ClientComponent extends ComponentBase { public revoking = new EventEmitter(); @Output() - public changing = new EventEmitter(); + public updating = new EventEmitter(); @Input() public appName: string; @@ -44,6 +44,8 @@ export class ClientComponent extends ComponentBase { @Input() public client: AppClientDto; + public clientPermissions = [ 'Developer', 'Editor', 'Reader' ]; + public isRenaming = false; public token: AccessTokenDto; diff --git a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html index 3752e2957..187611585 100644 --- a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html +++ b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html @@ -19,7 +19,7 @@
diff --git a/src/Squidex/app/features/settings/pages/clients/clients-page.component.ts b/src/Squidex/app/features/settings/pages/clients/clients-page.component.ts index 3d5b61494..1c89ec2a1 100644 --- a/src/Squidex/app/features/settings/pages/clients/clients-page.component.ts +++ b/src/Squidex/app/features/settings/pages/clients/clients-page.component.ts @@ -89,13 +89,13 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit { }); } - public changeClient(client: AppClientDto, isReader: boolean) { - const requestDto = new UpdateAppClientDto(undefined, isReader); + public updateClient(client: AppClientDto, permission: string) { + const requestDto = new UpdateAppClientDto(undefined, permission); this.appNameOnce() .switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.appClients.version)) .subscribe(dto => { - this.updateClients(this.appClients.updateClient(client.change(isReader), dto.version)); + this.updateClients(this.appClients.updateClient(client.update(permission), dto.version)); }, error => { this.notifyError(error); }); diff --git a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts index 80ba05fc8..7963fe414 100644 --- a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts +++ b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts @@ -58,12 +58,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni public maxContributors = -1; public usersDataSource: UsersDataSource; - public usersPermissions = [ - 'Owner', - 'Developer', - 'Editor', - 'Reader' - ]; + public usersPermissions = [ 'Owner', 'Developer', 'Editor' ]; public get canAddContributor() { return this.addContributorForm.valid && (this.maxContributors <= -1 || this.appContributors.contributors.length < this.maxContributors); diff --git a/src/Squidex/app/shared/services/app-clients.service.spec.ts b/src/Squidex/app/shared/services/app-clients.service.spec.ts index 1bd92760f..bd0c6741f 100644 --- a/src/Squidex/app/shared/services/app-clients.service.spec.ts +++ b/src/Squidex/app/shared/services/app-clients.service.spec.ts @@ -21,9 +21,9 @@ import { } from './../'; describe('AppClientsDto', () => { - const client1 = new AppClientDto('1', '1', '1', false); - const client2 = new AppClientDto('2', '2', '1', false); - const client2_new = new AppClientDto('2', '2 New', '1 New', false); + const client1 = new AppClientDto('1', '1', '1', 'Editor'); + const client2 = new AppClientDto('2', '2', '1', 'Editor'); + const client2_new = new AppClientDto('2', '2 New', '1 New', 'Editor'); const version = new Version('1'); const newVersion = new Version('2'); @@ -54,17 +54,17 @@ describe('AppClientsDto', () => { describe('AppClientDto', () => { it('should update name property when renaming', () => { - const client_1 = new AppClientDto('1', 'old-name', 'secret', false); + const client_1 = new AppClientDto('1', 'old-name', 'secret', 'Editor'); const client_2 = client_1.rename('new-name'); expect(client_2.name).toBe('new-name'); }); it('should update isReader property when changing', () => { - const client_1 = new AppClientDto('1', 'old-name', 'secret', false); - const client_2 = client_1.change(true); + const client_1 = new AppClientDto('1', 'old-name', 'secret', 'Editor'); + const client_2 = client_1.update('Developer'); - expect(client_2.isReader).toBeTruthy(); + expect(client_2.permission).toEqual('Developer'); }); }); @@ -107,13 +107,13 @@ describe('AppClientsService', () => { id: 'client1', name: 'Client 1', secret: 'secret1', - isReader: true + permission: 'Editor' }, { id: 'client2', name: 'Client 2', secret: 'secret2', - isReader: true + permission: 'Developer' } ], { headers: { @@ -123,8 +123,8 @@ describe('AppClientsService', () => { expect(clients).toEqual( new AppClientsDto([ - new AppClientDto('client1', 'Client 1', 'secret1', true), - new AppClientDto('client2', 'Client 2', 'secret2', true) + new AppClientDto('client1', 'Client 1', 'secret1', 'Editor'), + new AppClientDto('client2', 'Client 2', 'secret2', 'Developer') ], new Version('2'))); })); @@ -144,10 +144,10 @@ describe('AppClientsService', () => { expect(req.request.method).toEqual('POST'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({ id: 'client1', name: 'Client 1', secret: 'secret1', isReader: true }); + req.flush({ id: 'client1', name: 'Client 1', secret: 'secret1', permission: 'Developer' }); expect(client).toEqual( - new AppClientDto('client1', 'Client 1', 'secret1', true)); + new AppClientDto('client1', 'Client 1', 'secret1', 'Developer')); })); it('should make put request to rename client', @@ -183,7 +183,7 @@ describe('AppClientsService', () => { let accessTokenDto: AccessTokenDto | null = null; - appClientsService.createToken('my-app', new AppClientDto('myClientId', 'myClient', 'mySecret', false)).subscribe(result => { + appClientsService.createToken('my-app', new AppClientDto('myClientId', 'myClient', 'mySecret', 'Editor')).subscribe(result => { accessTokenDto = result; }); diff --git a/src/Squidex/app/shared/services/app-clients.service.ts b/src/Squidex/app/shared/services/app-clients.service.ts index 5f47531c6..12d35fdeb 100644 --- a/src/Squidex/app/shared/services/app-clients.service.ts +++ b/src/Squidex/app/shared/services/app-clients.service.ts @@ -44,16 +44,16 @@ export class AppClientDto { public readonly id: string, public readonly name: string, public readonly secret: string, - public readonly isReader: boolean + public readonly permission: string ) { } public rename(name: string): AppClientDto { - return new AppClientDto(this.id, name, this.secret, this.isReader); + return new AppClientDto(this.id, name, this.secret, this.permission); } - public change(isReader: boolean): AppClientDto { - return new AppClientDto(this.id, this.name, this.secret, isReader); + public update(permission: string): AppClientDto { + return new AppClientDto(this.id, this.name, this.secret, permission); } } @@ -67,7 +67,7 @@ export class CreateAppClientDto { export class UpdateAppClientDto { constructor( public readonly name?: string, - public readonly isReader?: boolean + public readonly permission?: string ) { } } @@ -103,7 +103,7 @@ export class AppClientsService { item.id, item.name || body.id, item.secret, - item.isReader); + item.permission); }); return new AppClientsDto(clients, response.version); @@ -122,7 +122,7 @@ export class AppClientsService { body.id, body.name || body.id, body.secret, - body.isReader); + body.permission); return new Versioned(response.version, client); }) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Apps/RoleExtensionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Apps/RoleExtensionTests.cs new file mode 100644 index 000000000..743d541a6 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Apps/RoleExtensionTests.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// RoleExtensionTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Apps +{ + public class RoleExtensionTests + { + [Fact] + public void Should_convert_from_client_permission_to_app_permission() + { + Assert.Equal(AppPermission.Developer, AppClientPermission.Developer.ToAppPermission()); + Assert.Equal(AppPermission.Editor, AppClientPermission.Editor.ToAppPermission()); + Assert.Equal(AppPermission.Reader, AppClientPermission.Reader.ToAppPermission()); + } + + [Fact] + public void Should_throw_when_converting_from_invalid_client_permission() + { + Assert.Throws(() => ((AppClientPermission)10).ToAppPermission()); + } + + [Fact] + public void Should_convert_from_contributor_permission_to_app_permission() + { + Assert.Equal(AppPermission.Developer, AppContributorPermission.Developer.ToAppPermission()); + Assert.Equal(AppPermission.Editor, AppContributorPermission.Editor.ToAppPermission()); + Assert.Equal(AppPermission.Owner, AppContributorPermission.Owner.ToAppPermission()); + } + + [Fact] + public void Should_throw_when_converting_from_invalid_contributor_permission() + { + Assert.Throws(() => ((AppContributorPermission)10).ToAppPermission()); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Schemas/SchemaTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Schemas/SchemaTests.cs index 554bdd0ad..683d0aef3 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Schemas/SchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Schemas/SchemaTests.cs @@ -342,7 +342,7 @@ namespace Squidex.Domain.Apps.Core.Schemas { var languagesConfig = LanguagesConfig.Create(Language.DE, Language.EN); - var jsonSchema = BuildMixedSchema().BuildJsonSchema(languagesConfig.ToResolver(), (n, s) => new JsonSchema4 { SchemaReference = s }); + var jsonSchema = BuildMixedSchema().BuildJsonSchema(languagesConfig.ToResolver(), (n, s) => new JsonSchema4 { Reference = s }); Assert.NotNull(jsonSchema); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs index 7ee633ac5..488040b06 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Write.Apps sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppCreated { Name = AppName }), - CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Permission = PermissionLevel.Owner }), + CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Permission = AppContributorPermission.Owner }), CreateEvent(new AppLanguageAdded { Language = Language.EN }) ); } @@ -148,7 +148,7 @@ namespace Squidex.Domain.Apps.Write.Apps Assert.Throws(() => { - sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = User.Identifier, Permission = PermissionLevel.Editor })); + sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = User.Identifier, Permission = AppContributorPermission.Editor })); }); } @@ -157,11 +157,11 @@ namespace Squidex.Domain.Apps.Write.Apps { CreateApp(); - sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor })); + sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor })); Assert.Throws(() => { - sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor })); + sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor })); }); } @@ -170,11 +170,11 @@ namespace Squidex.Domain.Apps.Write.Apps { CreateApp(); - sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor })); + sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor })); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Permission = PermissionLevel.Editor }) + CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Permission = AppContributorPermission.Editor }) ); } @@ -223,7 +223,7 @@ namespace Squidex.Domain.Apps.Write.Apps { CreateApp(); - sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor })); + sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor })); sut.RemoveContributor(CreateCommand(new RemoveContributor { ContributorId = contributorId })); sut.GetUncomittedEvents().Skip(1) @@ -358,6 +358,11 @@ namespace Squidex.Domain.Apps.Write.Apps { sut.UpdateClient(CreateCommand(new UpdateClient { Id = string.Empty })); }); + + Assert.Throws(() => + { + sut.UpdateClient(CreateCommand(new UpdateClient { Permission = (AppClientPermission)int.MaxValue })); + }); } [Fact] @@ -379,7 +384,7 @@ namespace Squidex.Domain.Apps.Write.Apps Assert.Throws(() => { - sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, IsReader = false })); + sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Permission = AppClientPermission.Editor })); }); } @@ -403,12 +408,12 @@ namespace Squidex.Domain.Apps.Write.Apps CreateApp(); CreateClient(); - sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName, IsReader = true })); + sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName, Permission = AppClientPermission.Developer })); sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppClientRenamed { Id = clientId, Name = clientNewName }), - CreateEvent(new AppClientChanged { Id = clientId, IsReader = true }) + CreateEvent(new AppClientUpdated { Id = clientId, Permission = AppClientPermission.Developer }) ); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs new file mode 100644 index 000000000..acb390b7e --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs @@ -0,0 +1,51 @@ +// ========================================================================== +// AppEventTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Apps; +using Squidex.Domain.Apps.Events.Apps.Old; +using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Infrastructure; +using Xunit; + +#pragma warning disable CS0612 // Type or member is obsolete + +namespace Squidex.Domain.Apps.Write.Apps +{ + public class AppEventTests + { + private readonly RefToken actor = new RefToken("User", Guid.NewGuid().ToString()); + private readonly NamedId appId = new NamedId(Guid.NewGuid(), "my-app"); + + [Fact] + public void Should_migrate_client_changed_as_reader_to_client_updated() + { + var source = CreateEvent(new AppClientChanged { IsReader = true }); + + source.Migrate().ShouldBeSameEvent(CreateEvent(new AppClientUpdated { Permission = AppClientPermission.Reader })); + } + + [Fact] + public void Should_migrate_client_changed_as_writer_to_client_updated() + { + var source = CreateEvent(new AppClientChanged { IsReader = false }); + + source.Migrate().ShouldBeSameEvent(CreateEvent(new AppClientUpdated { Permission = AppClientPermission.Editor })); + } + + private T CreateEvent(T contentEvent) where T : AppEvent + { + contentEvent.Actor = actor; + contentEvent.AppId = appId; + + return contentEvent; + } + } +}