Browse Source

Not allowed page. (#430)

* Not allowed page.
* Do not restore default roles in DB.
pull/432/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
94a5fcd25f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs
  2. 13
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs
  3. 63
      src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
  4. 135
      src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs
  5. 2
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs
  6. 2
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs
  7. 20
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs
  8. 10
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs
  9. 61
      src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs
  10. 2
      src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs
  11. 4
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  12. 3
      src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs
  13. 2
      src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs
  14. 4
      src/Squidex.Shared/Permissions.cs
  15. 6
      src/Squidex.Web/PermissionExtensions.cs
  16. 4
      src/Squidex.Web/Pipeline/AppResolver.cs
  17. 4
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  18. 8
      src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs
  19. 2
      src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs
  20. 5
      src/Squidex/app/app.routes.ts
  21. 9
      src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts
  22. 8
      src/Squidex/app/shared/interceptors/auth.interceptor.ts
  23. 5
      src/Squidex/app/shell/declarations.ts
  24. 3
      src/Squidex/app/shell/module.ts
  25. 32
      src/Squidex/app/shell/pages/forbidden/forbidden-page.component.ts
  26. 13
      src/Squidex/app/shell/pages/not-found/not-found-page.component.html
  27. 2
      src/Squidex/app/shell/pages/not-found/not-found-page.component.scss
  28. 11
      src/Squidex/app/shell/pages/not-found/not-found-page.component.ts
  29. 79
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleTests.cs
  30. 12
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs
  31. 77
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs
  32. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs
  33. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs
  34. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs
  35. 80
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsTests.cs
  36. 5
      tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs

1
src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs

@ -4,6 +4,7 @@
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;

13
src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs

@ -18,11 +18,11 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
protected override void WriteValue(JsonWriter writer, Roles value, JsonSerializer serializer) protected override void WriteValue(JsonWriter writer, Roles value, JsonSerializer serializer)
{ {
var json = new Dictionary<string, string[]>(value.Count); var json = new Dictionary<string, string[]>(value.CustomCount);
foreach (var role in value) foreach (var role in value.Custom)
{ {
json.Add(role.Key, role.Value.Permissions.ToIds().ToArray()); json.Add(role.Name, role.Permissions.ToIds().ToArray());
} }
serializer.Serialize(writer, json); serializer.Serialize(writer, json);
@ -32,7 +32,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
var json = serializer.Deserialize<Dictionary<string, string[]>>(reader); var json = serializer.Deserialize<Dictionary<string, string[]>>(reader);
return new Roles(json.Select(Convert).ToArray()); if (json.Count == 0)
{
return Roles.Empty;
}
return new Roles(json.Select(Convert));
} }
private static KeyValuePair<string, Role> Convert(KeyValuePair<string, string[]> kvp) private static KeyValuePair<string, Role> Convert(KeyValuePair<string, string[]> kvp)

63
src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs

@ -8,9 +8,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using P = Squidex.Shared.Permissions; using AllPermissions = Squidex.Shared.Permissions;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
@ -21,16 +22,13 @@ namespace Squidex.Domain.Apps.Core.Apps
public const string Owner = "Owner"; public const string Owner = "Owner";
public const string Reader = "Reader"; public const string Reader = "Reader";
private static readonly HashSet<string> DefaultRolesSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
Editor,
Developer,
Owner,
Reader
};
public PermissionSet Permissions { get; } public PermissionSet Permissions { get; }
public bool IsDefault
{
get { return Roles.IsDefault(this); }
}
public Role(string name, PermissionSet permissions) public Role(string name, PermissionSet permissions)
: base(name) : base(name)
{ {
@ -39,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Apps
Permissions = permissions; Permissions = permissions;
} }
public Role(string name, params Permission[] permissions) public Role(string name, params string[] permissions)
: this(name, new PermissionSet(permissions)) : this(name, new PermissionSet(permissions))
{ {
} }
@ -50,50 +48,29 @@ namespace Squidex.Domain.Apps.Core.Apps
return new Role(Name, new PermissionSet(permissions)); return new Role(Name, new PermissionSet(permissions));
} }
public static bool IsDefaultRole(string role) public bool Equals(string name)
{ {
return role != null && DefaultRolesSet.Contains(role); return name != null && name.Equals(Name, StringComparison.Ordinal);
} }
public static bool IsRole(string name, string expected) public Role ForApp(string app)
{ {
return name != null && string.Equals(name, expected, StringComparison.OrdinalIgnoreCase); var result = new HashSet<Permission>
}
public static Role CreateOwner(string app)
{ {
return new Role(Owner, AllPermissions.ForApp(AllPermissions.AppCommon, app)
P.ForApp(P.App, app)); };
}
public static Role CreateEditor(string app) if (Permissions.Any())
{ {
return new Role(Editor, var prefix = AllPermissions.ForApp(AllPermissions.App, app).Id;
P.ForApp(P.AppAssets, app),
P.ForApp(P.AppCommon, app),
P.ForApp(P.AppContents, app),
P.ForApp(P.AppWorkflowsRead, app));
}
public static Role CreateReader(string app) foreach (var permission in Permissions)
{ {
return new Role(Reader, result.Add(new Permission(string.Concat(prefix, ".", permission.Id)));
P.ForApp(P.AppAssetsRead, app), }
P.ForApp(P.AppCommon, app),
P.ForApp(P.AppContentsRead, app));
} }
public static Role CreateDeveloper(string app) return new Role(Name, new PermissionSet(result));
{
return new Role(Developer,
P.ForApp(P.AppApi, app),
P.ForApp(P.AppAssets, app),
P.ForApp(P.AppCommon, app),
P.ForApp(P.AppContents, app),
P.ForApp(P.AppPatterns, app),
P.ForApp(P.AppWorkflows, app),
P.ForApp(P.AppRules, app),
P.ForApp(P.AppSchemas, app));
} }
} }
} }

135
src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs

@ -11,26 +11,78 @@ using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class Roles : ArrayDictionary<string, Role> public sealed class Roles
{ {
public static readonly Roles Empty = new Roles(); private readonly ArrayDictionary<string, Role> inner;
private Roles() public static readonly IReadOnlyDictionary<string, Role> Defaults = new Dictionary<string, Role>
{ {
[Role.Owner] =
new Role(Role.Owner, new PermissionSet(
Clean(Permissions.App))),
[Role.Reader] =
new Role(Role.Reader, new PermissionSet(
Clean(Permissions.AppAssetsRead),
Clean(Permissions.AppContentsRead))),
[Role.Editor] =
new Role(Role.Editor, new PermissionSet(
Clean(Permissions.AppAssets),
Clean(Permissions.AppContents),
Clean(Permissions.AppRolesRead),
Clean(Permissions.AppWorkflowsRead))),
[Role.Developer] =
new Role(Role.Developer, new PermissionSet(
Clean(Permissions.AppApi),
Clean(Permissions.AppAssets),
Clean(Permissions.AppContents),
Clean(Permissions.AppPatterns),
Clean(Permissions.AppRolesRead),
Clean(Permissions.AppRules),
Clean(Permissions.AppSchemas),
Clean(Permissions.AppWorkflows)))
};
public static readonly Roles Empty = new Roles(new ArrayDictionary<string, Role>());
public int CustomCount
{
get { return inner.Count; }
}
public Role this[string name]
{
get { return inner[name]; }
}
public IEnumerable<Role> Custom
{
get { return inner.Values; }
}
public IEnumerable<Role> All
{
get { return inner.Values.Union(Defaults.Values); }
} }
public Roles(KeyValuePair<string, Role>[] items) private Roles(ArrayDictionary<string, Role> roles)
: base(items)
{ {
inner = roles;
}
public Roles(IEnumerable<KeyValuePair<string, Role>> items)
{
inner = new ArrayDictionary<string, Role>(Cleaned(items));
} }
[Pure] [Pure]
public Roles Remove(string name) public Roles Remove(string name)
{ {
return new Roles(Without(name)); return new Roles(inner.Without(name));
} }
[Pure] [Pure]
@ -38,12 +90,12 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
var newRole = new Role(name); var newRole = new Role(name);
if (ContainsKey(name)) if (inner.ContainsKey(name))
{ {
throw new ArgumentException("Name already exists.", nameof(name)); throw new ArgumentException("Name already exists.", nameof(name));
} }
return new Roles(With(name, newRole)); return new Roles(inner.With(name, newRole));
} }
[Pure] [Pure]
@ -52,24 +104,71 @@ namespace Squidex.Domain.Apps.Core.Apps
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(permissions, nameof(permissions)); Guard.NotNull(permissions, nameof(permissions));
if (!TryGetValue(name, out var role)) if (!inner.TryGetValue(name, out var role))
{ {
return this; return this;
} }
return new Roles(With(name, role.Update(permissions))); return new Roles(inner.With(name, role.Update(permissions)));
}
public static bool IsDefault(string role)
{
return role != null && Defaults.ContainsKey(role);
} }
public static Roles CreateDefaults(string app) public static bool IsDefault(Role role)
{ {
return new Roles( return role != null && Defaults.ContainsKey(role.Name);
new Dictionary<string, Role> }
public bool ContainsCustom(string name)
{
return inner.ContainsKey(name);
}
public bool Contains(string name)
{
return inner.ContainsKey(name) || Defaults.ContainsKey(name);
}
public bool TryGet(string app, string name, out Role value)
{
Guard.NotNull(app, nameof(app));
value = null;
if (Defaults.TryGetValue(name, out var role) || inner.TryGetValue(name, out role))
{
value = role.ForApp(app);
return true;
}
return false;
}
private static string Clean(string permission)
{
permission = Permissions.ForApp(permission).Id;
var prefix = Permissions.ForApp(Permissions.App);
if (permission.StartsWith(prefix.Id, StringComparison.OrdinalIgnoreCase))
{
permission = permission.Substring(prefix.Id.Length);
}
if (permission.Length == 0)
{
return Permission.Any;
}
return permission.Substring(1);
}
private static KeyValuePair<string, Role>[] Cleaned(IEnumerable<KeyValuePair<string, Role>> items)
{ {
[Role.Developer] = Role.CreateDeveloper(app), return items.Where(x => !Defaults.ContainsKey(x.Key)).ToArray();
[Role.Editor] = Role.CreateEditor(app),
[Role.Owner] = Role.CreateOwner(app),
[Role.Reader] = Role.CreateReader(app)
}.ToArray());
} }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs

@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
e(Not.DefinedOr("name", "role"), nameof(command.Name), nameof(command.Role)); e(Not.DefinedOr("name", "role"), nameof(command.Name), nameof(command.Role));
} }
if (command.Role != null && !roles.ContainsKey(command.Role)) if (command.Role != null && !roles.Contains(command.Role))
{ {
e(Not.Valid("role"), nameof(command.Role)); e(Not.Valid("role"), nameof(command.Role));
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
return Validate.It(() => "Cannot assign contributor.", async e => return Validate.It(() => "Cannot assign contributor.", async e =>
{ {
if (!roles.ContainsKey(command.Role)) if (!roles.Contains(command.Role))
{ {
e(Not.Valid("role"), nameof(command.Role)); e(Not.Valid("role"), nameof(command.Role));
} }

20
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined("Name"), nameof(command.Name));
} }
else if (roles.ContainsKey(command.Name)) else if (roles.Contains(command.Name))
{ {
e("A role with the same name already exists."); e("A role with the same name already exists.");
} }
@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
GetRoleOrThrow(roles, command.Name); CheckRoleExists(roles, command.Name);
Validate.It(() => "Cannot delete role.", e => Validate.It(() => "Cannot delete role.", e =>
{ {
@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined("Name"), nameof(command.Name));
} }
else if (Role.IsDefaultRole(command.Name)) else if (Roles.IsDefault(command.Name))
{ {
e("Cannot delete a default role."); e("Cannot delete a default role.");
} }
@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
GetRoleOrThrow(roles, command.Name); CheckRoleExists(roles, command.Name);
Validate.It(() => "Cannot delete role.", e => Validate.It(() => "Cannot delete role.", e =>
{ {
@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined("Name"), nameof(command.Name));
} }
else if (Role.IsDefaultRole(command.Name)) else if (Roles.IsDefault(command.Name))
{ {
e("Cannot update a default role."); e("Cannot update a default role.");
} }
@ -86,19 +86,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
}); });
} }
private static Role GetRoleOrThrow(Roles roles, string name) private static void CheckRoleExists(Roles roles, string name)
{ {
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name) || Roles.IsDefault(name))
{ {
return null; return;
} }
if (!roles.TryGetValue(name, out var role)) if (!roles.ContainsCustom(name))
{ {
throw new DomainObjectNotFoundException(name, "Roles", typeof(IAppEntity)); throw new DomainObjectNotFoundException(name, "Roles", typeof(IAppEntity));
} }
return role;
} }
} }
} }

10
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
GetWorkflowOrThrow(workflows, command.WorkflowId); CheckWorkflowExists(workflows, command.WorkflowId);
Validate.It(() => "Cannot update workflow.", e => Validate.It(() => "Cannot update workflow.", e =>
{ {
@ -94,17 +94,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
GetWorkflowOrThrow(workflows, command.WorkflowId); CheckWorkflowExists(workflows, command.WorkflowId);
} }
private static Workflow GetWorkflowOrThrow(Workflows workflows, Guid id) private static void CheckWorkflowExists(Workflows workflows, Guid id)
{ {
if (!workflows.TryGetValue(id, out var workflow)) if (!workflows.ContainsKey(id))
{ {
throw new DomainObjectNotFoundException(id.ToString(), "Workflows", typeof(IAppEntity)); throw new DomainObjectNotFoundException(id.ToString(), "Workflows", typeof(IAppEntity));
} }
return workflow;
} }
} }
} }

61
src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs

@ -1,61 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
namespace Squidex.Domain.Apps.Entities.Apps
{
public static class RoleExtensions
{
public static string[] Prefix(this string[] permissions, string name)
{
var result = new string[permissions.Length + 1];
result[0] = Permissions.ForApp(Permissions.AppCommon, name).Id;
if (permissions.Length > 0)
{
var prefix = Permissions.ForApp(Permissions.App, name).Id;
for (var i = 0; i < permissions.Length; i++)
{
result[i + 1] = string.Concat(prefix, ".", permissions[i]);
}
}
permissions = result.Distinct().ToArray();
return permissions;
}
public static PermissionSet WithoutApp(this PermissionSet set, string name)
{
var prefix = Permissions.ForApp(Permissions.App, name).Id;
return new PermissionSet(set.Select(x =>
{
var id = x.Id;
if (string.Equals(id, prefix, StringComparison.OrdinalIgnoreCase))
{
return Permission.Any;
}
else if (id.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return id.Substring(prefix.Length + 1);
}
else
{
return id;
}
}).Where(x => x != "common"));
}
}
}

2
src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs

@ -12,6 +12,8 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Shared; using Squidex.Shared;
#pragma warning disable IDE0028 // Simplify collection initialization
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public sealed class RolePermissionsProvider public sealed class RolePermissionsProvider

4
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -62,8 +62,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
{ {
case AppCreated e: case AppCreated e:
{ {
Roles = Roles.CreateDefaults(e.Name);
SimpleMapper.Map(e, this); SimpleMapper.Map(e, this);
break; break;
@ -204,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
case AppRoleUpdated e: case AppRoleUpdated e:
{ {
Roles = Roles.Update(e.Name, e.Permissions.Prefix(Name)); Roles = Roles.Update(e.Name, e.Permissions);
break; break;
} }

3
src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs

@ -132,6 +132,8 @@ namespace Squidex.Infrastructure.Collections
public bool TryGetValue(TKey key, out TValue value) public bool TryGetValue(TKey key, out TValue value)
{ {
value = default;
for (var i = 0; i < items.Length; i++) for (var i = 0; i < items.Length; i++)
{ {
if (keyComparer.Equals(items[i].Key, key)) if (keyComparer.Equals(items[i].Key, key))
@ -141,7 +143,6 @@ namespace Squidex.Infrastructure.Collections
} }
} }
value = default;
return false; return false;
} }

2
src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs

@ -185,7 +185,7 @@ namespace Squidex.Infrastructure.UsageTracking
private static string GetCategory(string category) private static string GetCategory(string category)
{ {
return !string.IsNullOrWhiteSpace(category) ? category.Trim() : "*"; return !string.IsNullOrWhiteSpace(category) ? category.Trim() : FallbackCategory;
} }
private static string GetKey(string key) private static string GetKey(string key)

4
src/Squidex.Shared/Permissions.cs

@ -153,11 +153,11 @@ namespace Squidex.Shared
} }
} }
public static Permission ForApp(string id, string app = "*", string schema = "*") public static Permission ForApp(string id, string app = Permission.Any, string schema = Permission.Any)
{ {
Guard.NotNull(id, nameof(id)); Guard.NotNull(id, nameof(id));
return new Permission(id.Replace("{app}", app ?? "*").Replace("{name}", schema ?? "*")); return new Permission(id.Replace("{app}", app ?? Permission.Any).Replace("{name}", schema ?? Permission.Any));
} }
public static PermissionSet ToAppPermissions(this PermissionSet permissions, string app) public static PermissionSet ToAppPermissions(this PermissionSet permissions, string app)

6
src/Squidex.Web/PermissionExtensions.cs

@ -38,9 +38,9 @@ namespace Squidex.Web
return controller.HttpContext.HasPermission(permission) || additional?.Allows(permission) == true; return controller.HttpContext.HasPermission(permission) || additional?.Allows(permission) == true;
} }
public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*", PermissionSet additional = null) public static bool HasPermission(this ApiController controller, string id, string app = Permission.Any, string schema = Permission.Any, PermissionSet additional = null)
{ {
if (app == "*") if (app == Permission.Any)
{ {
if (controller.RouteData.Values.TryGetValue("app", out var value) && value is string s) if (controller.RouteData.Values.TryGetValue("app", out var value) && value is string s)
{ {
@ -48,7 +48,7 @@ namespace Squidex.Web
} }
} }
if (schema == "*") if (schema == Permission.Any)
{ {
if (controller.RouteData.Values.TryGetValue("name", out var value) && value is string s) if (controller.RouteData.Values.TryGetValue("name", out var value) && value is string s)
{ {

4
src/Squidex.Web/Pipeline/AppResolver.cs

@ -90,7 +90,7 @@ namespace Squidex.Web.Pipeline
{ {
var clientId = user.GetClientId(); var clientId = user.GetClientId();
if (clientId != null && app.Clients.TryGetValue(clientId, out var client) && app.Roles.TryGetValue(client.Role, out var role)) if (clientId != null && app.Clients.TryGetValue(clientId, out var client) && app.Roles.TryGet(app.Name, client.Role, out var role))
{ {
return (client.Role, role.Permissions); return (client.Role, role.Permissions);
} }
@ -102,7 +102,7 @@ namespace Squidex.Web.Pipeline
{ {
var subjectId = user.OpenIdSubject(); var subjectId = user.OpenIdSubject();
if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var roleName) && app.Roles.TryGetValue(roleName, out var role)) if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var roleName) && app.Roles.TryGet(app.Name, roleName, out var role))
{ {
return (roleName, role.Permissions); return (roleName, role.Permissions);
} }

4
src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs

@ -104,7 +104,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
result.CanAccessApi = true; result.CanAccessApi = true;
} }
if (controller.Includes(AllPermissions.ForApp(AllPermissions.AppContents, app.Name, "*"), permissions)) if (controller.Includes(AllPermissions.ForApp(AllPermissions.AppContents, app.Name), permissions))
{ {
result.CanAccessContent = true; result.CanAccessContent = true;
} }
@ -119,7 +119,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
{ {
var permissions = new List<Permission>(); var permissions = new List<Permission>();
if (app.Contributors.TryGetValue(userId, out var roleName) && app.Roles.TryGetValue(roleName, out var role)) if (app.Contributors.TryGetValue(userId, out var roleName) && app.Roles.TryGet(app.Name, roleName, out var role))
{ {
permissions.AddRange(role.Permissions); permissions.AddRange(role.Permissions);
} }

8
src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs

@ -46,7 +46,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
public static RoleDto FromRole(Role role, IAppEntity app) public static RoleDto FromRole(Role role, IAppEntity app)
{ {
var permissions = role.Permissions.WithoutApp(app.Name); var permissions = role.Permissions;
var result = new RoleDto var result = new RoleDto
{ {
@ -54,7 +54,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
NumClients = GetNumClients(role, app), NumClients = GetNumClients(role, app),
NumContributors = GetNumContributors(role, app), NumContributors = GetNumContributors(role, app),
Permissions = permissions.ToIds(), Permissions = permissions.ToIds(),
IsDefaultRole = Role.IsDefaultRole(role.Name) IsDefaultRole = role.IsDefault
}; };
return result; return result;
@ -62,12 +62,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
private static int GetNumContributors(Role role, IAppEntity app) private static int GetNumContributors(Role role, IAppEntity app)
{ {
return app.Contributors.Count(x => Role.IsRole(x.Value, role.Name)); return app.Contributors.Count(x => role.Equals(x.Value));
} }
private static int GetNumClients(Role role, IAppEntity app) private static int GetNumClients(Role role, IAppEntity app)
{ {
return app.Clients.Count(x => Role.IsRole(x.Value.Role, role.Name)); return app.Clients.Count(x => role.Equals(x.Value.Role));
} }
public RoleDto WithLinks(ApiController controller, string app) public RoleDto WithLinks(ApiController controller, string app)

2
src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs

@ -28,7 +28,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
var result = new RolesDto var result = new RolesDto
{ {
Items = Items =
app.Roles.Values app.Roles.All
.Select(x => RoleDto.FromRole(x, app)) .Select(x => RoleDto.FromRole(x, app))
.Select(x => x.WithLinks(controller, appName)) .Select(x => x.WithLinks(controller, appName))
.OrderBy(x => x.Name) .OrderBy(x => x.Name)

5
src/Squidex/app/app.routes.ts

@ -10,6 +10,7 @@ import { RouterModule, Routes } from '@angular/router';
import { import {
AppAreaComponent, AppAreaComponent,
ForbiddenPageComponent,
HomePageComponent, HomePageComponent,
InternalAreaComponent, InternalAreaComponent,
LoginPageComponent, LoginPageComponent,
@ -91,6 +92,10 @@ export const routes: Routes = [
path: 'login', path: 'login',
component: LoginPageComponent component: LoginPageComponent
}, },
{
path: 'forbidden',
component: ForbiddenPageComponent
},
{ {
path: '**', path: '**',
component: NotFoundPageComponent component: NotFoundPageComponent

9
src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts

@ -8,6 +8,7 @@
import { HTTP_INTERCEPTORS, HttpClient, HttpHeaders } from '@angular/common/http'; import { HTTP_INTERCEPTORS, HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { onErrorResumeNext } from 'rxjs/operators'; import { onErrorResumeNext } from 'rxjs/operators';
import { IMock, Mock, Times } from 'typemoq'; import { IMock, Mock, Times } from 'typemoq';
@ -17,15 +18,19 @@ import { AuthInterceptor } from './auth.interceptor';
describe('AuthInterceptor', () => { describe('AuthInterceptor', () => {
let authService: IMock<AuthService>; let authService: IMock<AuthService>;
let router: IMock<Router>;
beforeEach(() => { beforeEach(() => {
authService = Mock.ofType(AuthService); authService = Mock.ofType(AuthService);
router = Mock.ofType<Router>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
HttpClientTestingModule HttpClientTestingModule
], ],
providers: [ providers: [
{ provide: Router, useFactory: () => router.object },
{ provide: AuthService, useValue: authService.object }, { provide: AuthService, useValue: authService.object },
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') },
{ {
@ -103,7 +108,7 @@ describe('AuthInterceptor', () => {
})); }));
[403].forEach(statusCode => { [403].forEach(statusCode => {
it(`should logout for ${statusCode} status code`, it(`should redirect for ${statusCode} status code`,
inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
authService.setup(x => x.userChanges).returns(() => of(<any>{ authToken: 'letmein' })); authService.setup(x => x.userChanges).returns(() => of(<any>{ authToken: 'letmein' }));
@ -116,7 +121,7 @@ describe('AuthInterceptor', () => {
expect().nothing(); expect().nothing();
authService.verify(x => x.logoutRedirect(), Times.once()); router.verify(x => x.navigate(['/forbidden'], { replaceUrl: true }), Times.once());
})); }));
}); });

8
src/Squidex/app/shared/interceptors/auth.interceptor.ts

@ -7,6 +7,7 @@
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable} from '@angular/core'; import { Injectable} from '@angular/core';
import { Router } from '@angular/router';
import { empty, Observable, throwError } from 'rxjs'; import { empty, Observable, throwError } from 'rxjs';
import { catchError, switchMap, take } from 'rxjs/operators'; import { catchError, switchMap, take } from 'rxjs/operators';
@ -19,7 +20,8 @@ export class AuthInterceptor implements HttpInterceptor {
private baseUrl: string; private baseUrl: string;
constructor(apiUrlConfig: ApiUrlConfig, constructor(apiUrlConfig: ApiUrlConfig,
private readonly authService: AuthService private readonly authService: AuthService,
private readonly router: Router
) { ) {
this.baseUrl = apiUrlConfig.buildUrl(''); this.baseUrl = apiUrlConfig.buildUrl('');
} }
@ -58,7 +60,11 @@ export class AuthInterceptor implements HttpInterceptor {
switchMap(u => this.makeRequest(req, next, u))); switchMap(u => this.makeRequest(req, next, u)));
} else if (error.status === 401 || error.status === 403) { } else if (error.status === 401 || error.status === 403) {
if (req.method === 'GET') { if (req.method === 'GET') {
if (error.status === 401) {
this.authService.logoutRedirect(); this.authService.logoutRedirect();
} else {
this.router.navigate(['/forbidden'], { replaceUrl: true });
}
return empty(); return empty();
} else { } else {

5
src/Squidex/app/shell/declarations.ts

@ -7,14 +7,11 @@
export * from './pages/app/app-area.component'; export * from './pages/app/app-area.component';
export * from './pages/app/left-menu.component'; export * from './pages/app/left-menu.component';
export * from './pages/forbidden/forbidden-page.component';
export * from './pages/home/home-page.component'; export * from './pages/home/home-page.component';
export * from './pages/internal/apps-menu.component'; export * from './pages/internal/apps-menu.component';
export * from './pages/internal/internal-area.component'; export * from './pages/internal/internal-area.component';
export * from './pages/internal/profile-menu.component'; export * from './pages/internal/profile-menu.component';
export * from './pages/login/login-page.component'; export * from './pages/login/login-page.component';
export * from './pages/logout/logout-page.component'; export * from './pages/logout/logout-page.component';
export * from './pages/not-found/not-found-page.component'; export * from './pages/not-found/not-found-page.component';

3
src/Squidex/app/shell/module.ts

@ -12,6 +12,7 @@ import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { import {
AppAreaComponent, AppAreaComponent,
AppsMenuComponent, AppsMenuComponent,
ForbiddenPageComponent,
HomePageComponent, HomePageComponent,
InternalAreaComponent, InternalAreaComponent,
LeftMenuComponent, LeftMenuComponent,
@ -29,12 +30,14 @@ import {
exports: [ exports: [
AppAreaComponent, AppAreaComponent,
HomePageComponent, HomePageComponent,
ForbiddenPageComponent,
InternalAreaComponent, InternalAreaComponent,
NotFoundPageComponent NotFoundPageComponent
], ],
declarations: [ declarations: [
AppAreaComponent, AppAreaComponent,
AppsMenuComponent, AppsMenuComponent,
ForbiddenPageComponent,
HomePageComponent, HomePageComponent,
InternalAreaComponent, InternalAreaComponent,
LeftMenuComponent, LeftMenuComponent,

32
src/Squidex/app/shell/pages/forbidden/forbidden-page.component.ts

@ -0,0 +1,32 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Location } from '@angular/common';
import { Component } from '@angular/core';
@Component({
selector: 'sqx-forbidden-page',
template: `
<sqx-title message="Not Found"></sqx-title>
<div class="landing-page">
<img class="splash-image" src="~/../squid.svg?title=FORBIDDEN&text=You%20are%20not%20allowed%20to%20view%20this%20page&background=white&small" />
<a href="#" (click)="back()">Back to previous page.</a>
</div>
`
})
export class ForbiddenPageComponent {
constructor(
private readonly location: Location
) {
}
public back() {
this.location.back();
}
}

13
src/Squidex/app/shell/pages/not-found/not-found-page.component.html

@ -1,13 +0,0 @@
<sqx-title message="Not Found"></sqx-title>
<div class="landing-page">
<img class="splash-image" src="~/../squid.svg?title=OH%20DAMN&text=This%20is%20not%20the%20page%20you%20are%20looking%20for!&background=white&small" />
<h1>Not Found</h1>
<p>
Sorry, the page or resource you are looking for does not exist.
</p>
<a href="#" (click)="back()">Back to previous page.</a>
</div>

2
src/Squidex/app/shell/pages/not-found/not-found-page.component.scss

@ -1,2 +0,0 @@
@import '_mixins';
@import '_vars';

11
src/Squidex/app/shell/pages/not-found/not-found-page.component.ts

@ -10,8 +10,15 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'sqx-not-found-page', selector: 'sqx-not-found-page',
styleUrls: ['./not-found-page.component.scss'], template: `
templateUrl: './not-found-page.component.html' <sqx-title message="Not Found"></sqx-title>
<div class="landing-page">
<img class="splash-image" src="~/../squid.svg?title=Not Found&text=This%20is%20not%20the%20page%20you%20are%20looking%20for!&background=white&small" />
<a href="#" (click)="back()">Back to previous page.</a>
</div>
`
}) })
export class NotFoundPageComponent { export class NotFoundPageComponent {
constructor( constructor(

79
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleTests.cs

@ -0,0 +1,79 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Apps
{
public class RoleTests
{
[Fact]
public void Should_be_default_role()
{
var role = new Role("Owner");
Assert.True(role.IsDefault);
}
[Fact]
public void Should_not_be_default_role()
{
var role = new Role("Custom");
Assert.False(role.IsDefault);
}
[Fact]
public void Should_add_common_permission()
{
var role = new Role("Name");
var result = role.ForApp("my-app").Permissions.ToIds();
Assert.Equal(new[] { "squidex.apps.my-app.common" }, result);
}
[Fact]
public void Should_not_have_duplicate_permission()
{
var role = new Role("Name", "common", "common", "common");
var result = role.ForApp("my-app").Permissions.ToIds();
Assert.Single(result);
}
[Fact]
public void Should_ForApp_permission()
{
var role = new Role("Name", "clients.read");
var result = role.ForApp("my-app").Permissions.ToIds();
Assert.Equal("squidex.apps.my-app.clients.read", result.ElementAt(1));
}
[Fact]
public void Should_check_for_name()
{
var role = new Role("Custom");
Assert.True(role.Equals("Custom"));
}
[Fact]
public void Should_check_for_null_name()
{
var role = new Role("Custom");
Assert.False(role.Equals(null));
Assert.False(role.Equals("Other"));
}
}
}

12
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs

@ -16,11 +16,21 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact] [Fact]
public void Should_serialize_and_deserialize() public void Should_serialize_and_deserialize()
{ {
var sut = Roles.CreateDefaults("my-app"); var sut = Roles.Empty.Add("Custom").Update("Custom", "Permission1", "Permission2");
var roles = sut.SerializeAndDeserialize(); var roles = sut.SerializeAndDeserialize();
roles.Should().BeEquivalentTo(sut); roles.Should().BeEquivalentTo(sut);
} }
[Fact]
public void Should_serialize_and_deserialize_empty()
{
var sut = Roles.Empty;
var roles = sut.SerializeAndDeserialize();
Assert.Same(Roles.Empty, roles);
}
} }
} }

77
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using FluentAssertions; using FluentAssertions;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
@ -26,6 +27,14 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
roles_0 = Roles.Empty.Add(firstRole); roles_0 = Roles.Empty.Add(firstRole);
} }
[Fact]
public void Should_create_roles_without_defaults()
{
var roles = new Roles(Roles.Defaults.ToArray());
Assert.Equal(0, roles.CustomCount);
}
[Fact] [Fact]
public void Should_add_role() public void Should_add_role()
{ {
@ -63,7 +72,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
var roles_1 = roles_0.Remove(firstRole); var roles_1 = roles_0.Remove(firstRole);
Assert.Empty(roles_1); Assert.Equal(0, roles_1.CustomCount);
} }
[Fact] [Fact]
@ -71,23 +80,75 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
var roles_1 = roles_0.Remove(role); var roles_1 = roles_0.Remove(role);
Assert.NotEmpty(roles_1); Assert.True(roles_1.CustomCount > 0);
} }
[Fact] [Fact]
public void Should_create_defaults() public void Should_get_custom_roles()
{ {
var sut = Roles.CreateDefaults("my-app"); var names = roles_0.Custom.Select(x => x.Name).ToArray();
Assert.Equal(4, sut.Count); Assert.Equal(new[] { firstRole }, names);
}
foreach (var sutRole in sut) [Fact]
public void Should_get_all_roles()
{ {
foreach (var permission in sutRole.Value.Permissions) var names = roles_0.All.Select(x => x.Name).ToArray();
Assert.Equal(new[] { firstRole, "Owner", "Reader", "Editor", "Developer" }, names);
}
[Fact]
public void Should_check_for_custom_role()
{ {
Assert.StartsWith("squidex.apps.my-app", permission.Id); Assert.True(roles_0.ContainsCustom(firstRole));
} }
[Fact]
public void Should_check_for_non_custom_role()
{
Assert.False(roles_0.ContainsCustom(Role.Owner));
} }
[Fact]
public void Should_check_for_default_role()
{
Assert.True(Roles.IsDefault(Role.Owner));
}
[Fact]
public void Should_check_for_non_default_role()
{
Assert.False(Roles.IsDefault(firstRole));
}
[InlineData("Developer")]
[InlineData("Editor")]
[InlineData("Owner")]
[InlineData("Reader")]
[Theory]
public void Should_get_default_roles(string name)
{
var found = roles_0.TryGet("app", name, out var role);
Assert.True(found);
Assert.True(role.IsDefault);
Assert.True(roles_0.Contains(name));
foreach (var permission in role.Permissions)
{
Assert.StartsWith("squidex.apps.app.", permission.Id);
}
}
[Fact]
public void Should_return_null_if_role_not_found()
{
var found = roles_0.TryGet("app", "custom", out var role);
Assert.False(found);
Assert.Null(role);
} }
} }
} }

4
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs

@ -487,7 +487,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
result.ShouldBeEquivalent(sut.Snapshot); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(5, sut.Snapshot.Roles.Count); Assert.Equal(1, sut.Snapshot.Roles.CustomCount);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
@ -507,7 +507,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
result.ShouldBeEquivalent(sut.Snapshot); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(4, sut.Snapshot.Roles.Count); Assert.Equal(0, sut.Snapshot.Roles.CustomCount);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(

2
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs

@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
public class GuardAppClientsTests public class GuardAppClientsTests
{ {
private readonly AppClients clients_0 = AppClients.Empty; private readonly AppClients clients_0 = AppClients.Empty;
private readonly Roles roles = Roles.CreateDefaults("my-app"); private readonly Roles roles = Roles.Empty;
[Fact] [Fact]
public void CanAttach_should_throw_execption_if_client_id_is_null() public void CanAttach_should_throw_execption_if_client_id_is_null()

2
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs

@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
private readonly IUserResolver users = A.Fake<IUserResolver>(); private readonly IUserResolver users = A.Fake<IUserResolver>();
private readonly IAppLimitsPlan appPlan = A.Fake<IAppLimitsPlan>(); private readonly IAppLimitsPlan appPlan = A.Fake<IAppLimitsPlan>();
private readonly AppContributors contributors_0 = AppContributors.Empty; private readonly AppContributors contributors_0 = AppContributors.Empty;
private readonly Roles roles = Roles.CreateDefaults("my-app"); private readonly Roles roles = Roles.Empty;
public GuardAppContributorsTests() public GuardAppContributorsTests()
{ {

80
tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsTests.cs

@ -1,80 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Squidex.Infrastructure.Security;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Apps
{
public class RoleExtensionsTests
{
[Fact]
public void Should_add_common_permission()
{
var source = Array.Empty<string>();
var result = source.Prefix("my-app");
Assert.Equal(new[] { "squidex.apps.my-app.common" }, result);
}
[Fact]
public void Should_not_have_duplicate_permission()
{
var source = new[] { "common", "common", "common" };
var result = source.Prefix("my-app");
Assert.Single(result);
}
[Fact]
public void Should_prefix_permission()
{
var source = new[] { "clients.read" };
var result = source.Prefix("my-app");
Assert.Equal("squidex.apps.my-app.clients.read", result[1]);
}
[Fact]
public void Should_remove_app_prefix()
{
var source = new PermissionSet("squidex.apps.my-app.clients");
var result = source.WithoutApp("my-app");
Assert.Equal("clients", result.First().Id);
}
[Fact]
public void Should_not_remove_app_prefix_when_other_app()
{
var source = new PermissionSet("squidex.apps.other-app.clients");
var result = source.WithoutApp("my-app");
Assert.Equal("squidex.apps.other-app.clients", result.First().Id);
}
[Fact]
public void Should_set_to_wildcard_when_app_root_permission()
{
var source = new PermissionSet("squidex.apps.my-app");
var result = source.WithoutApp("my-app");
Assert.Equal(Permission.Any, result.First().Id);
}
[Fact]
public void Should_remove_common_permission()
{
var source = new PermissionSet("squidex.apps.my-app.common");
var result = source.WithoutApp("my-app");
Assert.Empty(result);
}
}
}

5
tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs

@ -185,8 +185,11 @@ namespace Squidex.Web.Pipeline
.Returns(AppClients.Empty); .Returns(AppClients.Empty);
} }
A.CallTo(() => appEntity.Name)
.Returns(name);
A.CallTo(() => appEntity.Roles) A.CallTo(() => appEntity.Roles)
.Returns(Roles.CreateDefaults(name)); .Returns(Roles.Empty);
return appEntity; return appEntity;
} }

Loading…
Cancel
Save