Browse Source

App to change client.

pull/95/head
Sebastian Stehle 9 years ago
parent
commit
76e7b8b1ca
  1. 20
      src/Squidex.Domain.Apps.Events/Apps/AppClientChanged.cs
  2. 4
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityClient.cs
  3. 8
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  4. 2
      src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs
  5. 41
      src/Squidex.Domain.Apps.Write/Apps/AppClient.cs
  6. 24
      src/Squidex.Domain.Apps.Write/Apps/AppClients.cs
  7. 4
      src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs
  8. 41
      src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs
  9. 12
      src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs
  10. 2
      src/Squidex/Controllers/Api/Apps/AppClientsController.cs
  11. 6
      src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs
  12. 18
      src/Squidex/Pipeline/AppApiFilter.cs
  13. 16
      src/Squidex/Pipeline/Extensions.cs
  14. 2
      tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandHandlerTests.cs
  15. 39
      tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs

20
src/Squidex.Domain.Apps.Events/Apps/AppClientChanged.cs

@ -0,0 +1,20 @@
// ==========================================================================
// AppClientChanged.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Events.Apps
{
[TypeName("AppClientChangedEvent")]
public sealed class AppClientChanged : AppEvent
{
public string Id { get; set; }
public bool IsReader { get; set; }
}
}

4
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntityClient.cs

@ -25,6 +25,10 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps
[BsonElement] [BsonElement]
public string Name { get; set; } public string Name { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public bool IsReader { get; set; }
string IAppClientEntity.Name string IAppClientEntity.Name
{ {
get { return !string.IsNullOrWhiteSpace(Name) ? Name : Id; } get { return !string.IsNullOrWhiteSpace(Name) ? Name : Id; }

8
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -73,6 +73,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps
}); });
} }
protected Task On(AppClientChanged @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, a =>
{
a.Clients[@event.Id].IsReader = @event.IsReader;
});
}
protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers) protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers)
{ {
return Collection.UpdateAsync(@event, headers, a => return Collection.UpdateAsync(@event, headers, a =>

2
src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs

@ -15,5 +15,7 @@ namespace Squidex.Domain.Apps.Read.Apps
string Name { get; } string Name { get; }
string Secret { get; } string Secret { get; }
bool IsReader { get; }
} }
} }

41
src/Squidex.Domain.Apps.Write/Apps/AppClient.cs

@ -6,44 +6,51 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.Domain.Apps.Write.Apps namespace Squidex.Domain.Apps.Write.Apps
{ {
public sealed class AppClient public sealed class AppClient
{ {
private readonly string name; private readonly string name;
private readonly string id;
private readonly string secret; private readonly string secret;
private readonly bool isReader;
public string Id public AppClient(string secret, string name, bool isReader)
{ {
get { return id; } Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNullOrEmpty(secret, nameof(secret));
this.name = name;
this.secret = secret;
this.isReader = isReader;
} }
public string Name public AppClient Change(bool newIsReader, Func<string> message)
{
if (isReader == newIsReader)
{ {
get { return name ?? Id; } var error = new ValidationError("Client has already the reader state.", "IsReader");
throw new ValidationException(message(), error);
} }
public string Secret return new AppClient(secret, name, newIsReader);
{
get { return secret; }
} }
public AppClient(string id, string secret, string name = null) public AppClient Rename(string newName, Func<string> message)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); if (string.Equals(name, newName))
Guard.NotNullOrEmpty(secret, nameof(secret)); {
var error = new ValidationError("Client already has the name", "Id");
this.id = id; throw new ValidationException(message(), error);
this.name = name;
this.secret = secret;
} }
public AppClient Rename(string newName) return new AppClient(secret, newName, isReader);
{
return new AppClient(Id, Secret, newName);
} }
} }
} }

24
src/Squidex.Domain.Apps.Write/Apps/AppClients.cs

@ -25,17 +25,23 @@ namespace Squidex.Domain.Apps.Write.Apps
public void Add(string id, string secret) public void Add(string id, string secret)
{ {
ThrowIfFound(id, () => "Cannot rename client"); ThrowIfFound(id, () => "Cannot add client");
clients[id] = new AppClient(id, secret); clients[id] = new AppClient(secret, id, false);
} }
public void Rename(string clientId, string name) public void Rename(string clientId, string name)
{ {
ThrowIfNotFound(clientId); ThrowIfNotFound(clientId);
ThrowIfSameName(clientId, name, () => "Cannot rename client");
clients[clientId] = clients[clientId].Rename(name); clients[clientId] = clients[clientId].Rename(name, () => "Cannot rename client");
}
public void Change(string clientId, bool isReader)
{
ThrowIfNotFound(clientId);
clients[clientId] = clients[clientId].Change(isReader, () => "Cannot change client");
} }
public void Revoke(string clientId) public void Revoke(string clientId)
@ -62,15 +68,5 @@ namespace Squidex.Domain.Apps.Write.Apps
throw new ValidationException(message(), error); throw new ValidationException(message(), error);
} }
} }
private void ThrowIfSameName(string clientId, string name, Func<string> message)
{
if (string.Equals(clients[clientId].Name, name))
{
var error = new ValidationError("Client already has the name", "Id");
throw new ValidationException(message(), error);
}
}
} }
} }

4
src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs

@ -123,9 +123,9 @@ namespace Squidex.Domain.Apps.Write.Apps
return handler.UpdateAsync<AppDomainObject>(context, a => a.RemoveContributor(command)); return handler.UpdateAsync<AppDomainObject>(context, a => a.RemoveContributor(command));
} }
protected Task On(RenameClient command, CommandContext context) protected Task On(UpdateClient command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => a.RenameClient(command)); return handler.UpdateAsync<AppDomainObject>(context, a => a.UpdateClient(command));
} }
protected Task On(RevokeClient command, CommandContext context) protected Task On(RevokeClient command, CommandContext context)

41
src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
@ -48,11 +47,6 @@ namespace Squidex.Domain.Apps.Write.Apps
get { return contributors.Count; } get { return contributors.Count; }
} }
public IReadOnlyDictionary<string, AppClient> Clients
{
get { return clients.Clients; }
}
public AppDomainObject(Guid id, int version) public AppDomainObject(Guid id, int version)
: base(id, version) : base(id, version)
{ {
@ -78,6 +72,11 @@ namespace Squidex.Domain.Apps.Write.Apps
clients.Add(@event.Id, @event.Secret); clients.Add(@event.Id, @event.Secret);
} }
protected void On(AppClientChanged @event)
{
clients.Change(@event.Id, @event.IsReader);
}
protected void On(AppClientRenamed @event) protected void On(AppClientRenamed @event)
{ {
clients.Rename(@event.Id, @event.Name); clients.Rename(@event.Id, @event.Name);
@ -131,6 +130,25 @@ namespace Squidex.Domain.Apps.Write.Apps
return this; return this;
} }
public AppDomainObject UpdateClient(UpdateClient command)
{
Guard.Valid(command, nameof(command), () => "Cannot rename client");
ThrowIfNotCreated();
if (!string.IsNullOrWhiteSpace(command.Name))
{
RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed()));
}
if (command.IsReader.HasValue)
{
RaiseEvent(SimpleMapper.Map(command, new AppClientChanged { IsReader = command.IsReader.Value }));
}
return this;
}
public AppDomainObject AssignContributor(AssignContributor command) public AppDomainObject AssignContributor(AssignContributor command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot assign contributor"); Guard.Valid(command, nameof(command), () => "Cannot assign contributor");
@ -164,17 +182,6 @@ namespace Squidex.Domain.Apps.Write.Apps
return this; return this;
} }
public AppDomainObject RenameClient(RenameClient command)
{
Guard.Valid(command, nameof(command), () => "Cannot rename client");
ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed()));
return this;
}
public AppDomainObject RevokeClient(RevokeClient command) public AppDomainObject RevokeClient(RevokeClient command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot revoke client"); Guard.Valid(command, nameof(command), () => "Cannot revoke client");

12
src/Squidex.Domain.Apps.Write/Apps/Commands/RenameClient.cs → src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs

@ -11,22 +11,24 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Write.Apps.Commands
{ {
public class RenameClient : AppAggregateCommand, IValidatable public class UpdateClient : AppAggregateCommand, IValidatable
{ {
public string Id { get; set; } public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool? IsReader { get; set; }
public void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> errors)
{ {
if (string.IsNullOrWhiteSpace(Name)) if (!Id.IsSlug())
{ {
errors.Add(new ValidationError("Name cannot be null or empty", nameof(Name))); errors.Add(new ValidationError("Client id must be a valid slug", nameof(Id)));
} }
if (!Id.IsSlug()) if (string.IsNullOrWhiteSpace(Name) && IsReader == null)
{ {
errors.Add(new ValidationError("Client id must be a valid slug", nameof(Id))); errors.Add(new ValidationError("Either name or IsReader must be defined.", nameof(Name), nameof(IsReader)));
} }
} }
} }

2
src/Squidex/Controllers/Api/Apps/AppClientsController.cs

@ -103,7 +103,7 @@ namespace Squidex.Controllers.Api.Apps
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PutClient(string app, string clientId, [FromBody] UpdateAppClientDto request) public async Task<IActionResult> PutClient(string app, string clientId, [FromBody] UpdateAppClientDto request)
{ {
await CommandBus.PublishAsync(SimpleMapper.Map(request, new RenameClient { Id = clientId })); await CommandBus.PublishAsync(SimpleMapper.Map(request, new UpdateClient { Id = clientId }));
return NoContent(); return NoContent();
} }

6
src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs

@ -15,8 +15,12 @@ namespace Squidex.Controllers.Api.Apps.Models
/// <summary> /// <summary>
/// The new display name of the client. /// The new display name of the client.
/// </summary> /// </summary>
[Required]
[StringLength(20)] [StringLength(20)]
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Determines if the client is a reader.
/// </summary>
public bool IsReader { get; set; }
} }
} }

18
src/Squidex/Pipeline/AppApiFilter.cs

@ -81,13 +81,19 @@ namespace Squidex.Pipeline
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppOwner)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppOwner));
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppDeveloper)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppDeveloper));
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor));
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader));
break; break;
case PermissionLevel.Developer: case PermissionLevel.Developer:
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppDeveloper)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppDeveloper));
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor));
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader));
break; break;
case PermissionLevel.Editor: case PermissionLevel.Editor:
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor)); defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppEditor));
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader));
break;
case PermissionLevel.Reader:
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, SquidexRoles.AppReader));
break; break;
} }
@ -97,20 +103,14 @@ namespace Squidex.Pipeline
private static PermissionLevel? FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user) private static PermissionLevel? FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user)
{ {
var clientId = user.FindFirst(OpenIdClaims.ClientId)?.Value; var client = app.Clients.FirstOrDefault(x => string.Equals(x.Id, user.GetClientId(), StringComparison.OrdinalIgnoreCase));
var clientIdParts = clientId?.Split(':');
if (clientIdParts?.Length != 2) if (client == null)
{ {
return null; return null;
} }
clientId = clientIdParts[1]; return client.IsReader ? PermissionLevel.Reader : PermissionLevel.Editor;
var contributor = app.Clients.FirstOrDefault(x => string.Equals(x.Id, clientId, StringComparison.OrdinalIgnoreCase));
return contributor != null ? PermissionLevel.Owner : PermissionLevel.Editor;
} }
private static PermissionLevel? FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user) private static PermissionLevel? FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user)

16
src/Squidex/Pipeline/Extensions.cs

@ -18,5 +18,21 @@ namespace Squidex.Pipeline
{ {
return principal.IsInClient(Constants.FrontendClient); return principal.IsInClient(Constants.FrontendClient);
} }
public static string GetClientId(this ClaimsPrincipal principal)
{
var clientId = principal.FindFirst(OpenIdClaims.ClientId)?.Value;
var clientIdParts = clientId?.Split(':');
if (clientIdParts?.Length != 2)
{
return null;
}
clientId = clientIdParts[1];
return clientId;
}
} }
} }

2
tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -193,7 +193,7 @@ namespace Squidex.Domain.Apps.Write.Apps
CreateApp() CreateApp()
.AttachClient(CreateCommand(new AttachClient { Id = clientName })); .AttachClient(CreateCommand(new AttachClient { Id = clientName }));
var context = CreateContextForCommand(new RenameClient { Id = clientName, Name = "New Name" }); var context = CreateContextForCommand(new UpdateClient { Id = clientName, Name = "New Name" });
await TestUpdate(app, async _ => await TestUpdate(app, async _ =>
{ {

39
tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs

@ -338,66 +338,79 @@ namespace Squidex.Domain.Apps.Write.Apps
} }
[Fact] [Fact]
public void RenameClient_should_throw_exception_if_not_created() public void UpdateClient_should_throw_exception_if_not_created()
{ {
Assert.Throws<DomainException>(() => Assert.Throws<DomainException>(() =>
{ {
sut.RenameClient(CreateCommand(new RenameClient { Id = "not-found", Name = clientNewName })); sut.UpdateClient(CreateCommand(new UpdateClient { Id = "not-found", Name = clientNewName }));
}); });
} }
[Fact] [Fact]
public void RenameClient_should_throw_exception_if_command_is_not_valid() public void UpdateClient_should_throw_exception_if_command_is_not_valid()
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => Assert.Throws<ValidationException>(() =>
{ {
sut.RenameClient(CreateCommand(new RenameClient())); sut.UpdateClient(CreateCommand(new UpdateClient()));
}); });
Assert.Throws<ValidationException>(() => Assert.Throws<ValidationException>(() =>
{ {
sut.RenameClient(CreateCommand(new RenameClient { Id = string.Empty })); sut.UpdateClient(CreateCommand(new UpdateClient { Id = string.Empty }));
}); });
} }
[Fact] [Fact]
public void RenameClient_should_throw_exception_if_client_not_found() public void UpdateClient_should_throw_exception_if_client_not_found()
{ {
CreateApp(); CreateApp();
Assert.Throws<DomainObjectNotFoundException>(() => Assert.Throws<DomainObjectNotFoundException>(() =>
{ {
sut.RenameClient(CreateCommand(new RenameClient { Id = "not-found", Name = clientNewName })); sut.UpdateClient(CreateCommand(new UpdateClient { Id = "not-found", Name = clientNewName }));
}); });
} }
[Fact] [Fact]
public void RenameClient_should_throw_exception_if_same_client_name() public void UpdateClient_should_throw_exception_if_client_has_same_reader_state()
{ {
CreateApp(); CreateApp();
CreateClient(); CreateClient();
sut.RenameClient(CreateCommand(new RenameClient { Id = clientId, Name = clientNewName })); Assert.Throws<ValidationException>(() =>
{
sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, IsReader = false }));
});
}
[Fact]
public void UpdateClient_should_throw_exception_if_same_client_name()
{
CreateApp();
CreateClient();
sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName }));
Assert.Throws<ValidationException>(() => Assert.Throws<ValidationException>(() =>
{ {
sut.RenameClient(CreateCommand(new RenameClient { Id = clientId, Name = clientNewName })); sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName }));
}); });
} }
[Fact] [Fact]
public void RenameClient_should_create_events() public void UpdateClient_should_create_events()
{ {
CreateApp(); CreateApp();
CreateClient(); CreateClient();
sut.RenameClient(CreateCommand(new RenameClient { Id = clientId, Name = clientNewName })); sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName, IsReader = true }));
sut.GetUncomittedEvents() sut.GetUncomittedEvents()
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppClientRenamed { Id = clientId, Name = clientNewName }) CreateEvent(new AppClientRenamed { Id = clientId, Name = clientNewName }),
CreateEvent(new AppClientChanged { Id = clientId, IsReader = true })
); );
} }

Loading…
Cancel
Save