Browse Source

1) Email claim for API

2) Update content form after edit.
pull/104/head
Sebastian Stehle 9 years ago
parent
commit
dc7535b149
  1. 29
      src/Squidex.Domain.Apps.Core/Scripting/ScriptUser.cs
  2. 2
      src/Squidex.Domain.Apps.Write/Contents/Commands/ContentCommand.cs
  3. 2
      src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs
  4. 36
      src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs
  5. 6
      src/Squidex/Config/Identity/IdentityServices.cs
  6. 1
      src/Squidex/Config/Identity/LazyClientStore.cs
  7. 16
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  8. 1
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  9. 2
      src/Squidex/app/shared/services/auth.service.ts
  10. 80
      tests/Squidex.Domain.Apps.Core.Tests/Scripting/ScriptUserTests.cs
  11. 24
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandHandlerTests.cs

29
src/Squidex.Domain.Apps.Core/Scripting/ScriptUser.cs

@ -7,23 +7,44 @@
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class ScriptUser
{
public bool IsClient { get; set; }
public string Id { get; set; }
public string Email { get; set; }
public bool IsClient { get; set; }
public Dictionary<string, string[]> Scopes { get; } = new Dictionary<string, string[]>();
public Dictionary<string, string[]> Claims { get; set; }
public static ScriptUser Create(ClaimsPrincipal principal)
{
return new ScriptUser();
Guard.NotNull(principal, nameof(principal));
var subjectId = principal.OpenIdSubject();
var user = new ScriptUser { IsClient = string.IsNullOrWhiteSpace(subjectId), Email = principal.OpenIdEmail() };
if (!user.IsClient)
{
user.Id = subjectId;
}
else
{
user.Id = principal.OpenIdClientId();
}
user.Claims = principal.Claims.GroupBy(x => x.Type).ToDictionary(x => x.Key, x => x.Select(y => y.Value).ToArray());
return user;
}
}
}

2
src/Squidex.Domain.Apps.Write/Contents/Commands/ContentCommand.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Write.Contents.Commands
{
public abstract class ContentCommand : SchemaCommand, IAggregateCommand
{
public ClaimsPrincipal Principal { get; set; }
public ClaimsPrincipal User { get; set; }
public Guid ContentId { get; set; }

2
src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs

@ -196,7 +196,7 @@ namespace Squidex.Domain.Apps.Write.Contents
private static ScriptContext CreateScriptContext(ContentDomainObject content, ContentCommand command, NamedContentData data = null)
{
return new ScriptContext { ContentId = content.Id, Data = data, OldData = content.Data, User = ScriptUser.Create(command.Principal) };
return new ScriptContext { ContentId = content.Id, Data = data, OldData = content.Data, User = ScriptUser.Create(command.User) };
}
private async Task<(ISchemaEntity SchemaEntity, IAppEntity AppEntity)> ResolveSchemaAndAppAsync(SchemaCommand command)

36
src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs

@ -0,0 +1,36 @@
// ==========================================================================
// UserClaimsPrincipalFactoryWithEmail.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Squidex.Infrastructure.Security;
using Squidex.Shared.Users;
namespace Squidex.Domain.Users
{
public sealed class UserClaimsPrincipalFactoryWithEmail : UserClaimsPrincipalFactory<IUser, IRole>
{
public UserClaimsPrincipalFactoryWithEmail(UserManager<IUser> userManager, RoleManager<IRole> roleManager, IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{
}
public override async Task<ClaimsPrincipal> CreateAsync(IUser user)
{
var principal = await base.CreateAsync(user);
principal.Identities.First().AddClaim(new Claim(OpenIdClaims.Email, await UserManager.GetEmailAsync(user)));
return principal;
}
}
}

6
src/Squidex/Config/Identity/IdentityServices.cs

@ -15,6 +15,7 @@ using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Users;
@ -91,6 +92,8 @@ namespace Squidex.Config.Identity
GetApiResources());
services.AddSingleton(
GetIdentityResources());
services.AddSingleton<IUserClaimsPrincipalFactory<IUser>,
UserClaimsPrincipalFactoryWithEmail>();
services.AddSingleton<IClientStore,
LazyClientStore>();
services.AddSingleton<IResourceStore,
@ -121,6 +124,7 @@ namespace Squidex.Config.Identity
{
UserClaims = new List<string>
{
JwtClaimTypes.Email,
JwtClaimTypes.Role
}
};
@ -130,7 +134,7 @@ namespace Squidex.Config.Identity
{
yield return new IdentityResources.OpenId();
yield return new IdentityResources.Profile();
yield return new IdentityResources.Profile();
yield return new IdentityResources.Email();
yield return new IdentityResource(Constants.RoleScope,
new[]
{

1
src/Squidex/Config/Identity/LazyClientStore.cs

@ -115,6 +115,7 @@ namespace Squidex.Config.Identity
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
Constants.ApiScope,
Constants.ProfileScope,
Constants.RoleScope

16
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -123,7 +123,7 @@ namespace Squidex.Controllers.ContentApi
if (hasScript && !isFrontendClient)
{
data = scriptEngine.ExecuteAndTransform(new ScriptContext { Data = data, ContentId = item.Id, User = scriptUser }, scriptText, "transform item");
data = scriptEngine.Transform(new ScriptContext { Data = data, ContentId = item.Id, User = scriptUser }, scriptText);
}
itemModel.Data = data;
@ -173,7 +173,7 @@ namespace Squidex.Controllers.ContentApi
if (hasScript)
{
data = scriptEngine.ExecuteAndTransform(new ScriptContext { Data = data, ContentId = entity.Id, User = scriptUser }, scriptText, "transform item");
data = scriptEngine.Transform(new ScriptContext { Data = data, ContentId = entity.Id, User = scriptUser }, scriptText);
}
}
@ -191,7 +191,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PostContent([FromBody] NamedContentData request, [FromQuery] bool publish = false)
{
var command = new CreateContent { ContentId = Guid.NewGuid(), Principal = User, Data = request.ToCleaned(), Publish = publish };
var command = new CreateContent { ContentId = Guid.NewGuid(), User = User, Data = request.ToCleaned(), Publish = publish };
var context = await CommandBus.PublishAsync(command);
@ -207,7 +207,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PutContent(Guid id, [FromBody] NamedContentData request)
{
var command = new UpdateContent { ContentId = id, Principal = User, Data = request.ToCleaned() };
var command = new UpdateContent { ContentId = id, User = User, Data = request.ToCleaned() };
var context = await CommandBus.PublishAsync(command);
@ -223,7 +223,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PatchContent(Guid id, [FromBody] NamedContentData request)
{
var command = new PatchContent { ContentId = id, Principal = User, Data = request.ToCleaned() };
var command = new PatchContent { ContentId = id, User = User, Data = request.ToCleaned() };
var context = await CommandBus.PublishAsync(command);
@ -239,7 +239,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PublishContent(Guid id)
{
var command = new PublishContent { ContentId = id, Principal = User };
var command = new PublishContent { ContentId = id, User = User };
await CommandBus.PublishAsync(command);
@ -252,7 +252,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> UnpublishContent(Guid id)
{
var command = new UnpublishContent { ContentId = id, Principal = User };
var command = new UnpublishContent { ContentId = id, User = User };
await CommandBus.PublishAsync(command);
@ -265,7 +265,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PutContent(Guid id)
{
var command = new DeleteContent { ContentId = id, Principal = User };
var command = new DeleteContent { ContentId = id, User = User };
await CommandBus.PublishAsync(command);

1
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -135,6 +135,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.emitContentUpdated(this.content);
this.notifyInfo('Content saved successfully.');
this.enableContentForm();
this.populateContentForm();
}, error => {
this.notifyError(error);
this.enableContentForm();

2
src/Squidex/app/shared/services/auth.service.ts

@ -75,7 +75,7 @@ export class AuthService {
this.userManager = new UserManager({
client_id: 'squidex-frontend',
scope: 'squidex-api openid profile squidex-profile role',
scope: 'squidex-api openid profile email squidex-profile role',
response_type: 'id_token token',
redirect_uri: apiUrl.buildUrl('login;'),
post_logout_redirect_uri: apiUrl.buildUrl('logout'),

80
tests/Squidex.Domain.Apps.Core.Tests/Scripting/ScriptUserTests.cs

@ -0,0 +1,80 @@
// ==========================================================================
// ScriptUserTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Security.Claims;
using FluentAssertions;
using Squidex.Infrastructure.Security;
using Xunit;
namespace Squidex.Domain.Apps.Core.Scripting
{
public class ScriptUserTests
{
[Fact]
public void Should_create_script_user_from_user_principal()
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(OpenIdClaims.Subject, "1"));
identity.AddClaim(new Claim(OpenIdClaims.Email, "hello@squidex.io"));
identity.AddClaim(new Claim("claim1", "1a"));
identity.AddClaim(new Claim("claim1", "1b"));
identity.AddClaim(new Claim("claim2", "2a"));
identity.AddClaim(new Claim("claim2", "2b"));
var principal = new ClaimsPrincipal(new[] { identity });
var scriptUser = ScriptUser.Create(principal);
scriptUser.ShouldBeEquivalentTo(
new ScriptUser
{
Email = "hello@squidex.io",
Id = "1",
IsClient = false,
Claims = new Dictionary<string, string[]>
{
{ "sub", new [] { "1" } },
{ "claim1", new[] { "1a", "1b" } },
{ "claim2", new[] { "2a", "2b" } },
{ "email", new [] { "hello@squidex.io" } }
}
});
}
[Fact]
public void Should_create_script_user_from_client_principal()
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(OpenIdClaims.ClientId, "1"));
identity.AddClaim(new Claim("claim1", "1a"));
identity.AddClaim(new Claim("claim1", "1b"));
identity.AddClaim(new Claim("claim2", "2a"));
identity.AddClaim(new Claim("claim2", "2b"));
var principal = new ClaimsPrincipal(new[] { identity });
var scriptUser = ScriptUser.Create(principal);
scriptUser.ShouldBeEquivalentTo(
new ScriptUser
{
Id = "1",
IsClient = true,
Claims = new Dictionary<string, string[]>
{
{ "client_id", new [] { "1" } } ,
{ "claim1", new[] { "1a", "1b" } },
{ "claim2", new[] { "2a", "2b" } }
}
});
}
}
}

24
tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandHandlerTests.cs

@ -7,6 +7,7 @@
// ==========================================================================
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core;
@ -38,6 +39,7 @@ namespace Squidex.Domain.Apps.Write.Contents
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppEntity appEntity = A.Fake<IAppEntity>();
private readonly ClaimsPrincipal user = new ClaimsPrincipal();
private readonly NamedContentData invalidData = new NamedContentData().AddField("my-field", new ContentFieldData().SetValue(null));
private readonly NamedContentData data = new NamedContentData().AddField("my-field", new ContentFieldData().SetValue(1));
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.DE);
@ -67,7 +69,7 @@ namespace Squidex.Domain.Apps.Write.Contents
{
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored, A<string>.Ignored)).Returns(invalidData);
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = invalidData });
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = invalidData, User = user });
await TestCreate(content, async _ =>
{
@ -81,7 +83,7 @@ namespace Squidex.Domain.Apps.Write.Contents
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored, A<string>.Ignored)).Returns(data);
A.CallTo(() => schemaEntity.ScriptCreate).Returns("<create-script>");
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = data });
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = data, User = user });
await TestCreate(content, async _ =>
{
@ -100,7 +102,7 @@ namespace Squidex.Domain.Apps.Write.Contents
CreateContent();
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = invalidData });
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = invalidData, User = user });
await TestUpdate(content, async _ =>
{
@ -116,7 +118,7 @@ namespace Squidex.Domain.Apps.Write.Contents
CreateContent();
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = data });
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = data, User = user });
await TestUpdate(content, async _ =>
{
@ -135,7 +137,7 @@ namespace Squidex.Domain.Apps.Write.Contents
CreateContent();
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = invalidData });
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = invalidData, User = user });
await TestUpdate(content, async _ =>
{
@ -149,13 +151,13 @@ namespace Squidex.Domain.Apps.Write.Contents
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored, A<string>.Ignored)).Returns(data);
A.CallTo(() => schemaEntity.ScriptUpdate).Returns("<update-script>");
var path = new NamedContentData().AddField("my-field", new ContentFieldData().SetValue(3));
var patch = new NamedContentData().AddField("my-field", new ContentFieldData().SetValue(3));
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored, A<string>.Ignored)).Returns(path);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored, A<string>.Ignored)).Returns(patch);
CreateContent();
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = path });
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = patch, User = user });
await TestUpdate(content, async _ =>
{
@ -174,7 +176,7 @@ namespace Squidex.Domain.Apps.Write.Contents
CreateContent();
var context = CreateContextForCommand(new PublishContent { ContentId = contentId });
var context = CreateContextForCommand(new PublishContent { ContentId = contentId, User = user });
await TestUpdate(content, async _ =>
{
@ -191,7 +193,7 @@ namespace Squidex.Domain.Apps.Write.Contents
CreateContent();
var context = CreateContextForCommand(new UnpublishContent { ContentId = contentId });
var context = CreateContextForCommand(new UnpublishContent { ContentId = contentId, User = user });
await TestUpdate(content, async _ =>
{
@ -208,7 +210,7 @@ namespace Squidex.Domain.Apps.Write.Contents
CreateContent();
var command = CreateContextForCommand(new DeleteContent { ContentId = contentId });
var command = CreateContextForCommand(new DeleteContent { ContentId = contentId, User = user });
await TestUpdate(content, async _ =>
{

Loading…
Cancel
Save