Browse Source

Merge branch 'master' into scripting

# Conflicts:
#	src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj
pull/104/head
Sebastian Stehle 9 years ago
parent
commit
934f6e43d3
  1. 5
      global.json
  2. 5
      src/Squidex.Domain.Apps.Core/Scripting/IScriptEngine.cs
  3. 69
      src/Squidex.Domain.Apps.Core/Scripting/JurassicScriptEngine.cs
  4. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj
  5. 28
      src/Squidex.Domain.Users.MongoDb/MongoRole.cs
  6. 67
      src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs
  7. 199
      src/Squidex.Domain.Users.MongoDb/MongoUser.cs
  8. 34
      src/Squidex.Domain.Users.MongoDb/MongoUserClaim.cs
  9. 43
      src/Squidex.Domain.Users.MongoDb/MongoUserLogin.cs
  10. 279
      src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs
  11. 27
      src/Squidex.Domain.Users.MongoDb/MongoUserToken.cs
  12. 1
      src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  13. 16
      src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs
  14. 46
      src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs
  15. 3
      src/Squidex.Domain.Users/UserManagerExtensions.cs
  16. 8
      src/Squidex.Infrastructure/CollectionExtensions.cs
  17. 25
      src/Squidex.Infrastructure/DomainForbiddenException.cs
  18. 1
      src/Squidex/Config/Identity/MicrosoftIdentityUsage.cs
  19. 18
      src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs
  20. 9
      src/Squidex/Squidex.csproj
  21. 135
      tests/Squidex.Domain.Apps.Core.Tests/Scripting/JurassicScriptEngineTests.cs
  22. 4
      tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

5
global.json

@ -0,0 +1,5 @@
{
"sdk": {
"version": "2.0.0"
}
}

5
src/Squidex.Domain.Apps.Core/Scripting/IScriptEngine.cs

@ -6,15 +6,14 @@
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting
{
public interface IScriptEngine
{
Task ExecuteAsync(ScriptContext context, string operationName, string script);
void Execute(ScriptContext context, string script, string operationName);
Task<NamedContentData> ExecuteAndTransformAsync(ScriptContext context, string operationName, string script);
NamedContentData ExecuteAndTransform(ScriptContext context, string script, string operationName, bool failOnError = false);
}
}

69
src/Squidex.Domain.Apps.Core/Scripting/JurassicScriptEngine.cs

@ -7,15 +7,13 @@
// ==========================================================================
using System;
using System.Security;
using System.Threading.Tasks;
using Jurassic;
using Jurassic.Library;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
// ReSharper disable ConvertToLambdaExpression
namespace Squidex.Domain.Apps.Core.Scripting
@ -31,66 +29,75 @@ namespace Squidex.Domain.Apps.Core.Scripting
this.serializerSettings = serializerSettings;
}
public Task ExecuteAsync(ScriptContext context, string operationName, string script)
public void Execute(ScriptContext context, string script, string operationName)
{
Guard.NotNull(context, nameof(context));
if (!string.IsNullOrWhiteSpace(script))
{
return TaskHelper.False;
}
var engine = CreateScriptEngine(context, operationName);
engine.Execute(script);
var engine = CreateScriptEngine(context, operationName);
return TaskHelper.False;
Execute(script, operationName, engine, true);
}
}
public Task<NamedContentData> ExecuteAndTransformAsync(ScriptContext context, string operationName, string script)
public NamedContentData ExecuteAndTransform(ScriptContext context, string script, string operationName, bool failOnError = false)
{
Guard.NotNull(context, nameof(context));
var result = context.Data;
if (!string.IsNullOrWhiteSpace(script))
{
return Task.FromResult(context.Data);
}
var engine = CreateScriptEngine(context, operationName);
var result = context.Data;
engine.SetGlobalFunction("replace", new Action<object>(data =>
{
try
{
result = JsonConvert.DeserializeObject<NamedContentData>(JSONObject.Stringify(engine, data));
}
catch
{
result = new NamedContentData();
}
}));
Execute(script, operationName, engine, failOnError);
}
var engine = CreateScriptEngine(context, operationName);
return result;
}
engine.SetGlobalFunction("replace", new Action<ObjectInstance>(data =>
private static void Execute(string script, string operationName, ScriptEngine engine, bool failOnError = false)
{
try
{
try
{
result = JsonConvert.DeserializeObject<NamedContentData>(JSONObject.Stringify(engine, data));
}
catch
engine.Execute(script);
}
catch (JavaScriptException ex)
{
if (failOnError)
{
result = new NamedContentData();
throw new ValidationException($"Failed to {operationName} with javascript error.", new ValidationError(ex.Message));
}
}));
engine.Execute(script);
return Task.FromResult(result);
}
}
private ScriptEngine CreateScriptEngine(ScriptContext context, string operationName)
{
Guard.NotNullOrEmpty(operationName, nameof(operationName));
var engine = new ScriptEngine();
var engine = new ScriptEngine { ForceStrictMode = true };
engine.SetGlobalFunction("disallow", new Action<string>(message =>
{
throw new SecurityException(message);
throw new DomainForbiddenException(!string.IsNullOrWhiteSpace(message) ? message : "Not allowed");
}));
engine.SetGlobalFunction("reject", new Action<string>(message =>
{
throw new ValidationException($"Failed to '{operationName}", !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null);
throw new ValidationException($"Failed to {operationName}", !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null);
}));
var json = JsonConvert.SerializeObject(context, serializerSettings);

2
src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj

@ -14,7 +14,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Read\Squidex.Domain.Apps.Read.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />
<PackageReference Include="Microsoft.OData.Core" Version="7.2.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>

28
src/Squidex.Domain.Users.MongoDb/MongoRole.cs

@ -0,0 +1,28 @@
// ==========================================================================
// MongoRole.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoRole : IRole
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement]
public string Id { get; set; }
[BsonRequired]
[BsonElement]
public string Name { get; set; }
[BsonRequired]
[BsonElement]
public string NormalizedName { get; set; }
}
}

67
src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs

@ -9,82 +9,101 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.MongoDB;
using MongoDB.Driver;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoRoleStore : IRoleStore<IRole>, IRoleFactory
public sealed class MongoRoleStore : MongoRepositoryBase<MongoRole>, IRoleStore<IRole>, IRoleFactory
{
private readonly RoleStore<WrappedIdentityRole> innerStore;
public MongoRoleStore(IMongoDatabase database)
: base(database)
{
var rolesCollection = database.GetCollection<WrappedIdentityRole>("Identity_Roles");
}
IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection);
protected override string CollectionName()
{
return "Identity_Roles";
}
innerStore = new RoleStore<WrappedIdentityRole>(rolesCollection);
protected override Task SetupCollectionAsync(IMongoCollection<MongoRole> collection)
{
return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NormalizedName), new CreateIndexOptions { Unique = true });
}
protected override MongoCollectionSettings CollectionSettings()
{
return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public void Dispose()
{
innerStore.Dispose();
}
public IRole Create(string name)
{
return new WrappedIdentityRole { Name = name };
return new MongoRole { Name = name };
}
public async Task<IRole> FindByIdAsync(string roleId, CancellationToken cancellationToken)
{
return await innerStore.FindByIdAsync(roleId, cancellationToken);
return await Collection.Find(x => x.Id == roleId).FirstOrDefaultAsync(cancellationToken);
}
public async Task<IRole> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
{
return await innerStore.FindByNameAsync(normalizedRoleName, cancellationToken);
return await Collection.Find(x => x.NormalizedName == normalizedRoleName).FirstOrDefaultAsync(cancellationToken);
}
public Task<IdentityResult> CreateAsync(IRole role, CancellationToken cancellationToken)
public async Task<IdentityResult> CreateAsync(IRole role, CancellationToken cancellationToken)
{
return innerStore.CreateAsync((WrappedIdentityRole)role, cancellationToken);
await Collection.InsertOneAsync((MongoRole)role, null, cancellationToken);
return IdentityResult.Success;
}
public Task<IdentityResult> UpdateAsync(IRole role, CancellationToken cancellationToken)
public async Task<IdentityResult> UpdateAsync(IRole role, CancellationToken cancellationToken)
{
return innerStore.UpdateAsync((WrappedIdentityRole)role, cancellationToken);
await Collection.ReplaceOneAsync(x => x.Id == ((MongoRole)role).Id, (MongoRole)role, null, cancellationToken);
return IdentityResult.Success;
}
public Task<IdentityResult> DeleteAsync(IRole role, CancellationToken cancellationToken)
public async Task<IdentityResult> DeleteAsync(IRole role, CancellationToken cancellationToken)
{
return innerStore.DeleteAsync((WrappedIdentityRole)role, cancellationToken);
await Collection.DeleteOneAsync(x => x.Id == ((MongoRole)role).Id, null, cancellationToken);
return IdentityResult.Success;
}
public Task<string> GetRoleIdAsync(IRole role, CancellationToken cancellationToken)
{
return innerStore.GetRoleIdAsync((WrappedIdentityRole)role, cancellationToken);
return Task.FromResult(((MongoRole)role).Id);
}
public Task<string> GetRoleNameAsync(IRole role, CancellationToken cancellationToken)
{
return innerStore.GetRoleNameAsync((WrappedIdentityRole)role, cancellationToken);
return Task.FromResult(((MongoRole)role).Name);
}
public Task SetRoleNameAsync(IRole role, string roleName, CancellationToken cancellationToken)
public Task<string> GetNormalizedRoleNameAsync(IRole role, CancellationToken cancellationToken)
{
return innerStore.SetRoleNameAsync((WrappedIdentityRole)role, roleName, cancellationToken);
return Task.FromResult(((MongoRole)role).NormalizedName);
}
public Task<string> GetNormalizedRoleNameAsync(IRole role, CancellationToken cancellationToken)
public Task SetRoleNameAsync(IRole role, string roleName, CancellationToken cancellationToken)
{
return innerStore.GetNormalizedRoleNameAsync((WrappedIdentityRole)role, cancellationToken);
((MongoRole)role).Name = roleName;
return TaskHelper.Done;
}
public Task SetNormalizedRoleNameAsync(IRole role, string normalizedName, CancellationToken cancellationToken)
{
return innerStore.SetNormalizedRoleNameAsync((WrappedIdentityRole)role, normalizedName, cancellationToken);
((MongoRole)role).NormalizedName = normalizedName;
return TaskHelper.Done;
}
}
}

199
src/Squidex.Domain.Users.MongoDb/MongoUser.cs

@ -0,0 +1,199 @@
// ==========================================================================
// MongoUser.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoUser : IUser
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement]
public string Id { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string SecurityStamp { get; set; }
[BsonRequired]
[BsonElement]
public string UserName { get; set; }
[BsonRequired]
[BsonElement]
public string NormalizedUserName { get; set; }
[BsonRequired]
[BsonElement]
public string Email { get; set; }
[BsonRequired]
[BsonElement]
public string NormalizedEmail { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string PhoneNumber { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string PasswordHash { get; set; }
[BsonRequired]
[BsonElement]
public bool EmailConfirmed { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public bool PhoneNumberConfirmed { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public bool TwoFactorEnabled { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public bool LockoutEnabled { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public DateTime? LockoutEndDateUtc { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public int AccessFailedCount { get; set; }
[BsonRequired]
[BsonElement]
public List<string> Roles { get; set; } = new List<string>();
[BsonRequired]
[BsonElement]
public List<MongoUserClaim> Claims { get; set; } = new List<MongoUserClaim>();
[BsonRequired]
[BsonElement]
public List<MongoUserToken> Tokens { get; set; } = new List<MongoUserToken>();
[BsonRequired]
[BsonElement]
public List<MongoUserLogin> Logins { get; set; } = new List<MongoUserLogin>();
public bool IsLocked
{
get { return LockoutEndDateUtc != null && LockoutEndDateUtc.Value > DateTime.UtcNow; }
}
IReadOnlyList<Claim> IUser.Claims
{
get { return Claims.Select(x => new Claim(x.Type, x.Value)).ToList(); }
}
IReadOnlyList<ExternalLogin> IUser.Logins
{
get { return Logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey, x.ProviderDisplayName)).ToList(); }
}
public MongoUser()
{
Id = ObjectId.GenerateNewId().ToString();
}
public void UpdateEmail(string email)
{
Email = UserName = email;
}
public void AddRole(string role)
{
Roles.Add(role);
}
public void RemoveRole(string role)
{
Roles.Remove(role);
}
public void AddLogin(UserLoginInfo login)
{
Logins.Add(login);
}
public void RemoveLogin(string loginProvider, string providerKey)
{
Logins.RemoveAll(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
}
public void AddClaim(Claim claim)
{
Claims.Add(claim);
}
public void AddClaims(IEnumerable<Claim> claims)
{
claims.Foreach(AddClaim);
}
public void RemoveClaim(Claim claim)
{
Claims.RemoveAll(c => c.Type == claim.Type && c.Value == claim.Value);
}
public void RemoveClaims(IEnumerable<Claim> claims)
{
claims.Foreach(RemoveClaim);
}
public void SetClaim(string type, string value)
{
SetClaim(new Claim(type, value));
}
public void SetClaim(Claim claim)
{
ReplaceClaim(claim, claim);
}
public string GetToken(string loginProider, string name)
{
return Tokens.FirstOrDefault(t => t.LoginProvider == loginProider && t.Name == name)?.Value;
}
public void AddToken(string loginProvider, string name, string value)
{
Tokens.Add(new MongoUserToken { LoginProvider = loginProvider, Name = name, Value = value });
}
public void RemoveToken(string loginProvider, string name)
{
Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name);
}
public void ReplaceClaim(Claim existingClaim, Claim newClaim)
{
RemoveClaim(existingClaim);
AddClaim(newClaim);
}
public void SetToken(string loginProider, string name, string value)
{
RemoveToken(loginProider, name);
AddToken(loginProider, name, value);
}
}
}

34
src/Squidex.Domain.Users.MongoDb/MongoUserClaim.cs

@ -0,0 +1,34 @@
// ==========================================================================
// MongoUserClaim.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Security.Claims;
using MongoDB.Bson.Serialization.Attributes;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoUserClaim
{
[BsonRequired]
[BsonElement]
public string Type { get; set; }
[BsonRequired]
[BsonElement]
public string Value { get; set; }
public static implicit operator MongoUserClaim(Claim claim)
{
return new MongoUserClaim { Type = claim.Type, Value = claim.Value };
}
public static implicit operator Claim(MongoUserClaim userClaim)
{
return new Claim(userClaim.Type, userClaim.Value);
}
}
}

43
src/Squidex.Domain.Users.MongoDb/MongoUserLogin.cs

@ -0,0 +1,43 @@
// ==========================================================================
// MongoUserLogin.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Identity;
using MongoDB.Bson.Serialization.Attributes;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoUserLogin
{
[BsonRequired]
[BsonElement]
public string LoginProvider { get; set; }
[BsonRequired]
[BsonElement]
public string ProviderDisplayName { get; set; }
[BsonRequired]
[BsonElement]
public string ProviderKey { get; set; }
public static implicit operator MongoUserLogin(UserLoginInfo login)
{
return new MongoUserLogin
{
LoginProvider = login.LoginProvider,
ProviderKey = login.ProviderKey,
ProviderDisplayName = login.ProviderDisplayName
};
}
public static implicit operator UserLoginInfo(MongoUserLogin userLogin)
{
return new UserLoginInfo(userLogin.LoginProvider, userLogin.ProviderKey, userLogin.ProviderDisplayName);
}
}
}

279
src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs

@ -13,13 +13,15 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.MongoDB;
using MongoDB.Driver;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Tasks;
using Squidex.Shared.Users;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoUserStore :
MongoRepositoryBase<MongoUser>,
IUserPasswordStore<IUser>,
IUserRoleStore<IUser>,
IUserLoginStore<IUser>,
@ -34,296 +36,357 @@ namespace Squidex.Domain.Users.MongoDb
IUserResolver,
IQueryableUserStore<IUser>
{
private readonly UserStore<WrappedIdentityUser> innerStore;
public MongoUserStore(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
var usersCollection = database.GetCollection<WrappedIdentityUser>("Identity_Users");
return "Identity_Users";
}
IndexChecks.EnsureUniqueIndexOnNormalizedEmail(usersCollection);
IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection);
protected override Task SetupCollectionAsync(IMongoCollection<MongoUser> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NormalizedUserName), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NormalizedEmail), new CreateIndexOptions { Unique = true }));
}
innerStore = new UserStore<WrappedIdentityUser>(usersCollection);
protected override MongoCollectionSettings CollectionSettings()
{
return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public void Dispose()
{
innerStore.Dispose();
}
public IQueryable<IUser> Users
{
get { return innerStore.Users; }
get { return Collection.AsQueryable(); }
}
public IUser Create(string email)
{
return new WrappedIdentityUser { Email = email, UserName = email };
return new MongoUser { Email = email, UserName = email };
}
public async Task<IUser> FindByIdAsync(string userId)
public async Task<IUser> FindByIdAsync(string id)
{
return await innerStore.FindByIdAsync(userId, CancellationToken.None);
return await Collection.Find(x => x.Id == id).FirstOrDefaultAsync();
}
public async Task<IUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
return await innerStore.FindByIdAsync(userId, cancellationToken);
return await Collection.Find(x => x.Id == userId).FirstOrDefaultAsync(cancellationToken);
}
public async Task<IUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
{
return await innerStore.FindByEmailAsync(normalizedEmail, cancellationToken);
return await Collection.Find(x => x.NormalizedEmail == normalizedEmail).FirstOrDefaultAsync(cancellationToken);
}
public async Task<IUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return await innerStore.FindByNameAsync(normalizedUserName, cancellationToken);
return await Collection.Find(x => x.NormalizedEmail == normalizedUserName).FirstOrDefaultAsync(cancellationToken);
}
public async Task<IUser> FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
{
return await innerStore.FindByLoginAsync(loginProvider, providerKey, cancellationToken);
return await Collection.Find(x => x.Logins.Any(y => y.LoginProvider == loginProvider && y.ProviderKey == providerKey)).FirstOrDefaultAsync(cancellationToken);
}
public async Task<IList<IUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken)
{
return (await innerStore.GetUsersForClaimAsync(claim, cancellationToken)).OfType<IUser>().ToList();
return (await Collection.Find(x => x.Claims.Any(y => y.Type == claim.Type && y.Value == claim.Value)).ToListAsync(cancellationToken)).OfType<IUser>().ToList();
}
public async Task<IList<IUser>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
{
return (await innerStore.GetUsersInRoleAsync(roleName, cancellationToken)).OfType<IUser>().ToList();
return (await Collection.Find(x => x.Roles.Contains(roleName)).ToListAsync(cancellationToken)).OfType<IUser>().ToList();
}
public Task<IdentityResult> CreateAsync(IUser user, CancellationToken cancellationToken)
public async Task<IdentityResult> CreateAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.CreateAsync((WrappedIdentityUser)user, cancellationToken);
await Collection.InsertOneAsync((MongoUser)user, null, cancellationToken);
return IdentityResult.Success;
}
public Task<IdentityResult> UpdateAsync(IUser user, CancellationToken cancellationToken)
public async Task<IdentityResult> UpdateAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.UpdateAsync((WrappedIdentityUser)user, cancellationToken);
await Collection.ReplaceOneAsync(x => x.Id == user.Id, (MongoUser)user, null, cancellationToken);
return IdentityResult.Success;
}
public Task<IdentityResult> DeleteAsync(IUser user, CancellationToken cancellationToken)
public async Task<IdentityResult> DeleteAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.DeleteAsync((WrappedIdentityUser)user, cancellationToken);
await Collection.DeleteOneAsync(x => x.Id == user.Id, null, cancellationToken);
return IdentityResult.Success;
}
public Task<string> GetUserIdAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetUserIdAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).Id);
}
public Task<string> GetUserNameAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetUserNameAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).UserName);
}
public Task SetUserNameAsync(IUser user, string userName, CancellationToken cancellationToken)
public Task<string> GetNormalizedUserNameAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetUserNameAsync((WrappedIdentityUser)user, userName, cancellationToken);
return Task.FromResult(((MongoUser)user).NormalizedUserName);
}
public Task<string> GetNormalizedUserNameAsync(IUser user, CancellationToken cancellationToken)
public Task<string> GetPasswordHashAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetNormalizedUserNameAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).PasswordHash);
}
public Task SetNormalizedUserNameAsync(IUser user, string normalizedName, CancellationToken cancellationToken)
public Task<IList<string>> GetRolesAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetNormalizedUserNameAsync((WrappedIdentityUser)user, normalizedName, cancellationToken);
return Task.FromResult<IList<string>>(((MongoUser)user).Roles);
}
public Task<string> GetPasswordHashAsync(IUser user, CancellationToken cancellationToken)
public Task<bool> IsInRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
return innerStore.GetPasswordHashAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).Roles.Contains(roleName));
}
public Task SetPasswordHashAsync(IUser user, string passwordHash, CancellationToken cancellationToken)
public Task<IList<UserLoginInfo>> GetLoginsAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetPasswordHashAsync((WrappedIdentityUser)user, passwordHash, cancellationToken);
return Task.FromResult<IList<UserLoginInfo>>(((MongoUser)user).Logins.Select(x => (UserLoginInfo)x).ToList());
}
public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
public Task<string> GetSecurityStampAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.AddToRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
return Task.FromResult(((MongoUser)user).SecurityStamp);
}
public Task RemoveFromRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
public Task<string> GetEmailAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.RemoveFromRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
return Task.FromResult(((MongoUser)user).Email);
}
public Task<IList<string>> GetRolesAsync(IUser user, CancellationToken cancellationToken)
public Task<bool> GetEmailConfirmedAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetRolesAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).EmailConfirmed);
}
public Task<bool> IsInRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
public Task<string> GetNormalizedEmailAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.IsInRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
return Task.FromResult(((MongoUser)user).NormalizedEmail);
}
public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken)
public Task<IList<Claim>> GetClaimsAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.AddLoginAsync((WrappedIdentityUser)user, login, cancellationToken);
return Task.FromResult<IList<Claim>>(((MongoUser)user).Claims.Select(x => (Claim)x).ToList());
}
public Task RemoveLoginAsync(IUser user, string loginProvider, string providerKey, CancellationToken cancellationToken)
public Task<string> GetPhoneNumberAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.RemoveLoginAsync((WrappedIdentityUser)user, loginProvider, providerKey, cancellationToken);
return Task.FromResult(((MongoUser)user).PhoneNumber);
}
public Task<IList<UserLoginInfo>> GetLoginsAsync(IUser user, CancellationToken cancellationToken)
public Task<bool> GetPhoneNumberConfirmedAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetLoginsAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).PhoneNumberConfirmed);
}
public Task<string> GetSecurityStampAsync(IUser user, CancellationToken cancellationToken)
public Task<bool> GetTwoFactorEnabledAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetSecurityStampAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).TwoFactorEnabled);
}
public Task SetSecurityStampAsync(IUser user, string stamp, CancellationToken cancellationToken)
public Task<DateTimeOffset?> GetLockoutEndDateAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetSecurityStampAsync((WrappedIdentityUser)user, stamp, cancellationToken);
return Task.FromResult<DateTimeOffset?>(((MongoUser)user).LockoutEndDateUtc);
}
public Task<string> GetEmailAsync(IUser user, CancellationToken cancellationToken)
public Task<int> GetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.GetEmailAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).AccessFailedCount);
}
public Task SetEmailAsync(IUser user, string email, CancellationToken cancellationToken)
public Task<bool> GetLockoutEnabledAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetEmailAsync((WrappedIdentityUser)user, email, cancellationToken);
return Task.FromResult(((MongoUser)user).LockoutEnabled);
}
public Task<bool> GetEmailConfirmedAsync(IUser user, CancellationToken cancellationToken)
public Task<string> GetTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
return innerStore.GetEmailConfirmedAsync((WrappedIdentityUser)user, cancellationToken);
return Task.FromResult(((MongoUser)user).GetToken(loginProvider, name));
}
public Task SetEmailConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
public Task<bool> HasPasswordAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetEmailConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken);
return Task.FromResult(!string.IsNullOrWhiteSpace(((MongoUser)user).PasswordHash));
}
public Task<string> GetNormalizedEmailAsync(IUser user, CancellationToken cancellationToken)
public Task SetUserNameAsync(IUser user, string userName, CancellationToken cancellationToken)
{
return innerStore.GetNormalizedEmailAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).UserName = userName;
return TaskHelper.Done;
}
public Task SetNormalizedEmailAsync(IUser user, string normalizedEmail, CancellationToken cancellationToken)
public Task SetNormalizedUserNameAsync(IUser user, string normalizedName, CancellationToken cancellationToken)
{
return innerStore.SetNormalizedEmailAsync((WrappedIdentityUser)user, normalizedEmail, cancellationToken);
((MongoUser)user).NormalizedUserName = normalizedName;
return TaskHelper.Done;
}
public Task<IList<Claim>> GetClaimsAsync(IUser user, CancellationToken cancellationToken)
public Task SetPasswordHashAsync(IUser user, string passwordHash, CancellationToken cancellationToken)
{
return innerStore.GetClaimsAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).PasswordHash = passwordHash;
return TaskHelper.Done;
}
public Task AddClaimsAsync(IUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
return innerStore.AddClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken);
((MongoUser)user).AddRole(roleName);
return TaskHelper.Done;
}
public Task ReplaceClaimAsync(IUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken)
public Task RemoveFromRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
return innerStore.ReplaceClaimAsync((WrappedIdentityUser)user, claim, newClaim, cancellationToken);
((MongoUser)user).RemoveRole(roleName);
return TaskHelper.Done;
}
public Task RemoveClaimsAsync(IUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken)
{
return innerStore.RemoveClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken);
((MongoUser)user).AddLogin(login);
return TaskHelper.Done;
}
public Task<string> GetPhoneNumberAsync(IUser user, CancellationToken cancellationToken)
public Task RemoveLoginAsync(IUser user, string loginProvider, string providerKey, CancellationToken cancellationToken)
{
return innerStore.GetPhoneNumberAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).RemoveLogin(loginProvider, providerKey);
return TaskHelper.Done;
}
public Task SetPhoneNumberAsync(IUser user, string phoneNumber, CancellationToken cancellationToken)
public Task SetSecurityStampAsync(IUser user, string stamp, CancellationToken cancellationToken)
{
return innerStore.SetPhoneNumberAsync((WrappedIdentityUser)user, phoneNumber, cancellationToken);
((MongoUser)user).SecurityStamp = stamp;
return TaskHelper.Done;
}
public Task<bool> GetPhoneNumberConfirmedAsync(IUser user, CancellationToken cancellationToken)
public Task SetEmailAsync(IUser user, string email, CancellationToken cancellationToken)
{
return innerStore.GetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).Email = email;
return TaskHelper.Done;
}
public Task SetPhoneNumberConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
public Task SetEmailConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
{
return innerStore.SetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken);
((MongoUser)user).EmailConfirmed = confirmed;
return TaskHelper.Done;
}
public Task<bool> GetTwoFactorEnabledAsync(IUser user, CancellationToken cancellationToken)
public Task SetNormalizedEmailAsync(IUser user, string normalizedEmail, CancellationToken cancellationToken)
{
return innerStore.GetTwoFactorEnabledAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).NormalizedEmail = normalizedEmail;
return TaskHelper.Done;
}
public Task SetTwoFactorEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
public Task AddClaimsAsync(IUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
{
return innerStore.SetTwoFactorEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken);
((MongoUser)user).AddClaims(claims);
return TaskHelper.Done;
}
public Task<DateTimeOffset?> GetLockoutEndDateAsync(IUser user, CancellationToken cancellationToken)
public Task ReplaceClaimAsync(IUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken)
{
return innerStore.GetLockoutEndDateAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).ReplaceClaim(claim, newClaim);
return TaskHelper.Done;
}
public Task SetLockoutEndDateAsync(IUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken)
public Task RemoveClaimsAsync(IUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
{
return innerStore.SetLockoutEndDateAsync((WrappedIdentityUser)user, lockoutEnd, cancellationToken);
((MongoUser)user).RemoveClaims(claims);
return TaskHelper.Done;
}
public Task<int> GetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
public Task SetPhoneNumberAsync(IUser user, string phoneNumber, CancellationToken cancellationToken)
{
return innerStore.GetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).PhoneNumber = phoneNumber;
return TaskHelper.Done;
}
public Task<int> IncrementAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
public Task SetPhoneNumberConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
{
return innerStore.IncrementAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).PhoneNumberConfirmed = confirmed;
return TaskHelper.Done;
}
public Task ResetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
public Task SetTwoFactorEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
{
return innerStore.ResetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).TwoFactorEnabled = enabled;
return TaskHelper.Done;
}
public Task<bool> GetLockoutEnabledAsync(IUser user, CancellationToken cancellationToken)
public Task SetLockoutEndDateAsync(IUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken)
{
return innerStore.GetLockoutEnabledAsync((WrappedIdentityUser)user, cancellationToken);
((MongoUser)user).LockoutEndDateUtc = lockoutEnd?.UtcDateTime;
return TaskHelper.Done;
}
public Task SetLockoutEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
public Task<int> IncrementAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetLockoutEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken);
((MongoUser)user).AccessFailedCount++;
return Task.FromResult(((MongoUser)user).AccessFailedCount);
}
public Task SetTokenAsync(IUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
public Task ResetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.SetTokenAsync((WrappedIdentityUser)user, loginProvider, name, value, cancellationToken);
((MongoUser)user).AccessFailedCount = 0;
return TaskHelper.Done;
}
public Task RemoveTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
public Task SetLockoutEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
{
return innerStore.RemoveTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken);
((MongoUser)user).LockoutEnabled = enabled;
return TaskHelper.Done;
}
public Task<string> GetTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
public Task SetTokenAsync(IUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
{
return innerStore.GetTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken);
((MongoUser)user).SetToken(loginProvider, name, value);
return TaskHelper.Done;
}
public Task<bool> HasPasswordAsync(IUser user, CancellationToken cancellationToken)
public Task RemoveTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
return Task.FromResult(!string.IsNullOrWhiteSpace(((WrappedIdentityUser)user).PasswordHash));
((MongoUser)user).RemoveToken(loginProvider, name);
return TaskHelper.Done;
}
}
}

27
src/Squidex.Domain.Users.MongoDb/MongoUserToken.cs

@ -0,0 +1,27 @@
// ==========================================================================
// MongoUserToken.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoUserToken
{
[BsonRequired]
[BsonElement]
public string LoginProvider { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string Name { get; set; }
[BsonRequired]
[BsonElement]
public string Value { get; set; }
}
}

1
src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -15,7 +15,6 @@
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="1.5.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.MongoDB" Version="1.0.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.4.0" />

16
src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs

@ -1,16 +0,0 @@
// ==========================================================================
// WrappedIdentityRole.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Identity.MongoDB;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class WrappedIdentityRole : IdentityRole, IRole
{
}
}

46
src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs

@ -1,46 +0,0 @@
// ==========================================================================
// WrappedIdentityUser.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity.MongoDB;
using Squidex.Shared.Users;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class WrappedIdentityUser : IdentityUser, IUser
{
public bool IsLocked
{
get { return LockoutEndDateUtc != null && LockoutEndDateUtc.Value > DateTime.UtcNow; }
}
IReadOnlyList<Claim> IUser.Claims
{
get { return Claims.Select(x => new Claim(x.Type, x.Value)).ToList(); }
}
IReadOnlyList<ExternalLogin> IUser.Logins
{
get { return Logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey, x.ProviderDisplayName)).ToList(); }
}
public void UpdateEmail(string email)
{
Email = UserName = email;
}
public void SetClaim(string type, string value)
{
Claims.RemoveAll(x => string.Equals(x.Type, type, StringComparison.OrdinalIgnoreCase));
Claims.Add(new IdentityUserClaim { Type = type, Value = value });
}
}
}

3
src/Squidex.Domain.Users/UserManagerExtensions.cs

@ -87,7 +87,8 @@ namespace Squidex.Domain.Users
if (!string.IsNullOrWhiteSpace(email))
{
user.UpdateEmail(email);
await DoChecked(() => userManager.SetEmailAsync(user, email), "Cannot update email.");
await DoChecked(() => userManager.SetUserNameAsync(user, email), "Cannot update email.");
}
if (!string.IsNullOrWhiteSpace(displayName))

8
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -128,5 +128,13 @@ namespace Squidex.Infrastructure
return result;
}
public static void Foreach<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var item in collection)
{
action(item);
}
}
}
}

25
src/Squidex.Infrastructure/DomainForbiddenException.cs

@ -0,0 +1,25 @@
// ==========================================================================
// DomainForbiddenException.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Infrastructure
{
public class DomainForbiddenException : DomainException
{
public DomainForbiddenException(string message)
: base(message)
{
}
public DomainForbiddenException(string message, Exception inner)
: base(message, inner)
{
}
}
}

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

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

18
src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs

@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Squidex.Controllers.Api;
@ -24,19 +25,15 @@ namespace Squidex.Pipeline
private static void AddHandler<T>(Func<T, IActionResult> handler) where T : Exception
{
Handlers.Add(ex =>
{
var typed = ex as T;
return typed != null ? handler(typed) : null;
});
Handlers.Add(ex => ex is T typed ? handler(typed) : null);
}
static ApiExceptionFilterAttribute()
{
AddHandler<DomainObjectNotFoundException>(OnDomainObjectNotFoundException);
AddHandler<DomainObjectVersionException>(OnDomainObjectVersionException);
AddHandler<DomainException>(OnDomainException);
AddHandler<DomainForbiddenException>(OnDomainForbiddenException);
AddHandler<DomainObjectVersionException>(OnDomainObjectVersionException);
AddHandler<DomainObjectNotFoundException>(OnDomainObjectNotFoundException);
AddHandler<ValidationException>(OnValidationException);
}
@ -55,6 +52,11 @@ namespace Squidex.Pipeline
return ErrorResult(400, new ErrorDto { Message = ex.Message });
}
private static IActionResult OnDomainForbiddenException(DomainForbiddenException ex)
{
return ErrorResult(401, new ErrorDto { Message = ex.Message });
}
private static IActionResult OnValidationException(ValidationException ex)
{
return ErrorResult(400, new ErrorDto { Message = ex.Message, Details = ex.Errors.Select(e => e.Message).ToArray() });

9
src/Squidex/Squidex.csproj

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<AssemblyName>Squidex</AssemblyName>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>
<NoWarn>$(NoWarn);CS1591;1591;1573;1572</NoWarn>
<OutputType>Exe</OutputType>
<PackageId>Squidex</PackageId>
<PreserveCompilationContext>true</PreserveCompilationContext>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
@ -62,13 +62,16 @@
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration.Tools" Version="1.1.0-preview4-final" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.2.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="NJsonSchema" Version="9.5.0" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" />

135
tests/Squidex.Domain.Apps.Core.Tests/Scripting/JurassicScriptEngineTests.cs

@ -0,0 +1,135 @@
// ==========================================================================
// JurassicScriptEngineTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Core.Scripting
{
public class JurassicScriptEngineTests
{
private readonly JurassicScriptEngine scriptEngine =
new JurassicScriptEngine(
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
[Fact]
public void Should_throw_validation_exception_when_calling_reject()
{
Assert.Throws<ValidationException>(() => scriptEngine.Execute(new ScriptContext(), "reject()", "update"));
Assert.Throws<ValidationException>(() => scriptEngine.Execute(new ScriptContext(), "reject('Not valid')", "update"));
}
[Fact]
public void Should_throw_security_exception_when_calling_reject()
{
Assert.Throws<DomainForbiddenException>(() => scriptEngine.Execute(new ScriptContext(), "disallow()", "Update"));
Assert.Throws<DomainForbiddenException>(() => scriptEngine.Execute(new ScriptContext(), "disallow('Not allowed')", "update"));
}
[Fact]
public void Should_catch_script_syntax_errors()
{
Assert.Throws<ValidationException>(() => scriptEngine.Execute(new ScriptContext(), "x => x", "update"));
}
[Fact]
public void Should_catch_script_runtime_errors()
{
Assert.Throws<ValidationException>(() => scriptEngine.Execute(new ScriptContext(), "throw 'Error';", "update"));
}
[Fact]
public void Should_catch_script_runtime_errors_on_transform()
{
Assert.Throws<ValidationException>(() => scriptEngine.ExecuteAndTransform(new ScriptContext(), "throw 'Error';", "update", true));
}
[Fact]
public void Should_return_original_content_when_script_failed()
{
var content = new NamedContentData();
var context = new ScriptContext { Data = content };
var result = scriptEngine.ExecuteAndTransform(context, "x => x", "update");
Assert.Same(content, result);
}
[Fact]
public void Should_return_original_content_when_content_is_not_replaced()
{
var content = new NamedContentData();
var context = new ScriptContext { Data = content };
var result = scriptEngine.ExecuteAndTransform(context, "var x = 0;", "update");
Assert.Same(content, result);
}
[Fact]
public void Should_returning_empty_content_when_replacing_with_invalid_content()
{
var content =
new NamedContentData()
.AddField("number0",
new ContentFieldData()
.AddValue("iv", 1))
.AddField("number1",
new ContentFieldData()
.AddValue("iv", 1));
var context = new ScriptContext { Data = content };
var result = scriptEngine.ExecuteAndTransform(context, @"replace({ test: 1 });", "update");
Assert.Equal(new NamedContentData(), result);
}
[Fact]
public void Should_transform_content_and_return()
{
var content =
new NamedContentData()
.AddField("number0",
new ContentFieldData()
.AddValue("iv", 1))
.AddField("number1",
new ContentFieldData()
.AddValue("iv", 1));
var expected =
new NamedContentData()
.AddField("number1",
new ContentFieldData()
.AddValue("iv", 2))
.AddField("number2",
new ContentFieldData()
.AddValue("iv", 10));
var context = new ScriptContext { Data = content };
var result = scriptEngine.ExecuteAndTransform(context, @"
var data = ctx.data;
delete data.number0;
data.number1.iv = data.number1.iv + 1;
data.number2 = { 'iv': 10 };
replace(data);", "update");
Assert.Equal(expected, result);
}
}
}

4
tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -13,8 +13,8 @@
<PackageReference Include="FakeItEasy" Version="4.0.0" />
<PackageReference Include="FluentAssertions" Version="4.19.4" />
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="xunit" Version="2.2.0" />

Loading…
Cancel
Save