mirror of https://github.com/Squidex/squidex.git
22 changed files with 560 additions and 18 deletions
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 schemas = await appProvider.GetSchemasAsync(app.Id); |
||||
|
var schemaNames = schemas.Select(x => x.Name).ToList(); |
||||
|
|
||||
|
schemaNames.Insert(0, Permission.Any); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.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 async Task<IActionResult> GetRoles(string app) |
||||
|
{ |
||||
|
var response = RolesDto.FromApp(App, await permissionsProvider.GetPermissionsAsync(App)); |
||||
|
|
||||
|
Response.Headers["ETag"] = App.Version.ToString(); |
||||
|
|
||||
|
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,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Core.Apps; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Apps.Models |
||||
|
{ |
||||
|
public sealed class RoleDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The role name.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Associated list of permissions.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public string[] Permissions { get; set; } |
||||
|
|
||||
|
public static RoleDto FromRole(Role role) |
||||
|
{ |
||||
|
return new RoleDto { Name = role.Name, Permissions = role.Permissions.Select(x => x.Id).ToArray() }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
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 roles.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public RoleDto[] Roles { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Suggested permissions.
|
||||
|
/// </summary>
|
||||
|
public string[] AllPermissions { get; set; } |
||||
|
|
||||
|
public static RolesDto FromApp(IAppEntity app, List<string> permissions) |
||||
|
{ |
||||
|
var roles = app.Roles.Values.Select(RoleDto.FromRole).ToArray(); |
||||
|
|
||||
|
return new RolesDto { Roles = roles, AllPermissions = permissions.ToList() }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public class RoleExtensionsRests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_add_common_permission() |
||||
|
{ |
||||
|
var source = new string[0]; |
||||
|
var result = source.Prefix("my-app"); |
||||
|
|
||||
|
Assert.Equal(new[] { "squidex.apps.my-app.common" }, 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]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable xUnit2017 // Do not use Contains() to check if a value exists in a collection
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public class RolePermissionsProviderTests |
||||
|
{ |
||||
|
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); |
||||
|
private readonly IAppEntity app = A.Fake<IAppEntity>(); |
||||
|
private readonly RolePermissionsProvider sut; |
||||
|
|
||||
|
public RolePermissionsProviderTests() |
||||
|
{ |
||||
|
A.CallTo(() => app.Name).Returns("my-app"); |
||||
|
|
||||
|
sut = new RolePermissionsProvider(appProvider); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_provide_all_permissions() |
||||
|
{ |
||||
|
A.CallTo(() => appProvider.GetSchemasAsync(A<Guid>.Ignored)) |
||||
|
.Returns(new List<ISchemaEntity> |
||||
|
{ |
||||
|
CreateSchema("schema1"), |
||||
|
CreateSchema("schema2") |
||||
|
}); |
||||
|
|
||||
|
var result = await sut.GetPermissionsAsync(app); |
||||
|
|
||||
|
Assert.True(result.Contains("*")); |
||||
|
Assert.True(result.Contains("clients.read")); |
||||
|
Assert.True(result.Contains("schemas.*.read")); |
||||
|
Assert.True(result.Contains("schemas.schema1.read")); |
||||
|
Assert.True(result.Contains("schemas.schema2.read")); |
||||
|
} |
||||
|
|
||||
|
private ISchemaEntity CreateSchema(string name) |
||||
|
{ |
||||
|
var schema = A.Fake<ISchemaEntity>(); |
||||
|
|
||||
|
A.CallTo(() => schema.Name).Returns(name); |
||||
|
|
||||
|
return schema; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue