mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
242 changed files with 4975 additions and 1294 deletions
@ -0,0 +1,39 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Infrastructure.Json; |
|||
using Squidex.Infrastructure.Security; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Apps.Json |
|||
{ |
|||
public sealed class RolesConverter : JsonClassConverter<Roles> |
|||
{ |
|||
protected override void WriteValue(JsonWriter writer, Roles value, JsonSerializer serializer) |
|||
{ |
|||
var json = new Dictionary<string, string[]>(value.Count); |
|||
|
|||
foreach (var role in value) |
|||
{ |
|||
json.Add(role.Key, role.Value.Permissions.ToIds().ToArray()); |
|||
} |
|||
|
|||
serializer.Serialize(writer, json); |
|||
} |
|||
|
|||
protected override Roles ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) |
|||
{ |
|||
var json = serializer.Deserialize<Dictionary<string, string[]>>(reader); |
|||
|
|||
return new Roles(json.ToImmutableDictionary(x => x.Key, x => new Role(x.Key, new PermissionSet(x.Value)))); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Security; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using P = Squidex.Shared.Permissions; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Apps |
|||
{ |
|||
public sealed class Role : Named |
|||
{ |
|||
public const string Editor = "Editor"; |
|||
public const string Developer = "Developer"; |
|||
public const string Owner = "Owner"; |
|||
public const string Reader = "Reader"; |
|||
|
|||
private static readonly HashSet<string> DefaultRolesSet = new HashSet<string> |
|||
{ |
|||
Editor, |
|||
Developer, |
|||
Owner, |
|||
Reader |
|||
}; |
|||
|
|||
public PermissionSet Permissions { get; } |
|||
|
|||
public Role(string name, PermissionSet permissions) |
|||
: base(name) |
|||
{ |
|||
Guard.NotNull(permissions, nameof(permissions)); |
|||
|
|||
Permissions = permissions; |
|||
} |
|||
|
|||
public Role(string name, params Permission[] permissions) |
|||
: this(name, new PermissionSet(permissions)) |
|||
{ |
|||
} |
|||
|
|||
[Pure] |
|||
public Role Update(string[] permissions) |
|||
{ |
|||
return new Role(Name, new PermissionSet(permissions)); |
|||
} |
|||
|
|||
public static bool IsDefaultRole(string role) |
|||
{ |
|||
return role != null && DefaultRolesSet.Contains(role); |
|||
} |
|||
|
|||
public static Role CreateOwner(string app) |
|||
{ |
|||
return new Role(Owner, |
|||
P.ForApp(P.App, app)); |
|||
} |
|||
|
|||
public static Role CreateEditor(string app) |
|||
{ |
|||
return new Role(Editor, |
|||
P.ForApp(P.AppAssets, app), |
|||
P.ForApp(P.AppCommon, app), |
|||
P.ForApp(P.AppContents, app)); |
|||
} |
|||
|
|||
public static Role CreateReader(string app) |
|||
{ |
|||
return new Role(Reader, |
|||
P.ForApp(P.AppAssetsRead, app), |
|||
P.ForApp(P.AppCommon, app), |
|||
P.ForApp(P.AppContentsRead, app)); |
|||
} |
|||
|
|||
public static Role CreateDeveloper(string app) |
|||
{ |
|||
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.AppRules, app), |
|||
P.ForApp(P.AppSchemas, app)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Apps |
|||
{ |
|||
public static class RoleExtension |
|||
{ |
|||
public static AppPermission ToAppPermission(this AppClientPermission clientPermission) |
|||
{ |
|||
Guard.Enum(clientPermission, nameof(clientPermission)); |
|||
|
|||
return (AppPermission)Enum.Parse(typeof(AppPermission), clientPermission.ToString()); |
|||
} |
|||
|
|||
public static AppPermission ToAppPermission(this AppContributorPermission contributorPermission) |
|||
{ |
|||
Guard.Enum(contributorPermission, nameof(contributorPermission)); |
|||
|
|||
return (AppPermission)Enum.Parse(typeof(AppPermission), contributorPermission.ToString()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Diagnostics.Contracts; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Apps |
|||
{ |
|||
public sealed class Roles : DictionaryWrapper<string, Role> |
|||
{ |
|||
public static readonly Roles Empty = new Roles(); |
|||
|
|||
private Roles() |
|||
: base(ImmutableDictionary<string, Role>.Empty) |
|||
{ |
|||
} |
|||
|
|||
public Roles(ImmutableDictionary<string, Role> inner) |
|||
: base(inner) |
|||
{ |
|||
} |
|||
|
|||
[Pure] |
|||
public Roles Add(string name) |
|||
{ |
|||
var newRole = new Role(name); |
|||
|
|||
return new Roles(Inner.Add(name, newRole)); |
|||
} |
|||
|
|||
[Pure] |
|||
public Roles Remove(string name) |
|||
{ |
|||
return new Roles(Inner.Remove(name)); |
|||
} |
|||
|
|||
[Pure] |
|||
public Roles Update(string name, params string[] permissions) |
|||
{ |
|||
Guard.NotNullOrEmpty(name, nameof(name)); |
|||
Guard.NotNull(permissions, nameof(permissions)); |
|||
|
|||
if (!TryGetValue(name, out var role)) |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
return new Roles(Inner.SetItem(name, role.Update(permissions))); |
|||
} |
|||
|
|||
public static Roles CreateDefaults(string app) |
|||
{ |
|||
return new Roles( |
|||
new Dictionary<string, Role> |
|||
{ |
|||
[Role.Developer] = Role.CreateDeveloper(app), |
|||
[Role.Editor] = Role.CreateEditor(app), |
|||
[Role.Owner] = Role.CreateOwner(app), |
|||
[Role.Reader] = Role.CreateReader(app) |
|||
}.ToImmutableDictionary()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
|||
{ |
|||
public sealed class AddRole : AppCommand |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
|||
{ |
|||
public sealed class DeleteRole : AppCommand |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
} |
|||
@ -1,19 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Pipeline |
|||
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
|||
{ |
|||
public sealed class MustBeAppOwnerAttribute : AppPermissionAttribute |
|||
public sealed class UpdateRole : AppCommand |
|||
{ |
|||
public MustBeAppOwnerAttribute() |
|||
: base(AppPermission.Owner) |
|||
{ |
|||
} |
|||
public string Name { get; set; } |
|||
|
|||
public string[] Permissions { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Guards |
|||
{ |
|||
public static class GuardAppRoles |
|||
{ |
|||
public static void CanAdd(Roles roles, AddRole command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
Validate.It(() => "Cannot add role.", e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.Name)) |
|||
{ |
|||
e("Name is required.", nameof(command.Name)); |
|||
} |
|||
else if (roles.ContainsKey(command.Name)) |
|||
{ |
|||
e("A role with the same name already exists."); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanDelete(Roles roles, DeleteRole command, AppContributors contributors, AppClients clients) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
GetRoleOrThrow(roles, command.Name); |
|||
|
|||
Validate.It(() => "Cannot delete role.", e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.Name)) |
|||
{ |
|||
e("Name is required.", nameof(command.Name)); |
|||
} |
|||
else if (Role.IsDefaultRole(command.Name)) |
|||
{ |
|||
e("Cannot delete a default role."); |
|||
} |
|||
|
|||
if (clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase))) |
|||
{ |
|||
e("Cannot remove a role when a client is assigned."); |
|||
} |
|||
|
|||
if (contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase))) |
|||
{ |
|||
e("Cannot remove a role when a contributor is assigned."); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanUpdate(Roles roles, UpdateRole command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
GetRoleOrThrow(roles, command.Name); |
|||
|
|||
Validate.It(() => "Cannot delete role.", e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.Name)) |
|||
{ |
|||
e("Name is required.", nameof(command.Name)); |
|||
} |
|||
else if (Role.IsDefaultRole(command.Name)) |
|||
{ |
|||
e("Cannot update a default role."); |
|||
} |
|||
|
|||
if (command.Permissions == null) |
|||
{ |
|||
e("Permissions is required.", nameof(command.Permissions)); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static Role GetRoleOrThrow(Roles roles, string name) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(name)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (!roles.TryGetValue(name, out var role)) |
|||
{ |
|||
throw new DomainObjectNotFoundException(name, "Roles", typeof(IAppEntity)); |
|||
} |
|||
|
|||
return role; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// ==========================================================================
|
|||
// 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; |
|||
|
|||
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; |
|||
} |
|||
})); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Security; |
|||
using Squidex.Shared; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps |
|||
{ |
|||
public sealed class RolePermissionsProvider |
|||
{ |
|||
private readonly IAppProvider appProvider; |
|||
|
|||
public RolePermissionsProvider(IAppProvider appProvider) |
|||
{ |
|||
Guard.NotNull(appProvider, nameof(appProvider)); |
|||
|
|||
this.appProvider = appProvider; |
|||
} |
|||
|
|||
public async Task<List<string>> GetPermissionsAsync(IAppEntity app) |
|||
{ |
|||
var schemaNames = await GetSchemaNamesAsync(app); |
|||
|
|||
var result = new List<string> { Permission.Any }; |
|||
|
|||
foreach (var permission in Permissions.ForAppsNonSchema) |
|||
{ |
|||
if (permission.Length > Permissions.App.Length + 1) |
|||
{ |
|||
var trimmed = permission.Substring(Permissions.App.Length + 1); |
|||
|
|||
if (trimmed.Length > 0) |
|||
{ |
|||
result.Add(trimmed); |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (var permission in Permissions.ForAppsSchema) |
|||
{ |
|||
var trimmed = permission.Substring(Permissions.App.Length + 1); |
|||
|
|||
foreach (var schema in schemaNames) |
|||
{ |
|||
var replaced = trimmed.Replace("{name}", schema); |
|||
|
|||
result.Add(replaced); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private async Task<List<string>> GetSchemaNamesAsync(IAppEntity app) |
|||
{ |
|||
var schemas = await appProvider.GetSchemasAsync(app.Id); |
|||
|
|||
var schemaNames = new List<string>(); ; |
|||
|
|||
schemaNames.Add(Permission.Any); |
|||
schemaNames.AddRange(schemas.Select(x => x.Name)); |
|||
|
|||
return schemaNames; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Apps |
|||
{ |
|||
[EventType(nameof(AppRoleAdded))] |
|||
public sealed class AppRoleAdded : AppEvent |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Apps |
|||
{ |
|||
[EventType(nameof(AppRoleDeleted))] |
|||
public sealed class AppRoleDeleted : AppEvent |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
} |
|||
@ -1,19 +1,19 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Pipeline |
|||
namespace Squidex.Domain.Apps.Events.Apps |
|||
{ |
|||
public sealed class MustBeAppDeveloperAttribute : AppPermissionAttribute |
|||
[EventType(nameof(AppRoleUpdated))] |
|||
public sealed class AppRoleUpdated : AppEvent |
|||
{ |
|||
public MustBeAppDeveloperAttribute() |
|||
: base(AppPermission.Developer) |
|||
{ |
|||
} |
|||
public string Name { get; set; } |
|||
|
|||
public string[] Permissions { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Squidex.Infrastructure.Security |
|||
{ |
|||
public sealed class Permission : IComparable<Permission>, IEquatable<Permission> |
|||
{ |
|||
public const string Any = "*"; |
|||
|
|||
private static readonly char[] MainSeparators = { '.' }; |
|||
private static readonly char[] AlternativeSeparators = { '|' }; |
|||
private readonly string id; |
|||
private readonly Lazy<HashSet<string>[]> idParts; |
|||
|
|||
public string Id |
|||
{ |
|||
get { return id; } |
|||
} |
|||
|
|||
public Permission(string id) |
|||
{ |
|||
Guard.NotNullOrEmpty(id, nameof(id)); |
|||
|
|||
this.id = id; |
|||
|
|||
idParts = new Lazy<HashSet<string>[]>(() => id |
|||
.Split(MainSeparators, StringSplitOptions.RemoveEmptyEntries) |
|||
.Select(x => |
|||
{ |
|||
if (x == Any) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var alternatives = x.Split(AlternativeSeparators, StringSplitOptions.RemoveEmptyEntries); |
|||
|
|||
return new HashSet<string>(alternatives, StringComparer.OrdinalIgnoreCase); |
|||
}) |
|||
.ToArray()); |
|||
} |
|||
|
|||
public bool Allows(Permission permission) |
|||
{ |
|||
if (permission == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var lhs = idParts.Value; |
|||
var rhs = permission.idParts.Value; |
|||
|
|||
if (lhs.Length > rhs.Length) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < lhs.Length; i++) |
|||
{ |
|||
var l = lhs[i]; |
|||
var r = rhs[i]; |
|||
|
|||
if (l != null && (r == null || !l.Intersect(r).Any())) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public bool Includes(Permission permission) |
|||
{ |
|||
if (permission == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var lhs = idParts.Value; |
|||
var rhs = permission.idParts.Value; |
|||
|
|||
for (var i = 0; i < Math.Min(lhs.Length, rhs.Length); i++) |
|||
{ |
|||
var l = lhs[i]; |
|||
var r = rhs[i]; |
|||
|
|||
if (l != null && r != null && !l.Intersect(r).Any()) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public bool StartsWith(string id) |
|||
{ |
|||
return id.StartsWith(id, StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
return Equals(obj as Permission); |
|||
} |
|||
|
|||
public bool Equals(Permission other) |
|||
{ |
|||
return other != null && string.Equals(id, other.id, StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
return id.GetHashCode(); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return id; |
|||
} |
|||
|
|||
public int CompareTo(Permission other) |
|||
{ |
|||
return other == null ? -1 : string.Compare(id, other.id, StringComparison.Ordinal); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Squidex.Infrastructure.Security |
|||
{ |
|||
public sealed class PermissionSet : IReadOnlyCollection<Permission> |
|||
{ |
|||
public static readonly PermissionSet Empty = new PermissionSet(new string[0]); |
|||
|
|||
private readonly List<Permission> permissions; |
|||
private readonly Lazy<string> display; |
|||
|
|||
public int Count |
|||
{ |
|||
get { return permissions.Count; } |
|||
} |
|||
|
|||
public PermissionSet(params Permission[] permissions) |
|||
: this((IEnumerable<Permission>)permissions) |
|||
{ |
|||
} |
|||
|
|||
public PermissionSet(params string[] permissions) |
|||
: this(permissions?.Select(x => new Permission(x))) |
|||
{ |
|||
} |
|||
|
|||
public PermissionSet(IEnumerable<string> permissions) |
|||
: this(permissions?.Select(x => new Permission(x))) |
|||
{ |
|||
} |
|||
|
|||
public PermissionSet(IEnumerable<Permission> permissions) |
|||
{ |
|||
Guard.NotNull(permissions, nameof(permissions)); |
|||
|
|||
this.permissions = permissions.ToList(); |
|||
|
|||
display = new Lazy<string>(() => string.Join(";", this.permissions)); |
|||
} |
|||
|
|||
public bool Allows(Permission other) |
|||
{ |
|||
if (other == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return permissions.Any(x => x.Allows(other)); |
|||
} |
|||
|
|||
public bool Includes(Permission other) |
|||
{ |
|||
if (other == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return permissions.Any(x => x.Includes(other)); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return display.Value; |
|||
} |
|||
|
|||
public IEnumerable<string> ToIds() |
|||
{ |
|||
return permissions.Select(x => x.Id); |
|||
} |
|||
|
|||
public IEnumerator<Permission> GetEnumerator() |
|||
{ |
|||
return permissions.GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return permissions.GetEnumerator(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Shared.Identity |
|||
{ |
|||
public static class SquidexRoles |
|||
{ |
|||
public static readonly string Administrator = "ADMINISTRATOR"; |
|||
|
|||
public static readonly string AppOwner = "app:owner"; |
|||
|
|||
public static readonly string AppEditor = "app:editor"; |
|||
|
|||
public static readonly string AppReader = "app:reader"; |
|||
|
|||
public static readonly string AppDeveloper = "app:dev"; |
|||
} |
|||
} |
|||
@ -0,0 +1,180 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Security; |
|||
|
|||
namespace Squidex.Shared |
|||
{ |
|||
public static class Permissions |
|||
{ |
|||
private static readonly List<string> ForAppsNonSchemaList = new List<string>(); |
|||
private static readonly List<string> ForAppsSchemaList = new List<string>(); |
|||
|
|||
public static IReadOnlyList<string> ForAppsNonSchema |
|||
{ |
|||
get { return ForAppsNonSchemaList; } |
|||
} |
|||
|
|||
public static IReadOnlyList<string> ForAppsSchema |
|||
{ |
|||
get { return ForAppsSchemaList; } |
|||
} |
|||
|
|||
public const string All = "squidex.*"; |
|||
|
|||
public const string Admin = "squidex.admin.*"; |
|||
public const string AdminOrleans = "squidex.admin.orleans"; |
|||
|
|||
public const string AdminRestore = "squidex.admin.restore"; |
|||
public const string AdminRestoreRead = "squidex.admin.restore.read"; |
|||
public const string AdminRestoreCreate = "squidex.admin.restore.create"; |
|||
|
|||
public const string AdminEvents = "squidex.admin.events"; |
|||
public const string AdminEventsRead = "squidex.admin.events.read"; |
|||
public const string AdminEventsManage = "squidex.admin.events.manage"; |
|||
|
|||
public const string AdminUsers = "squidex.admin.users"; |
|||
public const string AdminUsersRead = "squidex.admin.users.read"; |
|||
public const string AdminUsersCreate = "squidex.admin.users.create"; |
|||
public const string AdminUsersUpdate = "squidex.admin.users.update"; |
|||
public const string AdminUsersUnlock = "squidex.admin.users.unlock"; |
|||
public const string AdminUsersLock = "squidex.admin.users.lock"; |
|||
|
|||
public const string App = "squidex.apps.{app}"; |
|||
public const string AppCommon = "squidex.apps.{app}.common"; |
|||
|
|||
public const string AppDelete = "squidex.apps.{app}.delete"; |
|||
|
|||
public const string AppClients = "squidex.apps.{app}.clients"; |
|||
public const string AppClientsRead = "squidex.apps.{app}.clients.read"; |
|||
public const string AppClientsCreate = "squidex.apps.{app}.clients.create"; |
|||
public const string AppClientsUpdate = "squidex.apps.{app}.clients.update"; |
|||
public const string AppClientsDelete = "squidex.apps.{app}.clients.delete"; |
|||
|
|||
public const string AppContributors = "squidex.apps.{app}.contributors"; |
|||
public const string AppContributorsRead = "squidex.apps.{app}.contributors.read"; |
|||
public const string AppContributorsAssign = "squidex.apps.{app}.contributors.assign"; |
|||
public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke"; |
|||
|
|||
public const string AppLanguages = "squidex.apps.{app}.languages"; |
|||
public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create"; |
|||
public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update"; |
|||
public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete"; |
|||
|
|||
public const string AppRoles = "squidex.apps.{app}.roles"; |
|||
public const string AppRolesRead = "squidex.apps.{app}.roles.read"; |
|||
public const string AppRolesCreate = "squidex.apps.{app}.roles.create"; |
|||
public const string AppRolesUpdate = "squidex.apps.{app}.roles.update"; |
|||
public const string AppRolesDelete = "squidex.apps.{app}.roles.delete"; |
|||
|
|||
public const string AppPatterns = "squidex.apps.{app}.patterns"; |
|||
public const string AppPatternsRead = "squidex.apps.{app}.patterns.read"; |
|||
public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create"; |
|||
public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update"; |
|||
public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete"; |
|||
|
|||
public const string AppBackups = "squidex.apps.{app}.backups"; |
|||
public const string AppBackupsRead = "squidex.apps.{app}.backups.read"; |
|||
public const string AppBackupsCreate = "squidex.apps.{app}.backups.create"; |
|||
public const string AppBackupsDelete = "squidex.apps.{app}.backups.delete"; |
|||
|
|||
public const string AppPlans = "squidex.apps.{app}.plans"; |
|||
public const string AppPlansRead = "squidex.apps.{app}.plans.read"; |
|||
public const string AppPlansChange = "squidex.apps.{app}.plans.change"; |
|||
|
|||
public const string AppAssets = "squidex.apps.{app}.assets"; |
|||
public const string AppAssetsRead = "squidex.apps.{app}.assets.read"; |
|||
public const string AppAssetsCreate = "squidex.apps.{app}.assets.create"; |
|||
public const string AppAssetsUpdate = "squidex.apps.{app}.assets.update"; |
|||
public const string AppAssetsDelete = "squidex.apps.{app}.assets.delete"; |
|||
|
|||
public const string AppRules = "squidex.apps.{app}.rules"; |
|||
public const string AppRulesRead = "squidex.apps.{app}.rules.read"; |
|||
public const string AppRulesCreate = "squidex.apps.{app}.rules.create"; |
|||
public const string AppRulesUpdate = "squidex.apps.{app}.rules.update"; |
|||
public const string AppRulesDisable = "squidex.apps.{app}.rules.disable"; |
|||
public const string AppRulesDelete = "squidex.apps.{app}.rules.delete"; |
|||
|
|||
public const string AppSchemas = "squidex.apps.{app}.schemas.{name}"; |
|||
public const string AppSchemasRead = "squidex.apps.{app}.schemas.{name}.read"; |
|||
public const string AppSchemasCreate = "squidex.apps.{app}.schemas.{name}.create"; |
|||
public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{name}.update"; |
|||
public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{name}.scripts"; |
|||
public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{name}.publish"; |
|||
public const string AppSchemasDelete = "squidex.apps.{app}.schemas.{name}.delete"; |
|||
|
|||
public const string AppContents = "squidex.apps.{app}.contents.{name}"; |
|||
public const string AppContentsRead = "squidex.apps.{app}.contents.{name}.read"; |
|||
public const string AppContentsGraphQL = "squidex.apps.{app}.contents.{name}.graphql"; |
|||
public const string AppContentsCreate = "squidex.apps.{app}.contents.{name}.create"; |
|||
public const string AppContentsUpdate = "squidex.apps.{app}.contents.{name}.update"; |
|||
public const string AppContentsDiscard = "squidex.apps.{app}.contents.{name}.discard"; |
|||
public const string AppContentsArchive = "squidex.apps.{app}.contents.{name}.archive"; |
|||
public const string AppContentsRestore = "squidex.apps.{app}.contents.{name}.restore"; |
|||
public const string AppContentsPublish = "squidex.apps.{app}.contents.{name}.publish"; |
|||
public const string AppContentsUnpublish = "squidex.apps.{app}.contents.{name}.unpublish"; |
|||
public const string AppContentsDelete = "squidex.apps.{app}.contents.{name}.delete"; |
|||
|
|||
public const string AppApi = "squidex.apps.{app}.api"; |
|||
|
|||
static Permissions() |
|||
{ |
|||
foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static)) |
|||
{ |
|||
if (field.IsLiteral && !field.IsInitOnly) |
|||
{ |
|||
var value = (string)field.GetValue(null); |
|||
|
|||
if (value.StartsWith(App, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
if (value.IndexOf("{name}", App.Length, StringComparison.OrdinalIgnoreCase) >= 0) |
|||
{ |
|||
ForAppsSchemaList.Add(value); |
|||
} |
|||
else |
|||
{ |
|||
ForAppsNonSchemaList.Add(value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static Permission ForApp(string id, string app = "*", string schema = "*") |
|||
{ |
|||
Guard.NotNull(id, nameof(id)); |
|||
|
|||
return new Permission(id.Replace("{app}", app ?? "*").Replace("{name}", schema ?? "*")); |
|||
} |
|||
|
|||
public static PermissionSet ToAppPermissions(this PermissionSet permissions, string app) |
|||
{ |
|||
var matching = permissions.Where(x => x.StartsWith($"squidex.apps.{app}")); |
|||
|
|||
return new PermissionSet(matching); |
|||
} |
|||
|
|||
public static string[] ToAppNames(this PermissionSet permissions) |
|||
{ |
|||
var matching = permissions.Where(x => x.StartsWith($"squidex.apps.")); |
|||
|
|||
var result = |
|||
matching |
|||
.Select(x => x.Id.Split('.')) |
|||
.Select(x => x[2]) |
|||
.Distinct() |
|||
.ToArray(); |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Areas.Api.Controllers.Apps.Models; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Pipeline; |
|||
using Squidex.Shared; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps |
|||
{ |
|||
/// <summary>
|
|||
/// Manages and configures apps.
|
|||
/// </summary>
|
|||
[ApiExplorerSettings(GroupName = nameof(Apps))] |
|||
public sealed class AppRolesController : ApiController |
|||
{ |
|||
private readonly RolePermissionsProvider permissionsProvider; |
|||
|
|||
public AppRolesController(ICommandBus commandBus, RolePermissionsProvider permissionsProvider) |
|||
: base(commandBus) |
|||
{ |
|||
this.permissionsProvider = permissionsProvider; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get app roles.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <returns>
|
|||
/// 200 => App roles returned.
|
|||
/// 404 => App not found.
|
|||
/// </returns>
|
|||
[HttpGet] |
|||
[Route("apps/{app}/roles/")] |
|||
[ProducesResponseType(typeof(RolesDto), 200)] |
|||
[ApiPermission(Permissions.AppRolesRead)] |
|||
[ApiCosts(0)] |
|||
public IActionResult GetRoles(string app) |
|||
{ |
|||
var response = RolesDto.FromApp(App); |
|||
|
|||
Response.Headers["ETag"] = App.Version.ToString(); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get app permissions.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <returns>
|
|||
/// 200 => App permissions returned.
|
|||
/// 404 => App not found.
|
|||
/// </returns>
|
|||
[HttpGet] |
|||
[Route("apps/{app}/roles/permissions")] |
|||
[ProducesResponseType(typeof(string[]), 200)] |
|||
[ApiPermission(Permissions.AppRolesRead)] |
|||
[ApiCosts(0)] |
|||
public async Task<IActionResult> GetPermissions(string app) |
|||
{ |
|||
var response = await permissionsProvider.GetPermissionsAsync(App); |
|||
|
|||
Response.Headers["ETag"] = string.Join(";", response).Sha256Base64(); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add role to app.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="request">Role object that needs to be added to the app.</param>
|
|||
/// <returns>
|
|||
/// 200 => User assigned to app.
|
|||
/// 400 => Role name already in use.
|
|||
/// 404 => App not found.
|
|||
/// </returns>
|
|||
[HttpPost] |
|||
[Route("apps/{app}/roles/")] |
|||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|||
[ApiPermission(Permissions.AppRolesCreate)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> PostRole(string app, [FromBody] AddRoleDto request) |
|||
{ |
|||
var command = request.ToCommand(); |
|||
var context = await CommandBus.PublishAsync(command); |
|||
|
|||
return NoContent(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Update an existing app role.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="role">The name of the role to be updated.</param>
|
|||
/// <param name="request">Role to be updated for the app.</param>
|
|||
/// <returns>
|
|||
/// 204 => Role updated.
|
|||
/// 400 => Role request not valid.
|
|||
/// 404 => Role or app not found.
|
|||
/// </returns>
|
|||
[HttpPut] |
|||
[Route("apps/{app}/roles/{role}/")] |
|||
[ApiPermission(Permissions.AppRolesUpdate)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> UpdateRole(string app, string role, [FromBody] UpdateRoleDto request) |
|||
{ |
|||
await CommandBus.PublishAsync(request.ToCommand(role)); |
|||
|
|||
return NoContent(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Remove role from app.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="role">The name of the role.</param>
|
|||
/// <returns>
|
|||
/// 204 => Role deleted.
|
|||
/// 400 => Role is in use by contributor or client or default role.
|
|||
/// 404 => Role or app not found.
|
|||
/// </returns>
|
|||
[HttpDelete] |
|||
[Route("apps/{app}/roles/{role}/")] |
|||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|||
[ApiPermission(Permissions.AppRolesDelete)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> DeleteRole(string app, string role) |
|||
{ |
|||
await CommandBus.PublishAsync(new DeleteRole { Name = role }); |
|||
|
|||
return NoContent(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps.Models |
|||
{ |
|||
public sealed class AddRoleDto |
|||
{ |
|||
/// <summary>
|
|||
/// The role name.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Name { get; set; } |
|||
|
|||
public AddRole ToCommand() |
|||
{ |
|||
return new AddRole { Name = Name }; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps.Models |
|||
{ |
|||
public sealed class RoleDto |
|||
{ |
|||
/// <summary>
|
|||
/// The role name.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Name { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The number of clients with this role.
|
|||
/// </summary>
|
|||
public int NumClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The number of contributors with this role.
|
|||
/// </summary>
|
|||
public int NumContributors { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Associated list of permissions.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string[] Permissions { get; set; } |
|||
|
|||
public static RoleDto FromRole(Role role, IAppEntity app) |
|||
{ |
|||
var permissions = role.Permissions.WithoutApp(app.Name); |
|||
|
|||
return new RoleDto |
|||
{ |
|||
Name = role.Name, |
|||
NumClients = app.Clients.Count(x => string.Equals(x.Value.Role, role.Name, StringComparison.OrdinalIgnoreCase)), |
|||
NumContributors = app.Contributors.Count(x => string.Equals(x.Value, role.Name, StringComparison.OrdinalIgnoreCase)), |
|||
Permissions = permissions.ToIds().ToArray() |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps.Models |
|||
{ |
|||
public sealed class RolesDto |
|||
{ |
|||
/// <summary>
|
|||
/// The app roles.
|
|||
/// </summary>
|
|||
[Required] |
|||
public RoleDto[] Roles { get; set; } |
|||
|
|||
public static RolesDto FromApp(IAppEntity app) |
|||
{ |
|||
var roles = app.Roles.Values.Select(x => RoleDto.FromRole(x, app)).ToArray(); |
|||
|
|||
return new RolesDto { Roles = roles }; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps.Models |
|||
{ |
|||
public sealed class UpdateRoleDto |
|||
{ |
|||
/// <summary>
|
|||
/// Associated list of permissions.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string[] Permissions { get; set; } |
|||
|
|||
public UpdateRole ToCommand(string name) |
|||
{ |
|||
return new UpdateRole { Name = name, Permissions = Permissions }; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue