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