Browse Source

New permissions in API.

pull/332/head
Sebastian Stehle 8 years ago
parent
commit
6659e03063
  1. 47
      src/Squidex.Domain.Apps.Core.Model/Apps/RoleExtension.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs
  3. 6
      src/Squidex.Domain.Apps.Core.Model/DefaultPermissions.cs
  4. 119
      src/Squidex.Domain.Apps.Core.Model/Permissions.cs
  5. 92
      src/Squidex.Infrastructure/Security/Permission.cs
  6. 67
      src/Squidex.Infrastructure/Security/PermissionSet.cs
  7. 1
      src/Squidex/Areas/Api/Controllers/ApiController.cs
  8. 9
      src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
  9. 8
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  10. 12
      src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
  11. 9
      src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs
  12. 8
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  13. 2
      src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  14. 19
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  15. 2
      src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs
  16. 8
      src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs
  17. 10
      src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs
  18. 9
      src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs
  19. 3
      src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs
  20. 34
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  21. 4
      src/Squidex/Areas/Api/Controllers/Docs/DocsController.cs
  22. 14
      src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
  23. 6
      src/Squidex/Areas/Api/Controllers/History/HistoryController.cs
  24. 4
      src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs
  25. 8
      src/Squidex/Areas/Api/Controllers/Ping/PingController.cs
  26. 8
      src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs
  27. 13
      src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  28. 20
      src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs
  29. 22
      src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  30. 9
      src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
  31. 9
      src/Squidex/Areas/Api/Controllers/UI/UIController.cs
  32. 18
      src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs
  33. 5
      src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
  34. 2
      src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs
  35. 2
      src/Squidex/Areas/IdentityServer/Controllers/Error/ErrorController.cs
  36. 2
      src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs
  37. 3
      src/Squidex/Config/Web/WebServices.cs
  38. 20
      src/Squidex/Pipeline/ApiAuthorizeAttribute.cs
  39. 54
      src/Squidex/Pipeline/ApiPermissionAttribute.cs
  40. 19
      src/Squidex/Pipeline/AppApiAttribute.cs
  41. 55
      src/Squidex/Pipeline/AppApiFilter.cs
  42. 106
      src/Squidex/Pipeline/AppPermissionAttribute.cs
  43. 102
      src/Squidex/Pipeline/AppResolverFilter.cs
  44. 19
      src/Squidex/Pipeline/MustBeAdministratorAttribute.cs
  45. 19
      src/Squidex/Pipeline/MustBeAppDeveloperAttribute.cs
  46. 19
      src/Squidex/Pipeline/MustBeAppEditorAttribute.cs
  47. 19
      src/Squidex/Pipeline/MustBeAppOwnerAttribute.cs
  48. 19
      src/Squidex/Pipeline/MustBeAppReaderAttribute.cs
  49. 8
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleExtensionTests.cs
  50. 1
      tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentsGrainTests.cs
  51. 2
      tests/Squidex.Infrastructure.Tests/RefTokenTests.cs
  52. 65
      tests/Squidex.Infrastructure.Tests/Security/PermissionSetTests.cs
  53. 138
      tests/Squidex.Infrastructure.Tests/Security/PermissionTests.cs
  54. 2
      tests/Squidex.Tests/Pipeline/ApiCostsFilterTests.cs
  55. 2
      tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs
  56. 2
      tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs

47
src/Squidex.Domain.Apps.Core.Model/Apps/RoleExtension.cs

@ -5,25 +5,60 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Core.Apps
{
public static class RoleExtension
{
public static AppPermission ToAppPermission(this AppClientPermission clientPermission)
public static PermissionSet ToPermissions(this AppClientPermission clientPermission, string app)
{
Guard.Enum(clientPermission, nameof(clientPermission));
Guard.NotNullOrEmpty(app, nameof(app));
return (AppPermission)Enum.Parse(typeof(AppPermission), clientPermission.ToString());
switch (clientPermission)
{
case AppClientPermission.Developer:
return ToPermissions(AppContributorPermission.Developer, app);
case AppClientPermission.Editor:
return ToPermissions(AppContributorPermission.Editor, app);
case AppClientPermission.Reader:
return new PermissionSet(
Permissions.ForApp(Permissions.AppCommon, app),
Permissions.ForSchema(Permissions.AppContentsRead, app, "*"),
Permissions.ForSchema(Permissions.AppContentsGraphQL, app, "*"));
}
return PermissionSet.Empty;
}
public static AppPermission ToAppPermission(this AppContributorPermission contributorPermission)
public static PermissionSet ToPermissions(this AppContributorPermission contributorPermission, string app)
{
Guard.Enum(contributorPermission, nameof(contributorPermission));
Guard.NotNullOrEmpty(app, nameof(app));
switch (contributorPermission)
{
case AppContributorPermission.Owner:
return new PermissionSet(
Permissions.ForApp(Permissions.App, app));
case AppContributorPermission.Developer:
return new PermissionSet(
Permissions.ForApp(Permissions.AppCommon, app),
Permissions.ForApp(Permissions.AppContents, app),
Permissions.ForApp(Permissions.AppAssets, app),
Permissions.ForApp(Permissions.AppPatterns, app),
Permissions.ForApp(Permissions.AppRules, app),
Permissions.ForApp(Permissions.AppSchemas, app));
case AppContributorPermission.Editor:
return new PermissionSet(
Permissions.ForApp(Permissions.AppCommon, app),
Permissions.ForApp(Permissions.AppContents, app),
Permissions.ForApp(Permissions.AppAssets, app));
}
return (AppPermission)Enum.Parse(typeof(AppPermission), contributorPermission.ToString());
return PermissionSet.Empty;
}
}
}
}

2
src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs

@ -24,8 +24,10 @@ namespace Squidex.Domain.Apps.Core.Comments
public Comment(Guid id, Instant time, RefToken user, string text)
{
Id = id;
Time = time;
Text = text;
User = user;
}
}

6
src/Squidex.Domain.Apps.Core.Model/DefaultPermissions.cs

@ -0,0 +1,6 @@
namespace Squidex.Domain.Apps.Core
{
public sealed class DefaultPermissions
{
}
}

119
src/Squidex.Domain.Apps.Core.Model/Permissions.cs

@ -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 ?? "*"));
}
}
}

92
src/Squidex.Infrastructure/Security/Permission.cs

@ -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);
}
}
}

67
src/Squidex.Infrastructure/Security/PermissionSet.cs

@ -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
src/Squidex/Areas/Api/Controllers/ApiController.cs

@ -16,6 +16,7 @@ using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers
{
[Area("Api")]
[ApiExceptionFilter]
[ApiModelValidation(false)]
public abstract class ApiController : Controller
{

9
src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs

@ -9,6 +9,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -18,10 +19,6 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <summary>
/// Manages and configures apps.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppEditor]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppClientsController : ApiController
{
@ -44,6 +41,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet]
[Route("apps/{app}/clients/")]
[ProducesResponseType(typeof(ClientDto[]), 200)]
[ApiPermission(Permissions.AppClientsRead)]
[ApiCosts(0)]
public IActionResult GetClients(string app)
{
@ -70,6 +68,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpPost]
[Route("apps/{app}/clients/")]
[ProducesResponseType(typeof(ClientDto), 201)]
[ApiPermission(Permissions.AppClientsCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostClient(string app, [FromBody] CreateAppClientDto request)
{
@ -98,6 +97,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </remarks>
[HttpPut]
[Route("apps/{app}/clients/{clientId}/")]
[ApiPermission(Permissions.AppClientsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutClient(string app, string clientId, [FromBody] UpdateAppClientDto request)
{
@ -120,6 +120,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </remarks>
[HttpDelete]
[Route("apps/{app}/clients/{clientId}/")]
[ApiPermission(Permissions.AppClientsDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteClient(string app, string clientId)
{

8
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure.Commands;
@ -18,10 +19,6 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <summary>
/// Manages and configures apps.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppOwner]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppContributorsController : ApiController
{
@ -44,6 +41,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet]
[Route("apps/{app}/contributors/")]
[ProducesResponseType(typeof(ContributorsDto), 200)]
[ApiPermission(Permissions.AppContributorsRead)]
[ApiCosts(0)]
public IActionResult GetContributors(string app)
{
@ -68,6 +66,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[Route("apps/{app}/contributors/")]
[ProducesResponseType(typeof(ContributorAssignedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppContributorsAssign)]
[ApiCosts(1)]
public async Task<IActionResult> PostContributor(string app, [FromBody] AssignAppContributorDto request)
{
@ -93,6 +92,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpDelete]
[Route("apps/{app}/contributors/{id}/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppContributorsRevoke)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteContributor(string app, string id)
{

12
src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs

@ -9,6 +9,7 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
@ -19,9 +20,6 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <summary>
/// Manages and configures apps.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppLanguagesController : ApiController
{
@ -38,10 +36,10 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// 200 => Language configuration returned.
/// 404 => App not found.
/// </returns>
[MustBeAppReader]
[HttpGet]
[Route("apps/{app}/languages/")]
[ProducesResponseType(typeof(AppLanguageDto[]), 200)]
[ApiPermission(Permissions.AppLanguagesRead)]
[ApiCosts(0)]
public IActionResult GetLanguages(string app)
{
@ -62,11 +60,11 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// 400 => Language request not valid.
/// 404 => App not found.
/// </returns>
[MustBeAppEditor]
[HttpPost]
[Route("apps/{app}/languages/")]
[ProducesResponseType(typeof(AppLanguageDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppLanguagesCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostLanguage(string app, [FromBody] AddAppLanguageDto request)
{
@ -90,9 +88,9 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// 400 => Language request not valid.
/// 404 => Language or app not found.
/// </returns>
[MustBeAppEditor]
[HttpPut]
[Route("apps/{app}/languages/{language}/")]
[ApiPermission(Permissions.AppLanguagesUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> Update(string app, string language, [FromBody] UpdateAppLanguageDto request)
{
@ -110,9 +108,9 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// 204 => Language deleted.
/// 404 => Language or app not found.
/// </returns>
[MustBeAppEditor]
[HttpDelete]
[Route("apps/{app}/languages/{language}/")]
[ApiPermission(Permissions.AppLanguagesDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteLanguage(string app, string language)
{

9
src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs

@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -19,10 +20,6 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <summary>
/// Manages and configures app patterns.
/// </summary>
[ApiAuthorize]
[MustBeAppDeveloper]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppPatternsController : ApiController
{
@ -45,6 +42,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet]
[Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(AppPatternDto[]), 200)]
[ApiPermission(Permissions.AppPatternsRead)]
[ApiCosts(0)]
public IActionResult GetPatterns(string app)
{
@ -68,6 +66,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpPost]
[Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(AppPatternDto), 201)]
[ApiPermission(Permissions.AppPatternsCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostPattern(string app, [FromBody] UpdatePatternDto request)
{
@ -94,6 +93,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpPut]
[Route("apps/{app}/patterns/{id}/")]
[ProducesResponseType(typeof(AppPatternDto), 201)]
[ApiPermission(Permissions.AppPatternsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request)
{
@ -116,6 +116,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </remarks>
[HttpDelete]
[Route("apps/{app}/patterns/{id}/")]
[ApiPermission(Permissions.AppPatternsDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeletePattern(string app, Guid id)
{

8
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services;
@ -22,8 +23,6 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <summary>
/// Manages and configures apps.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppsController : ApiController
{
@ -52,6 +51,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet]
[Route("apps/")]
[ProducesResponseType(typeof(AppDto[]), 200)]
[ApiPermission]
[ApiCosts(0)]
public async Task<IActionResult> GetApps()
{
@ -84,6 +84,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ProducesResponseType(typeof(AppCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ApiPermission]
[ApiCosts(1)]
public async Task<IActionResult> PostApp([FromBody] CreateAppDto request)
{
@ -105,9 +106,8 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns>
[HttpDelete]
[Route("apps/{app}/")]
[AppApi]
[ApiPermission(Permissions.AppDelete)]
[ApiCosts(1)]
[MustBeAppOwner]
public async Task<IActionResult> DeleteApp(string app)
{
await CommandBus.PublishAsync(new ArchiveApp());

2
src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

@ -22,8 +22,6 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <summary>
/// Uploads and retrieves assets.
/// </summary>
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Assets))]
public sealed class AssetContentController : ApiController
{

19
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -14,6 +14,7 @@ using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Assets.Models;
using Squidex.Areas.Api.Controllers.Contents;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Services;
@ -30,9 +31,6 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <summary>
/// Uploads and retrieves assets.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Assets))]
public sealed class AssetsController : ApiController
{
@ -72,10 +70,10 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <remarks>
/// Get all tags for assets.
/// </remarks>
[MustBeAppReader]
[HttpGet]
[Route("apps/{app}/assets/tags")]
[ProducesResponseType(typeof(Dictionary<string, int>), 200)]
[ApiPermission(Permissions.AppAssetsRead)]
[ApiCosts(1)]
public async Task<IActionResult> GetTags(string app)
{
@ -96,10 +94,10 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <remarks>
/// Get all assets for the app.
/// </remarks>
[MustBeAppReader]
[HttpGet]
[Route("apps/{app}/assets/")]
[ProducesResponseType(typeof(AssetsDto), 200)]
[ApiPermission(Permissions.AppAssetsRead)]
[ApiCosts(1)]
public async Task<IActionResult> GetAssets(string app, [FromQuery] string ids = null)
{
@ -128,10 +126,10 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// 200 => Asset found.
/// 404 => Asset or app not found.
/// </returns>
[MustBeAppReader]
[HttpGet]
[Route("apps/{app}/assets/{id}/")]
[ProducesResponseType(typeof(AssetsDto), 200)]
[ApiPermission(Permissions.AppAssetsRead)]
[ApiCosts(1)]
public async Task<IActionResult> GetAsset(string app, Guid id)
{
@ -169,11 +167,12 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <remarks>
/// You can only upload one file at a time. The mime type of the file is not calculated by Squidex and is required correctly.
/// </remarks>
[MustBeAppEditor]
[HttpPost]
[Route("apps/{app}/assets/")]
[ProducesResponseType(typeof(AssetCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppAssetsCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostAsset(string app, [SwaggerIgnore] List<IFormFile> file)
{
var assetFile = await CheckAssetFileAsync(file);
@ -201,11 +200,11 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <remarks>
/// Use multipart request to upload an asset.
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("apps/{app}/assets/{id}/content/")]
[ProducesResponseType(typeof(AssetReplacedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppAssetsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutAssetContent(string app, Guid id, [SwaggerIgnore] List<IFormFile> file)
{
@ -231,10 +230,10 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// 400 => Asset name not valid.
/// 404 => Asset or app not found.
/// </returns>
[MustBeAppReader]
[HttpPut]
[Route("apps/{app}/assets/{id}/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppAssetsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutAsset(string app, Guid id, [FromBody] UpdateAssetDto request)
{
@ -252,9 +251,9 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// 204 => Asset has been deleted.
/// 404 => Asset or app not found.
/// </returns>
[MustBeAppEditor]
[HttpDelete]
[Route("apps/{app}/assets/{id}/")]
[ApiPermission(Permissions.AppAssetsDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteAsset(string app, Guid id)
{

2
src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs

@ -16,8 +16,6 @@ namespace Squidex.Areas.Api.Controllers.Backups
/// <summary>
/// Manages backups for app.
/// </summary>
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Backups))]
public class BackupContentController : ApiController
{

8
src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Orleans;
using Squidex.Areas.Api.Controllers.Backups.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Tasks;
@ -22,10 +23,6 @@ namespace Squidex.Areas.Api.Controllers.Backups
/// <summary>
/// Manages backups for app.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppOwner]
[ApiExplorerSettings(GroupName = nameof(Backups))]
public class BackupsController : ApiController
{
@ -48,6 +45,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
[HttpGet]
[Route("apps/{app}/backups/")]
[ProducesResponseType(typeof(List<BackupJobDto>), 200)]
[ApiPermission(Permissions.AppBackupsRead)]
[ApiCosts(0)]
public async Task<IActionResult> GetJobs(string app)
{
@ -71,6 +69,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
[HttpPost]
[Route("apps/{app}/backups/")]
[ProducesResponseType(typeof(List<BackupJobDto>), 200)]
[ApiPermission(Permissions.AppBackupsCreate)]
[ApiCosts(0)]
public IActionResult PostBackup(string app)
{
@ -93,6 +92,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
[HttpDelete]
[Route("apps/{app}/backups/{id}")]
[ProducesResponseType(typeof(List<BackupJobDto>), 200)]
[ApiPermission(Permissions.AppBackupsDelete)]
[ApiCosts(0)]
public async Task<IActionResult> DeleteBackup(string app, Guid id)
{

10
src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs

@ -7,9 +7,9 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Orleans;
using Squidex.Areas.Api.Controllers.Backups.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security;
@ -20,11 +20,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
/// <summary>
/// Restores backups.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[ApiModelValidation(true)]
[MustBeAdministrator]
[SwaggerIgnore]
public class RestoreController : ApiController
{
private readonly IGrainFactory grainFactory;
@ -37,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
[HttpGet]
[Route("apps/restore/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminRestoreRead)]
public async Task<IActionResult> GetJob()
{
var restoreGrain = grainFactory.GetGrain<IRestoreGrain>(User.OpenIdSubject());
@ -56,7 +52,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
[HttpPost]
[Route("apps/restore/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminRestoreCreate)]
public async Task<IActionResult> PostRestore([FromBody] RestoreRequest request)
{
var restoreGrain = grainFactory.GetGrain<IRestoreGrain>(User.OpenIdSubject());

9
src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Orleans;
using Squidex.Areas.Api.Controllers.Comments.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Comments;
using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Infrastructure;
@ -21,9 +22,6 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// <summary>
/// Manages comments for any kind of resource.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Comments))]
public sealed class CommentsController : ApiController
{
@ -51,6 +49,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
[HttpGet]
[Route("apps/{app}/comments/{commentsId}")]
[ProducesResponseType(typeof(CommentsDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetComments(string app, Guid commentsId, [FromQuery] long version = EtagVersion.Any)
{
@ -77,6 +76,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
[Route("apps/{app}/comments/{commentsId}")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> PostComment(string app, Guid commentsId, [FromBody] UpsertCommentDto request)
{
@ -100,10 +100,10 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// 400 => Comment text not valid.
/// 404 => Comment or app not found.
/// </returns>
[MustBeAppReader]
[HttpPut]
[Route("apps/{app}/comments/{commentsId}/{commentId}")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> PutComment(string app, Guid commentsId, Guid commentId, [FromBody] UpsertCommentDto request)
{
@ -125,6 +125,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
[HttpDelete]
[Route("apps/{app}/comments/{commentsId}/{commentId}")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> DeleteComment(string app, Guid commentsId, Guid commentId)
{

3
src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs

@ -7,7 +7,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Contents.Generator;
using Squidex.Domain.Apps.Entities;
using Squidex.Infrastructure.Commands;
@ -16,8 +15,6 @@ using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Contents
{
[ApiExceptionFilter]
[AppApi]
[SwaggerIgnore]
public sealed class ContentSwaggerController : ApiController
{
private readonly IAppProvider appProvider;

34
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -12,8 +12,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NodaTime;
using NodaTime.Text;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Contents.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
@ -24,10 +24,6 @@ using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Contents
{
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerIgnore]
public sealed class ContentsController : ApiController
{
private readonly IOptions<MyContentsControllerOptions> controllerOptions;
@ -58,10 +54,10 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppReader]
[HttpGet]
[HttpPost]
[Route("content/{app}/graphql/")]
[ApiPermission(Permissions.AppContentsGraphQL)]
[ApiCosts(2)]
public async Task<IActionResult> PostGraphQL(string app, [FromBody] GraphQLQuery query)
{
@ -89,10 +85,10 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppReader]
[HttpGet]
[HttpPost]
[Route("content/{app}/graphql/batch")]
[ApiPermission(Permissions.AppContentsGraphQL)]
[ApiCosts(2)]
public async Task<IActionResult> PostGraphQLBatch(string app, [FromBody] GraphQLQuery[] batch)
{
@ -122,9 +118,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppReader]
[HttpGet]
[Route("content/{app}/{name}/")]
[ApiPermission(Permissions.AppContentsRead)]
[ApiCosts(2)]
public async Task<IActionResult> GetContents(string app, string name, [FromQuery] bool archived = false, [FromQuery] string ids = null)
{
@ -161,9 +157,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppReader]
[HttpGet]
[Route("content/{app}/{name}/{id}/")]
[ApiPermission(Permissions.AppContentsRead)]
[ApiCosts(1)]
public async Task<IActionResult> GetContent(string app, string name, Guid id)
{
@ -197,9 +193,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppReader]
[HttpGet]
[Route("content/{app}/{name}/{id}/{version}/")]
[ApiPermission(Permissions.AppContentsRead)]
[ApiCosts(1)]
public async Task<IActionResult> GetContentVersion(string app, string name, Guid id, int version)
{
@ -233,9 +229,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPost]
[Route("content/{app}/{name}/")]
[ApiPermission(Permissions.AppContentsCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false)
{
@ -267,9 +263,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("content/{app}/{name}/{id}/")]
[ApiPermission(Permissions.AppContentsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false)
{
@ -300,9 +296,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPatch]
[Route("content/{app}/{name}/{id}/")]
[ApiPermission(Permissions.AppContentsUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false)
{
@ -332,9 +328,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("content/{app}/{name}/{id}/publish/")]
[ApiPermission(Permissions.AppContentsPublish)]
[ApiCosts(1)]
public async Task<IActionResult> PublishContent(string app, string name, Guid id, string dueTime = null)
{
@ -362,9 +358,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("content/{app}/{name}/{id}/unpublish/")]
[ApiPermission(Permissions.AppContentsUnpublish)]
[ApiCosts(1)]
public async Task<IActionResult> UnpublishContent(string app, string name, Guid id, string dueTime = null)
{
@ -392,9 +388,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("content/{app}/{name}/{id}/archive/")]
[ApiPermission(Permissions.AppContentsArchive)]
[ApiCosts(1)]
public async Task<IActionResult> ArchiveContent(string app, string name, Guid id, string dueTime = null)
{
@ -422,9 +418,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("content/{app}/{name}/{id}/restore/")]
[ApiPermission(Permissions.AppContentsRestore)]
[ApiCosts(1)]
public async Task<IActionResult> RestoreContent(string app, string name, Guid id, string dueTime = null)
{
@ -451,9 +447,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpPut]
[Route("content/{app}/{name}/{id}/discard/")]
[ApiPermission(Permissions.AppContentsDiscard)]
[ApiCosts(1)]
public async Task<IActionResult> DiscardChanges(string app, string name, Guid id)
{
@ -479,9 +475,9 @@ namespace Squidex.Areas.Api.Controllers.Contents
/// <remarks>
/// You can create an generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[MustBeAppEditor]
[HttpDelete]
[Route("content/{app}/{name}/{id}/")]
[ApiPermission(Permissions.AppContentsDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteContent(string app, string name, Guid id)
{

4
src/Squidex/Areas/Api/Controllers/Docs/DocsController.cs

@ -6,13 +6,10 @@
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Docs
{
[SwaggerIgnore]
public sealed class DocsController : ApiController
{
public DocsController(ICommandBus commandBus)
@ -22,7 +19,6 @@ namespace Squidex.Areas.Api.Controllers.Docs
[HttpGet]
[Route("docs/")]
[ApiCosts(0)]
public IActionResult Docs()
{
var vm = new DocsVM { Specification = "~/swagger/v1/swagger.json" };

14
src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs

@ -8,9 +8,9 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Orleans;
using Squidex.Areas.Api.Controllers.EventConsumers.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Orleans;
@ -18,10 +18,6 @@ using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.EventConsumers
{
[ApiAuthorize]
[ApiExceptionFilter]
[MustBeAdministrator]
[SwaggerIgnore]
public sealed class EventConsumersController : ApiController
{
private readonly IEventConsumerManagerGrain eventConsumerManagerGrain;
@ -34,7 +30,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpGet]
[Route("event-consumers/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminEventsRead)]
public async Task<IActionResult> GetEventConsumers()
{
var entities = await eventConsumerManagerGrain.GetConsumersAsync();
@ -46,7 +42,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/start/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminEventsManage)]
public async Task<IActionResult> Start(string name)
{
await eventConsumerManagerGrain.StartAsync(name);
@ -56,7 +52,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/stop/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminEventsManage)]
public async Task<IActionResult> Stop(string name)
{
await eventConsumerManagerGrain.StopAsync(name);
@ -66,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/reset/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminEventsManage)]
public async Task<IActionResult> Reset(string name)
{
await eventConsumerManagerGrain.ResetAsync(name);

6
src/Squidex/Areas/Api/Controllers/History/HistoryController.cs

@ -9,6 +9,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.History.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.History.Repositories;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -18,10 +19,6 @@ namespace Squidex.Areas.Api.Controllers.History
/// <summary>
/// Readonly API to get an event stream.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppEditor]
[ApiExplorerSettings(GroupName = nameof(History))]
public sealed class HistoryController : ApiController
{
@ -45,6 +42,7 @@ namespace Squidex.Areas.Api.Controllers.History
[HttpGet]
[Route("apps/{app}/history/")]
[ProducesResponseType(typeof(HistoryEventDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0.1)]
public async Task<IActionResult> GetHistory(string app, string channel)
{

4
src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs

@ -16,8 +16,6 @@ namespace Squidex.Areas.Api.Controllers.Languages
/// <summary>
/// Readonly API to the supported langauges.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[ApiExplorerSettings(GroupName = nameof(Languages))]
public sealed class LanguagesController : ApiController
{
@ -38,7 +36,7 @@ namespace Squidex.Areas.Api.Controllers.Languages
[HttpGet]
[Route("languages/")]
[ProducesResponseType(typeof(string[]), 200)]
[ApiCosts(0)]
[ApiPermission]
public IActionResult GetLanguages()
{
var response = Language.AllLanguages.Select(LanguageDto.FromLanguage).ToList();

8
src/Squidex/Areas/Api/Controllers/Ping/PingController.cs

@ -6,6 +6,7 @@
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using Squidex.Domain.Apps.Core;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -14,7 +15,6 @@ namespace Squidex.Areas.Api.Controllers.Ping
/// <summary>
/// Makes a ping request.
/// </summary>
[ApiExceptionFilter]
[ApiExplorerSettings(GroupName = nameof(Ping))]
public sealed class PingController : ApiController
{
@ -49,12 +49,10 @@ namespace Squidex.Areas.Api.Controllers.Ping
/// <remarks>
/// Can be used to test, if the Squidex API is alive and responding.
/// </remarks>
[ApiAuthorize]
[ApiCosts(0)]
[AppApi]
[MustBeAppReader]
[HttpGet]
[Route("ping/{app}/")]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public IActionResult GetPing(string app)
{
return NoContent();

8
src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Plans.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -17,9 +18,6 @@ namespace Squidex.Areas.Api.Controllers.Plans
/// <summary>
/// Manages and configures plans.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Plans))]
public sealed class AppPlansController : ApiController
{
@ -43,10 +41,10 @@ namespace Squidex.Areas.Api.Controllers.Plans
/// 200 => App plan information returned.
/// 404 => App not found.
/// </returns>
[MustBeAppOwner]
[HttpGet]
[Route("apps/{app}/plans/")]
[ProducesResponseType(typeof(AppPlansDto), 200)]
[ApiPermission(Permissions.AppPlansRead)]
[ApiCosts(0)]
public IActionResult GetPlans(string app)
{
@ -69,11 +67,11 @@ namespace Squidex.Areas.Api.Controllers.Plans
/// 400 => Plan not owned by user.
/// 404 => App not found.
/// </returns>
[MustBeAppOwner]
[HttpPut]
[Route("apps/{app}/plan/")]
[ProducesResponseType(typeof(PlanChangedDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppPlansChange)]
[ApiCosts(0)]
public async Task<IActionResult> ChangePlanAsync(string app, [FromBody] ChangePlanDto request)
{

13
src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -13,6 +13,7 @@ using IdentityServer4.Models;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using Squidex.Areas.Api.Controllers.Rules.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
@ -26,11 +27,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
/// <summary>
/// Manages and retrieves information about schemas.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Rules))]
[MustBeAppDeveloper]
public sealed class RulesController : ApiController
{
private static readonly string RuleActionsEtag = string.Join(";", RuleElementRegistry.Actions.Select(x => x.Key)).Sha256();
@ -56,6 +53,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[HttpGet]
[Route("rules/actions/")]
[ProducesResponseType(typeof(Dictionary<string, RuleElementDto>), 200)]
[ApiPermission(Permissions.AppRulesRead)]
[ApiCosts(0)]
public IActionResult GetActions()
{
@ -75,6 +73,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[HttpGet]
[Route("rules/triggers/")]
[ProducesResponseType(typeof(Dictionary<string, RuleElementDto>), 200)]
[ApiPermission(Permissions.AppRulesRead)]
[ApiCosts(0)]
public IActionResult GetTriggers()
{
@ -96,6 +95,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[HttpGet]
[Route("apps/{app}/rules/")]
[ProducesResponseType(typeof(RuleDto[]), 200)]
[ApiPermission(Permissions.AppRulesRead)]
[ApiCosts(1)]
public async Task<IActionResult> GetRules(string app)
{
@ -122,6 +122,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[Route("apps/{app}/rules/")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppRulesCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostRule(string app, [FromBody] CreateRuleDto request)
{
@ -150,6 +151,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[HttpPut]
[Route("apps/{app}/rules/{id}/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppRulesUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutRule(string app, Guid id, [FromBody] UpdateRuleDto request)
{
@ -170,6 +172,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
/// </returns>
[HttpPut]
[Route("apps/{app}/rules/{id}/enable/")]
[ApiPermission(Permissions.AppRulesDisable)]
[ApiCosts(1)]
public async Task<IActionResult> EnableRule(string app, Guid id)
{
@ -190,6 +193,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
/// </returns>
[HttpPut]
[Route("apps/{app}/rules/{id}/disable/")]
[ApiPermission(Permissions.AppRulesDisable)]
[ApiCosts(1)]
public async Task<IActionResult> DisableRule(string app, Guid id)
{
@ -209,6 +213,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
/// </returns>
[HttpDelete]
[Route("apps/{app}/rules/{id}/")]
[ApiPermission(Permissions.AppRulesDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteRule(string app, Guid id)
{

20
src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Schemas.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -17,10 +18,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <summary>
/// Manages and retrieves information about schemas.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppDeveloper]
[ApiExplorerSettings(GroupName = nameof(Schemas))]
public sealed class SchemaFieldsController : ApiController
{
@ -46,6 +43,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PostField(string app, string name, [FromBody] AddFieldDto request)
{
@ -75,6 +73,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PostNestedField(string app, string name, long parentId, [FromBody] AddFieldDto request)
{
@ -100,6 +99,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/ordering/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutSchemaFieldOrdering(string app, string name, [FromBody] ReorderFieldsDto request)
{
@ -123,6 +123,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/ordering/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutNestedFieldOrdering(string app, string name, long parentId, [FromBody] ReorderFieldsDto request)
{
@ -147,6 +148,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[Route("apps/{app}/schemas/{name}/fields/{id:long}/")]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutField(string app, string name, long id, [FromBody] UpdateFieldDto request)
{
@ -172,6 +174,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/")]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutNestedField(string app, string name, long parentId, long id, [FromBody] UpdateFieldDto request)
{
@ -197,6 +200,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/lock/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> LockField(string app, string name, long id)
{
@ -223,6 +227,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/lock/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> LockNestedField(string app, string name, long parentId, long id)
{
@ -248,6 +253,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/hide/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> HideField(string app, string name, long id)
{
@ -274,6 +280,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/hide/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> HideNestedField(string app, string name, long parentId, long id)
{
@ -299,6 +306,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/show/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> ShowField(string app, string name, long id)
{
@ -325,6 +333,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/show/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> ShowNestedField(string app, string name, long parentId, long id)
{
@ -350,6 +359,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/enable/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> EnableField(string app, string name, long id)
{
@ -376,6 +386,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/enable/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> EnableNestedField(string app, string name, long parentId, long id)
{
@ -401,6 +412,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/disable/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> DisableField(string app, string name, long id)
{

22
src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Schemas.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
@ -21,9 +22,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <summary>
/// Manages and retrieves information about schemas.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Schemas))]
public sealed class SchemasController : ApiController
{
@ -43,10 +41,10 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 200 => Schemas returned.
/// 404 => App not found.
/// </returns>
[MustBeAppEditor]
[HttpGet]
[Route("apps/{app}/schemas/")]
[ProducesResponseType(typeof(SchemaDto[]), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetSchemas(string app)
{
@ -68,10 +66,10 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 200 => Schema found.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppEditor]
[HttpGet]
[Route("apps/{app}/schemas/{name}/")]
[ProducesResponseType(typeof(SchemaDetailsDto[]), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetSchema(string app, string name)
{
@ -108,12 +106,12 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 400 => Schema name or properties are not valid.
/// 409 => Schema name already in use.
/// </returns>
[MustBeAppDeveloper]
[HttpPost]
[Route("apps/{app}/schemas/")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ApiPermission(Permissions.AppSchemasCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostSchema(string app, [FromBody] CreateSchemaDto request)
{
@ -137,9 +135,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 400 => Schema properties are not valid.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppDeveloper]
[HttpPut]
[Route("apps/{app}/schemas/{name}/")]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutSchema(string app, string name, [FromBody] UpdateSchemaDto request)
{
@ -159,9 +157,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 400 => Schema properties are not valid.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppDeveloper]
[HttpPut]
[Route("apps/{app}/schemas/{name}/category")]
[ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutCategory(string app, string name, [FromBody] ChangeCategoryDto request)
{
@ -181,9 +179,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 400 => Schema properties are not valid.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppDeveloper]
[HttpPut]
[Route("apps/{app}/schemas/{name}/scripts/")]
[ApiPermission(Permissions.AppSchemasScripts)]
[ApiCosts(1)]
public async Task<IActionResult> PutSchemaScripts(string app, string name, [FromBody] ConfigureScriptsDto request)
{
@ -202,10 +200,10 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 400 => Schema is already published.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppDeveloper]
[HttpPut]
[Route("apps/{app}/schemas/{name}/publish/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasPublish)]
[ApiCosts(1)]
public async Task<IActionResult> PublishSchema(string app, string name)
{
@ -224,10 +222,10 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 400 => Schema is not published.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppDeveloper]
[HttpPut]
[Route("apps/{app}/schemas/{name}/unpublish/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasPublish)]
[ApiCosts(1)]
public async Task<IActionResult> UnpublishSchema(string app, string name)
{
@ -245,9 +243,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// 204 => Schema has been deleted.
/// 404 => Schema or app not found.
/// </returns>
[MustBeAppDeveloper]
[HttpDelete]
[Route("apps/{app}/schemas/{name}/")]
[ApiPermission(Permissions.AppSchemasDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteSchema(string app, string name)
{

9
src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Statistics.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure.Commands;
@ -22,10 +23,6 @@ namespace Squidex.Areas.Api.Controllers.Statistics
/// <summary>
/// Retrieves usage information for apps.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppEditor]
[ApiExplorerSettings(GroupName = nameof(Statistics))]
public sealed class UsagesController : ApiController
{
@ -57,6 +54,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet]
[Route("apps/{app}/usages/calls/month/")]
[ProducesResponseType(typeof(CurrentCallsDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetMonthlyCalls(string app)
{
@ -83,6 +81,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet]
[Route("apps/{app}/usages/calls/{fromDate}/{toDate}/")]
[ProducesResponseType(typeof(Dictionary<string, CallsUsageDto[]>), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetUsages(string app, DateTime fromDate, DateTime toDate)
{
@ -109,6 +108,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet]
[Route("apps/{app}/usages/storage/today/")]
[ProducesResponseType(typeof(CurrentStorageDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetCurrentStorageSize(string app)
{
@ -135,6 +135,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet]
[Route("apps/{app}/usages/storage/{fromDate}/{toDate}/")]
[ProducesResponseType(typeof(StorageUsageDto[]), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public async Task<IActionResult> GetStorageSizes(string app, DateTime fromDate, DateTime toDate)
{

9
src/Squidex/Areas/Api/Controllers/UI/UIController.cs

@ -21,9 +21,6 @@ namespace Squidex.Areas.Api.Controllers.UI
/// <summary>
/// Manages ui settings and configs.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(UI))]
public sealed class UIController : ApiController
{
@ -53,7 +50,7 @@ namespace Squidex.Areas.Api.Controllers.UI
[HttpGet]
[Route("apps/{app}/ui/settings/")]
[ProducesResponseType(typeof(UISettingsDto), 200)]
[ApiCosts(0)]
[ApiPermission]
public async Task<IActionResult> GetSettings(string app)
{
var result = await grainFactory.GetGrain<IAppUISettingsGrain>(App.Id).GetAsync();
@ -77,7 +74,7 @@ namespace Squidex.Areas.Api.Controllers.UI
/// </returns>
[HttpPut]
[Route("apps/{app}/ui/settings/{key}")]
[ApiCosts(0)]
[ApiPermission]
public async Task<IActionResult> PutSetting(string app, string key, [FromBody] UpdateSettingDto request)
{
await grainFactory.GetGrain<IAppUISettingsGrain>(App.Id).SetAsync(key, request.Value);
@ -96,7 +93,7 @@ namespace Squidex.Areas.Api.Controllers.UI
/// </returns>
[HttpDelete]
[Route("apps/{app}/ui/settings/{key}")]
[ApiCosts(0)]
[ApiPermission]
public async Task<IActionResult> DeleteSetting(string app, string key)
{
await grainFactory.GetGrain<IAppUISettingsGrain>(App.Id).RemoveAsync(key);

18
src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs

@ -10,8 +10,8 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Users.Models;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
@ -21,11 +21,7 @@ using Squidex.Shared.Users;
namespace Squidex.Areas.Api.Controllers.Users
{
[ApiAuthorize]
[ApiExceptionFilter]
[ApiModelValidation(true)]
[MustBeAdministrator]
[SwaggerIgnore]
public sealed class UserManagementController : ApiController
{
private readonly UserManager<IUser> userManager;
@ -40,7 +36,7 @@ namespace Squidex.Areas.Api.Controllers.Users
[HttpGet]
[Route("user-management/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminUsersRead)]
public async Task<IActionResult> GetUsers([FromQuery] string query = null, [FromQuery] int skip = 0, [FromQuery] int take = 10)
{
var taskForItems = userManager.QueryByEmailAsync(query, take, skip);
@ -59,7 +55,7 @@ namespace Squidex.Areas.Api.Controllers.Users
[HttpGet]
[Route("user-management/{id}/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminUsersRead)]
public async Task<IActionResult> GetUser(string id)
{
var entity = await userManager.FindByIdAsync(id);
@ -76,7 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Users
[HttpPost]
[Route("user-management/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminUsersCreate)]
public async Task<IActionResult> PostUser([FromBody] CreateUserDto request)
{
var user = await userManager.CreateAsync(userFactory, request.Email, request.DisplayName, request.Password);
@ -88,7 +84,7 @@ namespace Squidex.Areas.Api.Controllers.Users
[HttpPut]
[Route("user-management/{id}/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminUsersUpdate)]
public async Task<IActionResult> PutUser(string id, [FromBody] UpdateUserDto request)
{
await userManager.UpdateAsync(id, request.Email, request.DisplayName, request.Password);
@ -98,7 +94,7 @@ namespace Squidex.Areas.Api.Controllers.Users
[HttpPut]
[Route("user-management/{id}/lock/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminUsersLock)]
public async Task<IActionResult> LockUser(string id)
{
if (IsSelf(id))
@ -113,7 +109,7 @@ namespace Squidex.Areas.Api.Controllers.Users
[HttpPut]
[Route("user-management/{id}/unlock/")]
[ApiCosts(0)]
[ApiPermission(Permissions.AdminUsersUnlock)]
public async Task<IActionResult> UnlockUser(string id)
{
if (IsSelf(id))

5
src/Squidex/Areas/Api/Controllers/Users/UsersController.cs

@ -24,7 +24,6 @@ namespace Squidex.Areas.Api.Controllers.Users
/// <summary>
/// Readonly API to retrieve information about squidex users.
/// </summary>
[ApiExceptionFilter]
[ApiExplorerSettings(GroupName = nameof(Users))]
public sealed class UsersController : ApiController
{
@ -68,10 +67,10 @@ namespace Squidex.Areas.Api.Controllers.Users
/// <returns>
/// 200 => Users returned.
/// </returns>
[ApiAuthorize]
[HttpGet]
[Route("users/")]
[ProducesResponseType(typeof(PublicUserDto[]), 200)]
[ApiPermission]
public async Task<IActionResult> GetUsers(string query)
{
try
@ -100,10 +99,10 @@ namespace Squidex.Areas.Api.Controllers.Users
/// 200 => User found.
/// 404 => User not found.
/// </returns>
[ApiAuthorize]
[HttpGet]
[Route("users/{id}/")]
[ProducesResponseType(typeof(PublicUserDto), 200)]
[ApiPermission]
public async Task<IActionResult> GetUser(string id)
{
try

2
src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs

@ -17,7 +17,6 @@ using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Config;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
@ -28,7 +27,6 @@ using Squidex.Shared.Users;
namespace Squidex.Areas.IdentityServer.Controllers.Account
{
[SwaggerIgnore]
public sealed class AccountController : IdentityServerController
{
private readonly SignInManager<IUser> signInManager;

2
src/Squidex/Areas/IdentityServer/Controllers/Error/ErrorController.cs

@ -6,11 +6,9 @@
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
namespace Squidex.Areas.IdentityServer.Controllers.Error
{
[SwaggerIgnore]
public sealed class ErrorController : IdentityServerController
{
[Route("error/")]

2
src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs

@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Config;
using Squidex.Domain.Users;
using Squidex.Infrastructure.Assets;
@ -26,7 +25,6 @@ using Squidex.Shared.Users;
namespace Squidex.Areas.IdentityServer.Controllers.Profile
{
[Authorize]
[SwaggerIgnore]
public sealed class ProfileController : IdentityServerController
{
private readonly SignInManager<IUser> signInManager;

3
src/Squidex/Config/Web/WebServices.cs

@ -18,7 +18,7 @@ namespace Squidex.Config.Web
services.AddSingletonAs<FileCallbackResultExecutor>()
.AsSelf();
services.AddSingletonAs<AppApiFilter>()
services.AddSingletonAs<AppResolverFilter>()
.AsSelf();
services.AddSingletonAs<ApiCostsFilter>()
@ -36,6 +36,7 @@ namespace Squidex.Config.Web
services.AddMvc(options =>
{
options.Filters.Add<ETagFilter>();
options.Filters.Add<AppResolverFilter>();
}).AddMySerializers();
services.AddCors();

20
src/Squidex/Pipeline/ApiAuthorizeAttribute.cs

@ -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;
}
}
}

54
src/Squidex/Pipeline/ApiPermissionAttribute.cs

@ -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();
}
}
}

19
src/Squidex/Pipeline/AppApiAttribute.cs

@ -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))
{
}
}
}

55
src/Squidex/Pipeline/AppApiFilter.cs

@ -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();
}
}
}

106
src/Squidex/Pipeline/AppPermissionAttribute.cs

@ -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;
}
}
}

102
src/Squidex/Pipeline/AppResolverFilter.cs

@ -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;
}
}
}

19
src/Squidex/Pipeline/MustBeAdministratorAttribute.cs

@ -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;
}
}
}

19
src/Squidex/Pipeline/MustBeAppDeveloperAttribute.cs

@ -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)
{
}
}
}

19
src/Squidex/Pipeline/MustBeAppEditorAttribute.cs

@ -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)
{
}
}
}

19
src/Squidex/Pipeline/MustBeAppOwnerAttribute.cs

@ -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)
{
}
}
}

19
src/Squidex/Pipeline/MustBeAppReaderAttribute.cs

@ -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)
{
}
}
}

8
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleExtensionTests.cs

@ -5,14 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Apps;
using Xunit;
// using System;
// using Squidex.Domain.Apps.Core.Apps;
// using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Apps
{
public class RoleExtensionTests
{
/*
[Fact]
public void Should_convert_from_client_permission_to_app_permission()
{
@ -40,5 +41,6 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{
Assert.Throws<ArgumentException>(() => ((AppContributorPermission)10).ToAppPermission());
}
*/
}
}

1
tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentsGrainTests.cs

@ -11,7 +11,6 @@ using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using NodaTime;
using Squidex.Domain.Apps.Core.Comments;
using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.Comments.State;

2
tests/Squidex.Infrastructure.Tests/RefTokenTests.cs

@ -90,6 +90,7 @@ namespace Squidex.Infrastructure
Assert.True(token1a.Equals(token1b));
Assert.False(token1a.Equals(token2a));
Assert.False(token1b.Equals(token2a));
}
[Fact]
@ -102,6 +103,7 @@ namespace Squidex.Infrastructure
Assert.Equal(token1a.GetHashCode(), token1b.GetHashCode());
Assert.NotEqual(token1a.GetHashCode(), token2a.GetHashCode());
Assert.NotEqual(token1b.GetHashCode(), token2a.GetHashCode());
}
[Fact]

65
tests/Squidex.Infrastructure.Tests/Security/PermissionSetTests.cs

@ -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));
}
}
}

138
tests/Squidex.Infrastructure.Tests/Security/PermissionTests.cs

@ -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);
}
}
}

2
tests/Squidex.Tests/Pipeline/ApiCostsFilterTests.cs

@ -161,7 +161,7 @@ namespace Squidex.Pipeline
private void SetupApp()
{
httpContext.Features.Set<IAppFeature>(new AppApiFilter.AppFeature(appEntity));
httpContext.Features.Set<IAppFeature>(new AppResolverFilter.AppFeature(appEntity));
}
}
}

2
tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs

@ -117,7 +117,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
A.CallTo(() => appEntity.Id).Returns(appId);
A.CallTo(() => appEntity.Name).Returns(appName);
httpContext.Features.Set<IAppFeature>(new AppApiFilter.AppFeature(appEntity));
httpContext.Features.Set<IAppFeature>(new AppResolverFilter.AppFeature(appEntity));
}
}
}

2
tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs

@ -208,7 +208,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
A.CallTo(() => appEntity.Id).Returns(appId);
A.CallTo(() => appEntity.Name).Returns(appName);
httpContext.Features.Set<IAppFeature>(new AppApiFilter.AppFeature(appEntity));
httpContext.Features.Set<IAppFeature>(new AppResolverFilter.AppFeature(appEntity));
}
}
}

Loading…
Cancel
Save