diff --git a/zero.Backoffice.UI/app/core/plugins.js b/zero.Backoffice.UI/app/core/plugins.js index 6033adb0..c3b2f775 100644 --- a/zero.Backoffice.UI/app/core/plugins.js +++ b/zero.Backoffice.UI/app/core/plugins.js @@ -1,5 +1 @@ -import zeroPlugin0 from '@zeroplugin0/plugin.js'; -import zeroPlugin1 from '@zeroplugin1/plugin.js'; -import zeroPlugin2 from '@zeroplugin2/plugin.js'; -import zeroPlugin3 from '@zeroplugin3/plugin.js'; -export default [ zeroPlugin0, zeroPlugin1, zeroPlugin2, zeroPlugin3 ]; \ No newline at end of file +export default [ ]; \ No newline at end of file diff --git a/zero.Backoffice.UI/vite.config.js b/zero.Backoffice.UI/vite.config.js index e6bb9b36..dbb17055 100644 --- a/zero.Backoffice.UI/vite.config.js +++ b/zero.Backoffice.UI/vite.config.js @@ -7,7 +7,8 @@ let loadedPlugins = JSON.parse(process.env.ZERO_PLUGINS || "[]"); if (!process.env.ZERO_PLUGINS) { - loadedPlugins = ["../zero.Commerce/Plugin", "../zero.Stories/Plugin", "../zero.Forms/Plugin", "../../Laola/Laola.Backoffice/Plugin"]; + //loadedPlugins = ["../zero.Commerce/Plugin", "../zero.Stories/Plugin", "../zero.Forms/Plugin", "../../Laola/Laola.Backoffice/Plugin"]; + loadedPlugins = []; } let zeroPlugins = []; diff --git a/zero.Backoffice/Configuration/BackofficeMaps.cs b/zero.Backoffice/Configuration/BackofficeMaps.cs new file mode 100644 index 00000000..a9279bca --- /dev/null +++ b/zero.Backoffice/Configuration/BackofficeMaps.cs @@ -0,0 +1,28 @@ +using AutoMapper; + +namespace zero.Backoffice.Configuration; + +/// +/// +/// +public class BackofficeMaps +{ + public MapperConfiguration MapperConfiguration { get; internal set; } + + internal List> Expressions { get; } + + + public void Configure(Action expression) + { + Expressions.Add(expression); + } + + + internal void Run(IMapperConfigurationExpression mapperConfig) + { + foreach (var expression in Expressions) + { + expression(mapperConfig); + } + } +} \ No newline at end of file diff --git a/zero.Backoffice/Configuration/BackofficeOptions.cs b/zero.Backoffice/Configuration/BackofficeOptions.cs index 80b3d59b..68c7b096 100644 --- a/zero.Backoffice/Configuration/BackofficeOptions.cs +++ b/zero.Backoffice/Configuration/BackofficeOptions.cs @@ -1,4 +1,6 @@ -using zero.Backoffice.Modules.Search; +using AutoMapper; +using System.Linq.Expressions; +using zero.Backoffice.Modules.Search; namespace zero.Backoffice.Configuration; @@ -38,4 +40,9 @@ public class BackofficeOptions /// Language ISO codes which are supported by the zero backoffice /// public string[] SupportedLanguages { get; internal set; } + + /// + /// Configuration for AutoMapper + /// + public BackofficeMaps Mapper { get; private set; } = new(); } \ No newline at end of file diff --git a/zero.Backoffice/Controllers/ZeroIndexController.cs b/zero.Backoffice/Controllers/ZeroIndexController.cs index 8ac275b8..56d8095d 100644 --- a/zero.Backoffice/Controllers/ZeroIndexController.cs +++ b/zero.Backoffice/Controllers/ZeroIndexController.cs @@ -4,7 +4,7 @@ namespace zero.Backoffice.Controllers; [ZeroAuthorize(false)] [DisableBrowserCache] -public abstract class ZeroIndexController : Controller +public class ZeroIndexController : Controller { IZeroVue ZeroVue { get; set; } IZeroOptions Options { get; set; } diff --git a/zero.Backoffice/Modules/Countries/CountriesController.cs b/zero.Backoffice/Modules/Countries/CountriesController.cs index 1993ac64..266a853b 100644 --- a/zero.Backoffice/Modules/Countries/CountriesController.cs +++ b/zero.Backoffice/Modules/Countries/CountriesController.cs @@ -63,7 +63,7 @@ public class CountriesController : ZeroBackofficeApiController } - [HttpGet] + [HttpGet("")] [ZeroAuthorize(CountryPermissions.Read)] public virtual async Task> Get(ListQuery query) { @@ -73,7 +73,7 @@ public class CountriesController : ZeroBackofficeApiController } - [HttpPost] + [HttpPost("")] [ZeroAuthorize(CountryPermissions.Create)] public virtual async Task> Create(CountrySave saveModel) { diff --git a/zero.Backoffice/Modules/Countries/CountryPermissions.cs b/zero.Backoffice/Modules/Countries/CountryPermissions.cs index b7c5e72b..be6598bb 100644 --- a/zero.Backoffice/Modules/Countries/CountryPermissions.cs +++ b/zero.Backoffice/Modules/Countries/CountryPermissions.cs @@ -2,25 +2,28 @@ public class CountryPermissions : PermissionProvider { - public CountryPermissions() : base("@settings.application.countries.name") { } - - - /// - public override IEnumerable Requires() => new[] { SectionPermissions.Settings, "zero.settings.country" }; - - public const string Create = "zero.settings.country.create"; - public const string Read = "zero.settings.country.read"; + public const string Read = "zero.settings.country.read"; public const string Update = "zero.settings.country.update"; public const string Delete = "zero.settings.country.delete"; - /// - public override IEnumerable GetPermissions() => new Permission[] - { - new(Create, "@permission.states.create"), - new(Read, "@permission.states.read"), - new(Update, "@permission.states.update"), - new(Delete, "@permission.states.delete") - }; + public override Task Configure(IPermissionContext context) + { + if (!context.TryGetGroup(Constants.Permissions.Groups.Settings, out PermissionGroup group)) + { + group.Permissions.Add(new Permission("zero.settings.country", "@settings.application.countries.name") + { + Children = new() + { + new(Create, "@permission.states.create"), + new(Read, "@permission.states.read"), + new(Update, "@permission.states.update"), + new(Delete, "@permission.states.delete") + } + }); + } + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/zero.Backoffice/Modules/ServiceCollectionExtensions.cs b/zero.Backoffice/Modules/ServiceCollectionExtensions.cs index acfb8dab..0cf4512b 100644 --- a/zero.Backoffice/Modules/ServiceCollectionExtensions.cs +++ b/zero.Backoffice/Modules/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using AutoMapper; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using zero.Backoffice.Modules.Countries; using zero.Backoffice.Modules.Pages; @@ -22,6 +23,14 @@ public static class ServiceCollectionExtensions opts.Indexes.Add(); }); + services.Configure(opts => + { + opts.Mapper.Configure(maps => + { + maps.AddProfile(); + }); + }); + return services; } } \ No newline at end of file diff --git a/zero.Backoffice/Plugin.cs b/zero.Backoffice/Plugin.cs index 3adec97f..e12445ed 100644 --- a/zero.Backoffice/Plugin.cs +++ b/zero.Backoffice/Plugin.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Hosting; +using AutoMapper; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using System.IO; +using zero.Backoffice.Modules; namespace zero.Backoffice; @@ -34,12 +36,14 @@ public class ZeroBackofficePlugin : ZeroPlugin //}); services.AddZeroBackofficeUIComposition(); + services.AddZeroBackofficeModules(configuration); //services.AddTransient(); //services.AddTransient(); //services.AddScoped(); + PostConfigureServices?.Invoke(services, configuration); } diff --git a/zero.Backoffice/ServiceCollectionExtensions.cs b/zero.Backoffice/ServiceCollectionExtensions.cs index fd1a617f..ec0089f5 100644 --- a/zero.Backoffice/ServiceCollectionExtensions.cs +++ b/zero.Backoffice/ServiceCollectionExtensions.cs @@ -6,7 +6,7 @@ public static class ServiceCollectionExtensions { public static ZeroBuilder AddBackoffice(this ZeroBuilder builder) where T : ZeroBackofficePlugin, IZeroPlugin, new() { - return builder.AddBackoffice(); + return builder.AddBackoffice(_ => { }); } public static ZeroBuilder AddBackoffice(this ZeroBuilder builder) @@ -27,9 +27,4 @@ public static class ServiceCollectionExtensions return plugin; }); } - - public static ZeroBuilder AddStories(this ZeroBuilder builder, Action options) - { - return builder.AddBackoffice(options); - } } \ No newline at end of file diff --git a/zero.Backoffice/UIComposition/Sections/SectionPermissions.cs b/zero.Backoffice/UIComposition/Sections/SectionPermissions.cs index 9a8d496d..869dc88c 100644 --- a/zero.Backoffice/UIComposition/Sections/SectionPermissions.cs +++ b/zero.Backoffice/UIComposition/Sections/SectionPermissions.cs @@ -2,22 +2,32 @@ public class SectionPermissions : PermissionProvider { - public SectionPermissions() : base("@permissions.collections.sections") { } - - public const string Dashboard = "zero.sections.dashboard"; - public const string Pages = "zero.sections.pages"; - public const string Spaces = "zero.sections.spaces"; - public const string Media = "zero.sections.media"; - public const string Settings = "zero.sections.settings"; + protected IEnumerable Sections { get; private set; } - /// - public override IEnumerable GetPermissions() => new Permission[] - { - new(Dashboard, "@sections.item.dashboard"), - new(Pages, "@sections.item.pages"), - new(Spaces, "@sections.item.spaces"), - new(Media, "@sections.item.media"), - new(Settings, "@sections.item.settings") - }; + public SectionPermissions(IEnumerable sections) + { + Sections = sections; + } + + + public override Task Configure(IPermissionContext context) + { + PermissionGroup group = new("zero.sections", "@permissions.collections.sections"); + + foreach (IBackofficeSection section in Sections) + { + Permission permission = new("zero.sections." + section.Alias, section.Name); + + foreach (IChildBackofficeSection child in section.Children) + { + Permission childPermission = new(permission.Key + "." + child.Alias, child.Name); + permission.Children.Add(childPermission); + } + + group.Add(permission); + } + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/zero.Backoffice/UIComposition/Settings/SettingsPermissions.cs b/zero.Backoffice/UIComposition/Settings/SettingsPermissions.cs index 3507659d..57b0a1a6 100644 --- a/zero.Backoffice/UIComposition/Settings/SettingsPermissions.cs +++ b/zero.Backoffice/UIComposition/Settings/SettingsPermissions.cs @@ -2,24 +2,24 @@ public class SettingsPermissions : PermissionProvider { - public SettingsPermissions() : base("@permissions.collections.settings") { } + //public SettingsPermissions() : base("@permissions.collections.settings") { } - public const string Dashboard = "zero.sections.dashboard"; - public const string Pages = "zero.sections.pages"; - public const string Spaces = "zero.sections.spaces"; - public const string Media = "zero.sections.media"; - public const string Settings = "zero.sections.settings"; + //public const string Dashboard = "zero.sections.dashboard"; + //public const string Pages = "zero.sections.pages"; + //public const string Spaces = "zero.sections.spaces"; + //public const string Media = "zero.sections.media"; + //public const string Settings = "zero.sections.settings"; - /// - public override IEnumerable GetPermissions() => new Permission[] - { - new(Dashboard, "@sections.item.dashboard"), - new(Pages, "@sections.item.pages"), - new(Spaces, "@sections.item.spaces"), - new(Media, "@sections.item.media"), - new(Settings, "@sections.item.settings") - }; + ///// + //public override IEnumerable GetPermissions() => new Permission[] + //{ + // new(Dashboard, "@sections.item.dashboard"), + // new(Pages, "@sections.item.pages"), + // new(Spaces, "@sections.item.spaces"), + // new(Media, "@sections.item.media"), + // new(Settings, "@sections.item.settings") + //}; } //namespace zero.Backoffice.UIComposition; diff --git a/zero.Core/Configuration/Constants.cs b/zero.Core/Configuration/Constants.cs index d99d12f9..00e8bf1c 100644 --- a/zero.Core/Configuration/Constants.cs +++ b/zero.Core/Configuration/Constants.cs @@ -13,7 +13,7 @@ public static class Constants { public const string SystemUser = "system"; public const string DefaultScheme = "zeroScheme"; - public const string BackofficeDisplayName = "Zero Bacckoffice"; + public const string BackofficeDisplayName = "Zero Backoffice"; public const string BackofficeScheme = "zeroBackoffice"; public const string BackofficeCookieName = "zero.be.session"; public const string DefaultCookieName = "zero.session"; @@ -34,10 +34,23 @@ public static class Constants } } + + public static class Permissions + { + public static class Groups + { + public const string Sections = "zero.permissions.groups.sections"; + public const string Spaces = "zero.permissions.groups.spaces"; + public const string Settings = "zero.permissions.groups.settings"; + public const string Pages = "zero.permissions.groups.pages"; + public const string Modules = "zero.permissions.groups.modules"; + } + } + + public static class Database { public const string ReservationPrefix = "zero."; - public const string CoreIdPrefix = "core."; public const string Expires = Raven.Client.Constants.Documents.Metadata.Expires; } diff --git a/zero.Core/Configuration/ServiceCollectionExtensions.cs b/zero.Core/Configuration/ServiceCollectionExtensions.cs index 8fe4f28a..bc1f6341 100644 --- a/zero.Core/Configuration/ServiceCollectionExtensions.cs +++ b/zero.Core/Configuration/ServiceCollectionExtensions.cs @@ -8,13 +8,14 @@ internal static class ServiceCollectionExtensions { public static IServiceCollection AddZeroConfiguration(this IServiceCollection services, IConfiguration config) { - services.AddOptions().Bind(config.GetSection("Zero")).Configure(opts => + services.AddOptions().Configure((opts, svc) => { + opts.ServiceProvider = svc; opts.Version = "0.0.1-alpha.1"; opts.ZeroPath = "/zero"; - opts.TokenExpiration = TimeSpan.FromHours(3); - }); - services.AddTransient(factory => factory.GetService>().CurrentValue); + opts.TokenExpiration = TimeSpan.FromHours(3); + }).Bind(config.GetSection("Zero")); + services.AddTransient(factory => factory.GetService>().Value); services.AddOptions().Bind(config.GetSection("Zero:Features")); diff --git a/zero.Core/Configuration/ZeroOptions.cs b/zero.Core/Configuration/ZeroOptions.cs index 1960ecf8..52ff5455 100644 --- a/zero.Core/Configuration/ZeroOptions.cs +++ b/zero.Core/Configuration/ZeroOptions.cs @@ -19,17 +19,11 @@ public class ZeroOptions : IZeroOptions public TimeSpan TokenExpiration { get; set; } - protected IServiceProvider ServiceProvider { get; set; } + internal IServiceProvider ServiceProvider { get; set; } protected ConcurrentDictionary OptionsCache { get; private set; } = new(); - public ZeroOptions(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } - - /// public TOptions For() where TOptions : class { diff --git a/zero.Core/Identity/Permissions/Permission.cs b/zero.Core/Identity/Permissions/Permission.cs index 554d13a8..d6e6e18a 100644 --- a/zero.Core/Identity/Permissions/Permission.cs +++ b/zero.Core/Identity/Permissions/Permission.cs @@ -14,11 +14,23 @@ public struct Permission /// public string Label { get; set; } + /// + /// When a permission is disabled it can not be updated anymore + /// + public bool IsDisabled { get; set; } + + /// + /// Child permissions + /// + public HashSet Children { get; set; } + public Permission(string key, string label) { Key = key; Label = label; + IsDisabled = false; + Children = new(); } ///// diff --git a/zero.Core/Identity/Permissions/PermissionContext.cs b/zero.Core/Identity/Permissions/PermissionContext.cs new file mode 100644 index 00000000..f5815f91 --- /dev/null +++ b/zero.Core/Identity/Permissions/PermissionContext.cs @@ -0,0 +1,80 @@ +namespace zero.Identity; + +public class PermissionContext : IPermissionContext +{ + /// + public HashSet Groups { get; protected set; } + + /// + public IZeroOptions Options { get; protected set; } + + + public PermissionContext(IZeroOptions options) + { + Groups = new(); + Options = options; + } + + + /// + public PermissionGroup? GetGroup(string key) + { + return Groups.FirstOrDefault(x => x.Key == key); + } + + + /// + public bool TryGetGroup(string key, out PermissionGroup group) + { + group = Groups.FirstOrDefault(x => x.Key == key); + return Groups.Contains(group); + } + + + /// + public void AddGroup(PermissionGroup group) + { + Groups.Add(group); + } + + + /// + public void RemoveGroup(PermissionGroup group) + { + Groups.Remove(group); + } +} + + +public interface IPermissionContext +{ + /// + /// All registered permission groups + /// + HashSet Groups { get; } + + /// + /// Zero options + /// + IZeroOptions Options { get; } + + /// + /// Try to get a permission group by key + /// + PermissionGroup? GetGroup(string key); + + /// + /// Get permission group by key + /// + bool TryGetGroup(string key, out PermissionGroup group); + + /// + /// Add a new permission group to the context + /// + void AddGroup(PermissionGroup group); + + /// + /// Remove a permission group from the context + /// + void RemoveGroup(PermissionGroup group); +} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/PermissionGroup.cs b/zero.Core/Identity/Permissions/PermissionGroup.cs new file mode 100644 index 00000000..50375634 --- /dev/null +++ b/zero.Core/Identity/Permissions/PermissionGroup.cs @@ -0,0 +1,63 @@ +namespace zero.Identity; + +public struct PermissionGroup +{ + /// + /// Full key of the group + /// + public string Key { get; set; } + + /// + /// Title of this permission group for output + /// + public string Label { get; set; } + + /// + /// Permissions for this group + /// + public HashSet Permissions { get; set; } + + + public PermissionGroup(string key, string label) + { + Key = key; + Label = label; + Permissions = new(); + } + + + /// + /// Add a new permission to this group + /// + public void Add(Permission permission) + { + Permissions.Add(permission); + } + + + /// + /// Remove a permission + /// + public void Remove(Permission permission) + { + Permissions.Remove(permission); + } + + + /// + /// Get permission by key + /// + public Permission? Get(string key) + { + return Permissions.FirstOrDefault(x => x.Key == key); + } + + + /// + /// Get permission by key + /// + public object this[string key] + { + get => Permissions.FirstOrDefault(x => x.Key == key); + } +} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/PermissionProvider.cs b/zero.Core/Identity/Permissions/PermissionProvider.cs index 2769a58f..b5b9449f 100644 --- a/zero.Core/Identity/Permissions/PermissionProvider.cs +++ b/zero.Core/Identity/Permissions/PermissionProvider.cs @@ -3,47 +3,19 @@ public abstract class PermissionProvider : IPermissionProvider { /// - public string Name { get; private set; } - - - public PermissionProvider(string name) - { - Name = name; - } - - /// - public virtual IEnumerable Requires() => Array.Empty(); - - /// - public virtual IEnumerable GetPermissions() => Array.Empty(); - - - /// - public virtual Task> GetPermissionsAsync() => Task.FromResult(GetPermissions()); + public virtual Task Configure(IPermissionContext context) => Task.CompletedTask; } public interface IPermissionProvider { /// - /// Name (can be a localization) which is displayed in the permission group + /// Manage permissions (add/remove/update permission groups and containing permissions) /// - string Name { get; } + Task Configure(IPermissionContext context); /// /// In order for this permissions to work, the specified provider requires the following permission /// - IEnumerable Requires() => Array.Empty(); - - /// - /// Get all permissions for this provider. - /// These permissions can be granted to roles or individual users. - /// - IEnumerable GetPermissions(); - - /// - /// Get all permissions for this provider. - /// These permissions can be granted to roles or individual users. - /// - Task> GetPermissionsAsync(); + //IEnumerable Requires() => Array.Empty(); } \ No newline at end of file diff --git a/zero.Core/Identity/ServiceCollectionExtensions.cs b/zero.Core/Identity/ServiceCollectionExtensions.cs index 76ff8c85..f0019b4a 100644 --- a/zero.Core/Identity/ServiceCollectionExtensions.cs +++ b/zero.Core/Identity/ServiceCollectionExtensions.cs @@ -22,8 +22,8 @@ internal static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); - services.AddAuthentication(Constants.Auth.BackofficeScheme); - //.AddZeroBackofficeCookie(); // TODO + services.AddAuthentication(Constants.Auth.BackofficeScheme) + .AddZeroBackofficeCookie(); services.AddAuthorization(); return services; diff --git a/zero.Core/Identity/ZeroIdentityExtensions.cs b/zero.Core/Identity/ZeroIdentityExtensions.cs index 8c2c29be..c3733419 100644 --- a/zero.Core/Identity/ZeroIdentityExtensions.cs +++ b/zero.Core/Identity/ZeroIdentityExtensions.cs @@ -12,7 +12,7 @@ public static class ZeroIdentityExtensions { services.AddZeroIdentityCore(); - IdentityBuilder builder = new IdentityBuilder(typeof(TUser), typeof(TRole), services); + IdentityBuilder builder = new(typeof(TUser), typeof(TRole), services); builder.AddDefaultTokenProviders(); @@ -33,7 +33,7 @@ public static class ZeroIdentityExtensions { services.AddZeroIdentityCore(); - IdentityBuilder builder = new IdentityBuilder(typeof(TUser), services); + IdentityBuilder builder = new(typeof(TUser), services); builder.AddDefaultTokenProviders(); builder.AddUserStore>(); diff --git a/zero.Core/ZeroBuilder.cs b/zero.Core/ZeroBuilder.cs index bb32b1d6..7e71990f 100644 --- a/zero.Core/ZeroBuilder.cs +++ b/zero.Core/ZeroBuilder.cs @@ -28,6 +28,8 @@ public class ZeroBuilder StartupOptions.AssemblyDiscoveryRules.Add(new ZeroAssemblyDiscoveryRule()); setupAction?.Invoke(StartupOptions); + services.AddControllers(); + // adds and discovers additional and built-in assemblies new AssemblyDiscovery(Mvc).Execute(StartupOptions.AssemblyDiscoveryRules);