From ef692b593bc9d8f31f716f8bc497987235c2b08f Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Mon, 27 Apr 2020 15:49:14 +0200 Subject: [PATCH] compose available permissions in zero builder and output them in role detail --- zero.Core/Api/PermissionsApi.cs | 30 +++++ zero.Core/Api/UserApi.cs | 2 +- zero.Core/Constants.cs | 4 +- zero.Core/Identity/Permission.cs | 112 +++++++++++++----- zero.Core/Identity/PermissionCollection.cs | 22 ++++ zero.Core/Identity/PermissionValueType.cs | 18 +++ zero.Core/Identity/Permissions.cs | 14 ++- zero.Core/Identity/ZeroAuthorizeAttribute.cs | 3 - zero.Core/ZeroOptions.cs | 9 ++ zero.Web/App/Pages/settings/role.vue | 39 +++--- zero.Web/App/Resources/userRoles.js | 8 +- zero.Web/App/pages/pages/page.vue | 2 +- zero.Web/Controllers/UserRolesController.cs | 16 ++- zero.Web/Controllers/UsersController.cs | 15 ++- .../Resources/Localization/zero.en-us.json | 16 ++- zero.Web/Sass/Modules/_headlines.scss | 7 ++ zero.Web/ZeroBuilder.cs | 46 +++++-- zero.Web/ZeroVue.cs | 12 +- 18 files changed, 288 insertions(+), 87 deletions(-) create mode 100644 zero.Core/Api/PermissionsApi.cs create mode 100644 zero.Core/Identity/PermissionCollection.cs create mode 100644 zero.Core/Identity/PermissionValueType.cs diff --git a/zero.Core/Api/PermissionsApi.cs b/zero.Core/Api/PermissionsApi.cs new file mode 100644 index 00000000..3e589e9c --- /dev/null +++ b/zero.Core/Api/PermissionsApi.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Options; +using System.Collections.Generic; +using zero.Core.Identity; + +namespace zero.Core.Api +{ + public class PermissionsApi : IPermissionsApi + { + protected ZeroOptions Options { get; set; } + + + public PermissionsApi (IOptionsMonitor options) + { + Options = options.CurrentValue; + } + + + /// + //public IList GetPermissions() + //{ + + //} + } + + + public interface IPermissionsApi + { + + } +} diff --git a/zero.Core/Api/UserApi.cs b/zero.Core/Api/UserApi.cs index 27ea7392..1f7b08a4 100644 --- a/zero.Core/Api/UserApi.cs +++ b/zero.Core/Api/UserApi.cs @@ -74,7 +74,7 @@ namespace zero.Core.Api { return Principal.Claims .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix))) - .Select(claim => new Permission(claim, prefix)) + .Select(claim => Permission.FromClaim(claim, prefix)) .ToList(); } diff --git a/zero.Core/Constants.cs b/zero.Core/Constants.cs index 4f915dce..709cd8af 100644 --- a/zero.Core/Constants.cs +++ b/zero.Core/Constants.cs @@ -38,7 +38,7 @@ public const string Settings = "settings"; } - public static class SettingsAreas + public static class Settings { public const string Updates = "updates"; public const string Applications = "applications"; @@ -46,6 +46,8 @@ public const string Translations = "translations"; public const string Countries = "countries"; public const string Logging = "logs"; + public const string Plugins = "plugins"; + public const string CreatePlugin = "createplugin"; } } } diff --git a/zero.Core/Identity/Permission.cs b/zero.Core/Identity/Permission.cs index 80c4bc79..d74bd2f5 100644 --- a/zero.Core/Identity/Permission.cs +++ b/zero.Core/Identity/Permission.cs @@ -7,63 +7,119 @@ namespace zero.Core.Identity { public class Permission { + /// + /// Full key (stored in left part claim value) of the permission + /// public string Key { get; set; } + /// + /// Normalized key with optionally removed prefix + /// public string NormalizedKey { get; set; } + /// + /// Value of the permission (boolean, read/write, ...). Stored in right part of claim value + /// public string Value { get; set; } - public bool CanRead => Value == PermissionsValue.Read || Value == PermissionsValue.Write; + /// + /// Title of this permission for output + /// + public string Label { get; set; } - public bool CanWrite => Value == PermissionsValue.Write; + /// + /// Optional description text + /// + public string Description { get; set; } - public bool IsTrue => Value == PermissionsValue.True; - - public bool IsFalse => Value == PermissionsValue.False; + /// + /// Datatype of the value + /// + public PermissionValueType ValueType { get; set; } public Permission() { } - public Permission(Claim claim, string prefixToRemove = null) : this(claim.Value, prefixToRemove) { } - - public Permission(string claimValue, string prefixToRemove = null) + public Permission(string key, string value, PermissionValueType valueType = PermissionValueType.ReadWrite) { - string[] valueParts = claimValue.Split(':'); + Key = key; + Value = value; + ValueType = valueType; + } - Key = valueParts[0]; - NormalizedKey = Key; - Value = valueParts.Length > 1 ? valueParts[1] : null; - - if (!prefixToRemove.IsNullOrEmpty()) - { - NormalizedKey = valueParts[0].TrimStart(prefixToRemove); - } + public Permission(string key, string label, string description, PermissionValueType valueType = PermissionValueType.ReadWrite) + { + Key = key; + Label = label; + Description = description; + ValueType = valueType; } + /// + /// Whether the value is read or write + /// + public bool CanRead => Value == PermissionsValue.Read || Value == PermissionsValue.Write; + + /// + /// Whether the value is write + /// + public bool CanWrite => Value == PermissionsValue.Write; + + /// + /// Whether the value is true + /// + public bool IsTrue => Value == PermissionsValue.True; + + /// + /// Whether the value is false + /// + public bool IsFalse => Value == PermissionsValue.False; + + /// + /// Whether a resource (with authorization based on the key) can be read by any of the given permisssions + /// public static bool CanReadKey(IEnumerable permissions, string key, bool isNormalized = false) { Permission permission = permissions.FirstOrDefault(p => isNormalized ? p.NormalizedKey == key : p.Key == key); - - if (permission == null) - { - return false; - } - - return permission.CanRead; + return permission?.CanRead ?? false; } - + /// + /// Whether a resource (with authorization based on the key) can be written to by any of the given permisssions + /// public static bool CanWriteKey(IEnumerable permissions, string key, bool isNormalized = false) { Permission permission = permissions.FirstOrDefault(p => isNormalized ? p.NormalizedKey == key : p.Key == key); + return permission?.CanWrite ?? false; + } - if (permission == null) + /// + /// Create a permission from a claim + /// + public static Permission FromClaim(Claim claim, string prefixToRemove = null) + { + return FromClaim(claim.Value, prefixToRemove); + } + + /// + /// Create a permission from a claim + /// + public static Permission FromClaim(string claimValue, string prefixToRemove = null) + { + Permission permission = new Permission(); + string[] valueParts = claimValue.Split(':'); + + permission.Key = valueParts[0]; + permission.NormalizedKey = permission.Key; + permission.Value = valueParts.Length > 1 ? valueParts[1] : null; + + if (!prefixToRemove.IsNullOrEmpty()) { - return false; + permission.NormalizedKey = valueParts[0].TrimStart(prefixToRemove); } - return permission.CanWrite; + return permission; } } } diff --git a/zero.Core/Identity/PermissionCollection.cs b/zero.Core/Identity/PermissionCollection.cs new file mode 100644 index 00000000..5f67a0f9 --- /dev/null +++ b/zero.Core/Identity/PermissionCollection.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace zero.Core.Identity +{ + public class PermissionCollection + { + /// + /// Name of the group + /// + public string Label { get; set; } + + /// + /// Optional description text + /// + public string Description { get; set; } + + /// + /// Individual permissions (stored as claims on the user identity) + /// + public IList Items { get; set; } = new List(); + } +} diff --git a/zero.Core/Identity/PermissionValueType.cs b/zero.Core/Identity/PermissionValueType.cs new file mode 100644 index 00000000..091bf777 --- /dev/null +++ b/zero.Core/Identity/PermissionValueType.cs @@ -0,0 +1,18 @@ +namespace zero.Core.Identity +{ + public enum PermissionValueType + { + /// + /// True or false + /// + Boolean = 0, + /// + /// Read, write or none + /// + ReadWrite = 1, + /// + /// Simple text string + /// + String = 2 + } +} diff --git a/zero.Core/Identity/Permissions.cs b/zero.Core/Identity/Permissions.cs index 0f0f5ba9..748f6087 100644 --- a/zero.Core/Identity/Permissions.cs +++ b/zero.Core/Identity/Permissions.cs @@ -12,12 +12,14 @@ public struct Settings { public const string PREFIX = "settings.area."; - public const string Updates = PREFIX + Constants.SettingsAreas.Updates; - public const string Applications = PREFIX + Constants.SettingsAreas.Applications; - public const string Users = PREFIX + Constants.SettingsAreas.Users; - public const string Translations = PREFIX + Constants.SettingsAreas.Translations; - public const string Countries = PREFIX + Constants.SettingsAreas.Countries; - public const string Logging = PREFIX + Constants.SettingsAreas.Logging; + public const string Updates = PREFIX + Constants.Settings.Updates; + public const string Applications = PREFIX + Constants.Settings.Applications; + public const string Users = PREFIX + Constants.Settings.Users; + public const string Translations = PREFIX + Constants.Settings.Translations; + public const string Countries = PREFIX + Constants.Settings.Countries; + public const string Logging = PREFIX + Constants.Settings.Logging; + public const string Plugins = PREFIX + Constants.Settings.Plugins; + public const string CreatePlugin = PREFIX + Constants.Settings.CreatePlugin; } diff --git a/zero.Core/Identity/ZeroAuthorizeAttribute.cs b/zero.Core/Identity/ZeroAuthorizeAttribute.cs index e805e381..6b46eeb8 100644 --- a/zero.Core/Identity/ZeroAuthorizeAttribute.cs +++ b/zero.Core/Identity/ZeroAuthorizeAttribute.cs @@ -83,12 +83,9 @@ namespace zero.Core.Identity // do not run this filter if it is overridden if (siblingFilters.Length > 1 && currentIndex < siblingFilters.Length - 1) { - Console.WriteLine("skip " + Permission + ": " + String.Join(", ", PermissionValues)); return; } - Console.WriteLine("run " + Permission + ": " + String.Join(", ", PermissionValues)); - ClaimsPrincipal user = context.HttpContext.User; bool isAuthenticated = user.Identity.IsAuthenticated; diff --git a/zero.Core/ZeroOptions.cs b/zero.Core/ZeroOptions.cs index bb1676c9..665ad08d 100644 --- a/zero.Core/ZeroOptions.cs +++ b/zero.Core/ZeroOptions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using zero.Core.Entities; +using zero.Core.Identity; namespace zero.Core { @@ -10,5 +11,13 @@ namespace zero.Core public SectionCollection Sections { get; private set; } = new SectionCollection(); public IList SettingsAreas { get; private set; } = new List(); + + public ZeroAuthorizationOptions Authorization { get; private set; } = new ZeroAuthorizationOptions(); + } + + + public class ZeroAuthorizationOptions + { + public IList Permissions { get; private set; } = new List(); } } diff --git a/zero.Web/App/Pages/settings/role.vue b/zero.Web/App/Pages/settings/role.vue index 13fe6d8d..3f719a5b 100644 --- a/zero.Web/App/Pages/settings/role.vue +++ b/zero.Web/App/Pages/settings/role.vue @@ -26,17 +26,13 @@ -
-

- - - -
- -
-

- - +
+

+ {{ permissionCollection.label | localize }} +
{{ permissionCollection.description | localize }}
+

+ +
@@ -70,10 +66,7 @@ name: null, email: null }, - permissions: { - sections: [], - settings: [] - } + permissions: [] }), created() @@ -83,16 +76,6 @@ icon: 'fth-trash', action: this.onDelete }); - - this.permissions.sections = zero.sections; - - zero.settingsAreas.forEach(area => - { - area.items.forEach(item => - { - this.permissions.settings.push(item); - }); - }) }, @@ -110,6 +93,12 @@ { this.model = response; }); + + UserRolesApi.getAllPermissions().then(response => + { + console.info(response); + this.permissions = response; + }); }, onSubmit(form) diff --git a/zero.Web/App/Resources/userRoles.js b/zero.Web/App/Resources/userRoles.js index 7ddf4e62..9c269b8c 100644 --- a/zero.Web/App/Resources/userRoles.js +++ b/zero.Web/App/Resources/userRoles.js @@ -12,5 +12,11 @@ export default { getAll() { return Axios.get('userRoles/getAll').then(res => Promise.resolve(res.data)); - } + }, + + // get all permissions + getAllPermissions() + { + return Axios.get('userRoles/getAllPermissions').then(res => Promise.resolve(res.data)); + }, }; \ No newline at end of file diff --git a/zero.Web/App/pages/pages/page.vue b/zero.Web/App/pages/pages/page.vue index be2f15bf..54be3e00 100644 --- a/zero.Web/App/pages/pages/page.vue +++ b/zero.Web/App/pages/pages/page.vue @@ -5,7 +5,7 @@ - + diff --git a/zero.Web/Controllers/UserRolesController.cs b/zero.Web/Controllers/UserRolesController.cs index 0c769e8a..8ccc010b 100644 --- a/zero.Web/Controllers/UserRolesController.cs +++ b/zero.Web/Controllers/UserRolesController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using System.Threading.Tasks; using zero.Core; using zero.Core.Api; @@ -15,9 +16,13 @@ namespace zero.Web.Controllers { private IUserRolesApi Api { get; set; } - public UserRolesController(IZeroConfiguration config, IUserRolesApi api, IMapper mapper, IToken token) : base(config, mapper, token) + private ZeroOptions Options { get; set; } + + + public UserRolesController(IZeroConfiguration config, IUserRolesApi api, IMapper mapper, IToken token, IOptionsMonitor options) : base(config, mapper, token) { Api = api; + Options = options.CurrentValue; } @@ -38,5 +43,14 @@ namespace zero.Web.Controllers { return As(await Api.GetAll()); } + + + /// + /// Get all permissions for selection + /// + public IActionResult GetAllPermissions() + { + return Json(Options.Authorization.Permissions); + } } } diff --git a/zero.Web/Controllers/UsersController.cs b/zero.Web/Controllers/UsersController.cs index 940d32b2..995323cd 100644 --- a/zero.Web/Controllers/UsersController.cs +++ b/zero.Web/Controllers/UsersController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using System.Threading.Tasks; using zero.Core; using zero.Core.Api; @@ -14,9 +15,12 @@ namespace zero.Web.Controllers { private IUserApi Api { get; set; } - public UsersController(IZeroConfiguration config, IUserApi api, IMapper mapper, IToken token) : base(config, mapper, token) + private ZeroOptions Options { get; set; } + + public UsersController(IZeroConfiguration config, IUserApi api, IMapper mapper, IToken token, IOptionsMonitor options) : base(config, mapper, token) { Api = api; + Options = options.CurrentValue; } @@ -36,5 +40,14 @@ namespace zero.Web.Controllers { return As(await Api.GetByQuery(query, "zero.applications.1-A")); } + + + /// + /// Get all permissions for selection + /// + public IActionResult GetAllPermissions() + { + return Json(Options.Authorization.Permissions); + } } } diff --git a/zero.Web/Resources/Localization/zero.en-us.json b/zero.Web/Resources/Localization/zero.en-us.json index 9aa5916b..8da184fe 100644 --- a/zero.Web/Resources/Localization/zero.en-us.json +++ b/zero.Web/Resources/Localization/zero.en-us.json @@ -80,7 +80,8 @@ "system": { "updates": { "name": "Updates", - "text": "Version {zero_version}" + "text": "Version {zero_version}", + "text_default": "Update current zero version" }, "applications": { "name": "Applications", @@ -105,8 +106,9 @@ }, "plugins": { "installed": { - "name": "Installed", - "text": "{plugin_count} installed plugin(s)" + "name": "Installed plugins", + "text": "{plugin_count} installed plugin(s)", + "text_default": "Manage and install plugins" }, "create": { "name": "Create a plugin", @@ -147,8 +149,12 @@ }, "permission": { - "sections": "Sections", - "settings": "Settings" + "collections": { + "sections": "Sections", + "sections_description": "Allow access to the following sections", + "settings": "Settings", + "settings_description": "Define read and write access for settings areas" + } }, "login": { diff --git a/zero.Web/Sass/Modules/_headlines.scss b/zero.Web/Sass/Modules/_headlines.scss index d26140ec..c38be775 100644 --- a/zero.Web/Sass/Modules/_headlines.scss +++ b/zero.Web/Sass/Modules/_headlines.scss @@ -11,4 +11,11 @@ h2.ui-headline { font-size: var(--font-size-xl); } + + .-minor + { + font-size: var(--font-size); + color: var(--color-fg-mid); + font-weight: 400; + } } \ No newline at end of file diff --git a/zero.Web/ZeroBuilder.cs b/zero.Web/ZeroBuilder.cs index 35268d96..78c999ef 100644 --- a/zero.Web/ZeroBuilder.cs +++ b/zero.Web/ZeroBuilder.cs @@ -94,19 +94,49 @@ namespace zero.Web opts.Sections.Add(); SettingsGroup systemSettings = new SettingsGroup("@settings.groups.system"); - systemSettings.Add(Constants.SettingsAreas.Updates, "@settings.system.updates.name", "@settings.system.updates.text", "fth-check-circle"); - systemSettings.Add(Constants.SettingsAreas.Applications, "@settings.system.applications.name", "@settings.system.applications.text", "fth-layers"); - systemSettings.Add(Constants.SettingsAreas.Users, "@settings.system.users.name", "@settings.system.users.text", "fth-users"); - systemSettings.Add(Constants.SettingsAreas.Translations, "@settings.system.translations.name", "@settings.system.translations.text", "fth-type"); - systemSettings.Add(Constants.SettingsAreas.Countries, "@settings.system.countries.name", "@settings.system.countries.text", "fth-map-pin"); - systemSettings.Add(Constants.SettingsAreas.Logging, "@settings.system.logs.name", "@settings.system.logs.text", "fth-file-text"); + systemSettings.Add(Constants.Settings.Updates, "@settings.system.updates.name", "@settings.system.updates.text", "fth-check-circle"); + systemSettings.Add(Constants.Settings.Applications, "@settings.system.applications.name", "@settings.system.applications.text", "fth-layers"); + systemSettings.Add(Constants.Settings.Users, "@settings.system.users.name", "@settings.system.users.text", "fth-users"); + systemSettings.Add(Constants.Settings.Translations, "@settings.system.translations.name", "@settings.system.translations.text", "fth-type"); + systemSettings.Add(Constants.Settings.Countries, "@settings.system.countries.name", "@settings.system.countries.text", "fth-map-pin"); + systemSettings.Add(Constants.Settings.Logging, "@settings.system.logs.name", "@settings.system.logs.text", "fth-file-text"); SettingsGroup pluginSettings = new SettingsGroup("@settings.groups.plugins"); - pluginSettings.Add("plugins", "@settings.plugins.installed.name", "@settings.plugins.installed.text", "fth-package"); - pluginSettings.Add("createplugin", "@settings.plugins.create.name", "@settings.plugins.create.text", "fth-box"); + pluginSettings.Add(Constants.Settings.Plugins, "@settings.plugins.installed.name", "@settings.plugins.installed.text", "fth-package"); + pluginSettings.Add(Constants.Settings.CreatePlugin, "@settings.plugins.create.name", "@settings.plugins.create.text", "fth-box"); opts.SettingsAreas.Add(systemSettings); opts.SettingsAreas.Add(pluginSettings); + + PermissionCollection permissionSettings = new PermissionCollection() + { + Label = "@permission.collections.settings", + Description = "@permission.collections.settings_description" + }; + + permissionSettings.Items.Add(new Permission(Permissions.Settings.Updates, "@settings.system.updates.name", "@settings.system.updates.text_default", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.Applications, "@settings.system.applications.name", "@settings.system.applications.text", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.Users, "@settings.system.users.name", "@settings.system.users.text", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.Translations, "@settings.system.translations.name", "@settings.system.translations.text", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.Countries, "@settings.system.countries.name", "@settings.system.countries.text", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.Logging, "@settings.system.logs.name", "@settings.system.logs.text", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.Plugins, "@settings.plugins.installed.name", "@settings.plugins.installed.text_default", PermissionValueType.ReadWrite)); + permissionSettings.Items.Add(new Permission(Permissions.Settings.CreatePlugin, "@settings.plugins.create.name", "@settings.plugins.create.text", PermissionValueType.ReadWrite)); + + PermissionCollection permissionSections = new PermissionCollection() + { + Label = "@permission.collections.sections", + Description = "@permission.collections.sections_description" + }; + + permissionSections.Items.Add(new Permission(Permissions.Sections.Dashboard, "@sections.item.dashboard", null, PermissionValueType.Boolean)); + permissionSections.Items.Add(new Permission(Permissions.Sections.Pages, "@sections.item.pages", null, PermissionValueType.Boolean)); + permissionSections.Items.Add(new Permission(Permissions.Sections.Lists, "@sections.item.lists", null, PermissionValueType.Boolean)); + permissionSections.Items.Add(new Permission(Permissions.Sections.Media, "@sections.item.media", null, PermissionValueType.Boolean)); + permissionSections.Items.Add(new Permission(Permissions.Sections.Settings, "@sections.item.settings", null, PermissionValueType.Boolean)); + + opts.Authorization.Permissions.Add(permissionSections); + opts.Authorization.Permissions.Add(permissionSettings); } diff --git a/zero.Web/ZeroVue.cs b/zero.Web/ZeroVue.cs index 1c25ddc6..38e06383 100644 --- a/zero.Web/ZeroVue.cs +++ b/zero.Web/ZeroVue.cs @@ -134,12 +134,12 @@ namespace zero.Web sections.Add("settings", Constants.Sections.Settings); Dictionary settings = new Dictionary(); - settings.Add("applications", Constants.SettingsAreas.Applications); - settings.Add("countries", Constants.SettingsAreas.Countries); - settings.Add("logging", Constants.SettingsAreas.Logging); - settings.Add("translations", Constants.SettingsAreas.Translations); - settings.Add("updates", Constants.SettingsAreas.Updates); - settings.Add("users", Constants.SettingsAreas.Users); + settings.Add("applications", Constants.Settings.Applications); + settings.Add("countries", Constants.Settings.Countries); + settings.Add("logging", Constants.Settings.Logging); + settings.Add("translations", Constants.Settings.Translations); + settings.Add("updates", Constants.Settings.Updates); + settings.Add("users", Constants.Settings.Users); aliases.Add("sections", sections); aliases.Add("settings", settings);