mirror of https://github.com/Squidex/squidex.git
56 changed files with 824 additions and 463 deletions
@ -0,0 +1,6 @@ |
|||||
|
namespace Squidex.Domain.Apps.Core |
||||
|
{ |
||||
|
public sealed class DefaultPermissions |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,119 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core |
||||
|
{ |
||||
|
public sealed class Permissions |
||||
|
{ |
||||
|
public const string ClaimType = "Permission"; |
||||
|
|
||||
|
public const string All = "squidex.*"; |
||||
|
|
||||
|
public const string Admin = "squidex.admin*"; |
||||
|
|
||||
|
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 AppDelete = "squidex.apps.{app}.delete"; |
||||
|
public const string AppCommon = "squidex.apps.{app}.common"; |
||||
|
|
||||
|
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 AppLanguagesRead = "squidex.apps.{app}.languages.read"; |
||||
|
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 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 static Permission ForApp(string id, string app = "*") |
||||
|
{ |
||||
|
Guard.NotNull(id, nameof(id)); |
||||
|
|
||||
|
return new Permission(id.Replace("{app}", app ?? "*")); |
||||
|
} |
||||
|
|
||||
|
public static Permission ForSchema(string id, string app = "*", string schema = "*") |
||||
|
{ |
||||
|
Guard.NotNull(id, nameof(id)); |
||||
|
|
||||
|
return new Permission(id.Replace("{app}", app ?? "*").Replace("{name}", schema ?? "*")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Security |
||||
|
{ |
||||
|
public sealed class Permission : IComparable<Permission>, IEquatable<Permission> |
||||
|
{ |
||||
|
private const string Any = "*"; |
||||
|
private static readonly char[] Separators = { '.' }; |
||||
|
private readonly string description; |
||||
|
private readonly string id; |
||||
|
private readonly string[] idParts; |
||||
|
|
||||
|
public string Id |
||||
|
{ |
||||
|
get { return id; } |
||||
|
} |
||||
|
|
||||
|
public string Description |
||||
|
{ |
||||
|
get { return description; } |
||||
|
} |
||||
|
|
||||
|
public Permission(string id, string description = null) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(id, nameof(id)); |
||||
|
|
||||
|
this.description = description; |
||||
|
|
||||
|
this.id = id; |
||||
|
this.idParts = id.Split(Separators, StringSplitOptions.RemoveEmptyEntries); |
||||
|
} |
||||
|
|
||||
|
public bool GivesPermissionTo(Permission permission) |
||||
|
{ |
||||
|
if (permission == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (idParts.Length > permission.idParts.Length) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < idParts.Length; i++) |
||||
|
{ |
||||
|
var lhs = idParts[i]; |
||||
|
var rhs = permission.idParts[i]; |
||||
|
|
||||
|
if (!string.Equals(lhs, Any, StringComparison.OrdinalIgnoreCase) && |
||||
|
!string.Equals(lhs, rhs, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
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,67 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
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(); |
||||
|
|
||||
|
private readonly List<Permission> permissions; |
||||
|
|
||||
|
public int Count |
||||
|
{ |
||||
|
get { return permissions.Count; } |
||||
|
} |
||||
|
|
||||
|
public PermissionSet(IEnumerable<Permission> permissions) |
||||
|
{ |
||||
|
Guard.NotNull(permissions, nameof(permissions)); |
||||
|
|
||||
|
this.permissions = permissions.ToList(); |
||||
|
} |
||||
|
|
||||
|
public PermissionSet(params Permission[] permissions) |
||||
|
{ |
||||
|
Guard.NotNull(permissions, nameof(permissions)); |
||||
|
|
||||
|
this.permissions = permissions.ToList(); |
||||
|
} |
||||
|
|
||||
|
public bool GivesPermissionTo(Permission other) |
||||
|
{ |
||||
|
if (other == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
foreach (var permission in permissions) |
||||
|
{ |
||||
|
if (permission.GivesPermissionTo(other)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public IEnumerator<Permission> GetEnumerator() |
||||
|
{ |
||||
|
return permissions.GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
return permissions.GetEnumerator(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,20 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using IdentityServer4.AccessTokenValidation; |
|
||||
using Microsoft.AspNetCore.Authorization; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public class ApiAuthorizeAttribute : AuthorizeAttribute |
|
||||
{ |
|
||||
public ApiAuthorizeAttribute() |
|
||||
{ |
|
||||
AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,54 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using IdentityServer4.AccessTokenValidation; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.Filters; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
|
namespace Squidex.Pipeline |
||||
|
{ |
||||
|
public sealed class ApiPermissionAttribute : AuthorizeAttribute, IAsyncActionFilter |
||||
|
{ |
||||
|
private readonly string permissionId; |
||||
|
|
||||
|
public ApiPermissionAttribute(string id = null) |
||||
|
{ |
||||
|
AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme; |
||||
|
|
||||
|
permissionId = id; |
||||
|
} |
||||
|
|
||||
|
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) |
||||
|
{ |
||||
|
if (permissionId != null) |
||||
|
{ |
||||
|
var id = permissionId; |
||||
|
|
||||
|
foreach (var routeParam in context.RouteData.Values) |
||||
|
{ |
||||
|
id = id.Replace($"{{{routeParam.Key}}}", routeParam.Value?.ToString()); |
||||
|
} |
||||
|
|
||||
|
var set = new PermissionSet( |
||||
|
context.HttpContext.User.FindAll("Permission") |
||||
|
.Select(x => x.Value) |
||||
|
.Select(x => new Permission(x))); |
||||
|
|
||||
|
if (!set.GivesPermissionTo(new Permission(id))) |
||||
|
{ |
||||
|
// context.Result = new StatusCodeResult(403);
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return next(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class AppApiAttribute : ServiceFilterAttribute |
|
||||
{ |
|
||||
public AppApiAttribute() |
|
||||
: base(typeof(AppApiFilter)) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,55 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// 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 Microsoft.AspNetCore.Mvc.Filters; |
|
||||
using Squidex.Domain.Apps.Entities; |
|
||||
using Squidex.Domain.Apps.Entities.Apps; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class AppApiFilter : IAsyncActionFilter |
|
||||
{ |
|
||||
private readonly IAppProvider appProvider; |
|
||||
|
|
||||
public class AppFeature : IAppFeature |
|
||||
{ |
|
||||
public IAppEntity App { get; } |
|
||||
|
|
||||
public AppFeature(IAppEntity app) |
|
||||
{ |
|
||||
App = app; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public AppApiFilter(IAppProvider appProvider) |
|
||||
{ |
|
||||
this.appProvider = appProvider; |
|
||||
} |
|
||||
|
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) |
|
||||
{ |
|
||||
var appName = context.RouteData.Values["app"]?.ToString(); |
|
||||
|
|
||||
if (!string.IsNullOrWhiteSpace(appName)) |
|
||||
{ |
|
||||
var app = await appProvider.GetAppAsync(appName); |
|
||||
|
|
||||
if (app == null) |
|
||||
{ |
|
||||
context.Result = new NotFoundResult(); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
context.HttpContext.Features.Set<IAppFeature>(new AppFeature(app)); |
|
||||
} |
|
||||
|
|
||||
await next(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,106 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Security.Claims; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Microsoft.AspNetCore.Mvc.Filters; |
|
||||
using Squidex.Domain.Apps.Core.Apps; |
|
||||
using Squidex.Domain.Apps.Entities.Apps; |
|
||||
using Squidex.Infrastructure.Security; |
|
||||
using Squidex.Shared.Identity; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public abstract class AppPermissionAttribute : ActionFilterAttribute |
|
||||
{ |
|
||||
private readonly AppPermission requestedPermission; |
|
||||
|
|
||||
protected AppPermissionAttribute(AppPermission requestedPermission) |
|
||||
{ |
|
||||
this.requestedPermission = requestedPermission; |
|
||||
} |
|
||||
|
|
||||
public override void OnActionExecuting(ActionExecutingContext context) |
|
||||
{ |
|
||||
var app = context.HttpContext.Features.Get<IAppFeature>()?.App; |
|
||||
|
|
||||
if (app != null) |
|
||||
{ |
|
||||
var user = context.HttpContext.User; |
|
||||
|
|
||||
var permission = |
|
||||
FindByOpenIdSubject(app, user) ?? |
|
||||
FindByOpenIdClient(app, user); |
|
||||
|
|
||||
if (permission == null) |
|
||||
{ |
|
||||
context.Result = new NotFoundResult(); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (permission.Value > requestedPermission) |
|
||||
{ |
|
||||
context.Result = new StatusCodeResult(403); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var defaultIdentity = context.HttpContext.User.Identities.First(); |
|
||||
|
|
||||
var additionalRoles = new List<string> |
|
||||
{ |
|
||||
SquidexRoles.AppReader |
|
||||
}; |
|
||||
|
|
||||
if (permission.Value <= AppPermission.Editor) |
|
||||
{ |
|
||||
additionalRoles.Add(SquidexRoles.AppEditor); |
|
||||
} |
|
||||
|
|
||||
if (permission.Value <= AppPermission.Developer) |
|
||||
{ |
|
||||
additionalRoles.Add(SquidexRoles.AppDeveloper); |
|
||||
} |
|
||||
|
|
||||
if (permission.Value <= AppPermission.Owner) |
|
||||
{ |
|
||||
additionalRoles.Add(SquidexRoles.AppOwner); |
|
||||
} |
|
||||
|
|
||||
foreach (var role in additionalRoles) |
|
||||
{ |
|
||||
defaultIdentity.AddClaim(new Claim(defaultIdentity.RoleClaimType, role)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static AppPermission? FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user) |
|
||||
{ |
|
||||
var clientId = user.GetClientId(); |
|
||||
|
|
||||
if (clientId != null && app.Clients.TryGetValue(clientId, out var client)) |
|
||||
{ |
|
||||
return client.Permission.ToAppPermission(); |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
private static AppPermission? FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user) |
|
||||
{ |
|
||||
var subjectId = user.FindFirst(OpenIdClaims.Subject)?.Value; |
|
||||
|
|
||||
if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var permission)) |
|
||||
{ |
|
||||
return permission.ToAppPermission(); |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,102 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.Filters; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
|
namespace Squidex.Pipeline |
||||
|
{ |
||||
|
public sealed class AppResolverFilter : IAsyncActionFilter |
||||
|
{ |
||||
|
private readonly IAppProvider appProvider; |
||||
|
|
||||
|
public class AppFeature : IAppFeature |
||||
|
{ |
||||
|
public IAppEntity App { get; } |
||||
|
|
||||
|
public AppFeature(IAppEntity app) |
||||
|
{ |
||||
|
App = app; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public AppResolverFilter(IAppProvider appProvider) |
||||
|
{ |
||||
|
this.appProvider = appProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) |
||||
|
{ |
||||
|
var appName = context.RouteData.Values["app"]?.ToString(); |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(appName)) |
||||
|
{ |
||||
|
var app = await appProvider.GetAppAsync(appName); |
||||
|
|
||||
|
if (app == null) |
||||
|
{ |
||||
|
context.Result = new NotFoundResult(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var user = context.HttpContext.User; |
||||
|
|
||||
|
var permissions = |
||||
|
FindByOpenIdSubject(app, user) ?? |
||||
|
FindByOpenIdClient(app, user); |
||||
|
|
||||
|
if (permissions.Count == 0) |
||||
|
{ |
||||
|
context.Result = new NotFoundResult(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var identity = user.Identities.First(); |
||||
|
|
||||
|
foreach (var permission in permissions) |
||||
|
{ |
||||
|
identity.AddClaim(new Claim("Permission", permission.Id)); |
||||
|
} |
||||
|
|
||||
|
context.HttpContext.Features.Set<IAppFeature>(new AppFeature(app)); |
||||
|
} |
||||
|
|
||||
|
await next(); |
||||
|
} |
||||
|
|
||||
|
private static PermissionSet FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user) |
||||
|
{ |
||||
|
var clientId = user.GetClientId(); |
||||
|
|
||||
|
if (clientId != null && app.Clients.TryGetValue(clientId, out var client)) |
||||
|
{ |
||||
|
return client.Permission.ToPermissions(app.Name); |
||||
|
} |
||||
|
|
||||
|
return PermissionSet.Empty; |
||||
|
} |
||||
|
|
||||
|
private static PermissionSet FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user) |
||||
|
{ |
||||
|
var subjectId = user.FindFirst(OpenIdClaims.Subject)?.Value; |
||||
|
|
||||
|
if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var permission)) |
||||
|
{ |
||||
|
return permission.ToPermissions(app.Name); |
||||
|
} |
||||
|
|
||||
|
return PermissionSet.Empty; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Shared.Identity; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class MustBeAdministratorAttribute : ApiAuthorizeAttribute |
|
||||
{ |
|
||||
public MustBeAdministratorAttribute() |
|
||||
{ |
|
||||
Roles = SquidexRoles.Administrator; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Domain.Apps.Core.Apps; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class MustBeAppDeveloperAttribute : AppPermissionAttribute |
|
||||
{ |
|
||||
public MustBeAppDeveloperAttribute() |
|
||||
: base(AppPermission.Developer) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Domain.Apps.Core.Apps; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class MustBeAppEditorAttribute : AppPermissionAttribute |
|
||||
{ |
|
||||
public MustBeAppEditorAttribute() |
|
||||
: base(AppPermission.Editor) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Domain.Apps.Core.Apps; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class MustBeAppOwnerAttribute : AppPermissionAttribute |
|
||||
{ |
|
||||
public MustBeAppOwnerAttribute() |
|
||||
: base(AppPermission.Owner) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Domain.Apps.Core.Apps; |
|
||||
|
|
||||
namespace Squidex.Pipeline |
|
||||
{ |
|
||||
public sealed class MustBeAppReaderAttribute : AppPermissionAttribute |
|
||||
{ |
|
||||
public MustBeAppReaderAttribute() |
|
||||
: base(AppPermission.Reader) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,65 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Security |
||||
|
{ |
||||
|
public sealed class PermissionSetTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_provide_collection_features() |
||||
|
{ |
||||
|
var source = new List<Permission> |
||||
|
{ |
||||
|
new Permission("c"), |
||||
|
new Permission("b"), |
||||
|
new Permission("a") |
||||
|
}; |
||||
|
|
||||
|
var sut = new PermissionSet(source); |
||||
|
|
||||
|
Assert.Equal(sut.ToList(), source); |
||||
|
Assert.Equal(((IEnumerable)sut).OfType<Permission>().ToList(), source); |
||||
|
|
||||
|
Assert.Equal(3, source.Count); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_true_if_any_permission_gives_permission_to_request() |
||||
|
{ |
||||
|
var sut = new PermissionSet( |
||||
|
new Permission("app.contents"), |
||||
|
new Permission("app.assets")); |
||||
|
|
||||
|
Assert.True(sut.GivesPermissionTo(new Permission("app.contents"))); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_false_if_none_permission_gives_permission_to_request() |
||||
|
{ |
||||
|
var sut = new PermissionSet( |
||||
|
new Permission("app.contents"), |
||||
|
new Permission("app.assets")); |
||||
|
|
||||
|
Assert.False(sut.GivesPermissionTo(new Permission("app.schemas"))); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_false_if_permission_to_request_is_null() |
||||
|
{ |
||||
|
var sut = new PermissionSet( |
||||
|
new Permission("app.contents"), |
||||
|
new Permission("app.assets")); |
||||
|
|
||||
|
Assert.False(sut.GivesPermissionTo(null)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,138 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Security |
||||
|
{ |
||||
|
public class PermissionTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_generate_permissions() |
||||
|
{ |
||||
|
var sut = new Permission("app.contents", "App Contents"); |
||||
|
|
||||
|
Assert.Equal("app.contents", sut.ToString()); |
||||
|
Assert.Equal("app.contents", sut.Id); |
||||
|
Assert.Equal("App Contents", sut.Description); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_true_if_given_and_requested_permission_have_wildcards() |
||||
|
{ |
||||
|
var g = new Permission("app.*"); |
||||
|
var r = new Permission("app.*"); |
||||
|
|
||||
|
Assert.True(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_true_if_given_permission_equals_requested_permission() |
||||
|
{ |
||||
|
var g = new Permission("app.contents"); |
||||
|
var r = new Permission("app.contents"); |
||||
|
|
||||
|
Assert.True(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_true_if_given_permission_is_parent_of_requested_permission() |
||||
|
{ |
||||
|
var g = new Permission("app"); |
||||
|
var r = new Permission("app.contents"); |
||||
|
|
||||
|
Assert.True(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_true_if_given_permission_has_wildcard_for_requested_permission() |
||||
|
{ |
||||
|
var g = new Permission("app.*"); |
||||
|
var r = new Permission("app.contents"); |
||||
|
|
||||
|
Assert.True(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_false_if_given_permission_not_equals_requested_permission() |
||||
|
{ |
||||
|
var g = new Permission("app.contents"); |
||||
|
var r = new Permission("app.assets"); |
||||
|
|
||||
|
Assert.False(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_false_if_given_permission_is_child_of_requested_permission() |
||||
|
{ |
||||
|
var g = new Permission("app.contents"); |
||||
|
var r = new Permission("app"); |
||||
|
|
||||
|
Assert.False(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_false_if_given_permission_has_no_wildcard_but_requested_has() |
||||
|
{ |
||||
|
var g = new Permission("app.contents"); |
||||
|
var r = new Permission("app.*"); |
||||
|
|
||||
|
Assert.False(g.GivesPermissionTo(r)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_false_if_given_requested_permission_is_null() |
||||
|
{ |
||||
|
var g = new Permission("app.contents"); |
||||
|
|
||||
|
Assert.False(g.GivesPermissionTo(null)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_correct_object_equal_comparisons() |
||||
|
{ |
||||
|
object permission1a = new Permission("app.1"); |
||||
|
object permission1b = new Permission("app.1"); |
||||
|
object permission2a = new Permission("app.2"); |
||||
|
|
||||
|
Assert.True(permission1a.Equals(permission1b)); |
||||
|
|
||||
|
Assert.False(permission1a.Equals(permission2a)); |
||||
|
Assert.False(permission1b.Equals(permission2a)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_provide_correct_hash_codes() |
||||
|
{ |
||||
|
var permission1a = new Permission("app.1"); |
||||
|
var permission1b = new Permission("app.1"); |
||||
|
var permission2a = new Permission("app.2"); |
||||
|
|
||||
|
Assert.Equal(permission1a.GetHashCode(), permission1b.GetHashCode()); |
||||
|
|
||||
|
Assert.NotEqual(permission1a.GetHashCode(), permission2a.GetHashCode()); |
||||
|
Assert.NotEqual(permission1b.GetHashCode(), permission2a.GetHashCode()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_sort_by_name() |
||||
|
{ |
||||
|
var source = new List<Permission> |
||||
|
{ |
||||
|
new Permission("c"), |
||||
|
new Permission("b"), |
||||
|
new Permission("a") |
||||
|
}; |
||||
|
|
||||
|
var sorted = source.OrderBy(x => x).ToList(); |
||||
|
|
||||
|
Assert.Equal(new List<Permission> { source[2], source[1], source[0] }, sorted); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue