Skip to content

Commit

Permalink
Add permission support for menu items (#17263)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek authored Jan 8, 2025
1 parent 022aaf9 commit abbd3e8
Show file tree
Hide file tree
Showing 39 changed files with 758 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using OrchardCore.AdminMenu.Services;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Navigation;
using OrchardCore.Security.Permissions;

namespace OrchardCore.AdminMenu.AdminNodes;

public sealed class LinkAdminNodeDriver : DisplayDriver<MenuItem, LinkAdminNode>
{
private readonly IAdminMenuPermissionService _adminMenuPermissionService;
private readonly IPermissionService _permissionService;

public LinkAdminNodeDriver(IAdminMenuPermissionService adminMenuPermissionService)
public LinkAdminNodeDriver(IPermissionService permissionService)
{
_adminMenuPermissionService = adminMenuPermissionService;
_permissionService = permissionService;
}

public override Task<IDisplayResult> DisplayAsync(LinkAdminNode treeNode, BuildDisplayContext context)
Expand All @@ -31,22 +31,23 @@ public override IDisplayResult Edit(LinkAdminNode treeNode, BuildEditorContext c
model.IconClass = treeNode.IconClass;
model.Target = treeNode.Target;

var permissions = await _adminMenuPermissionService.GetPermissionsAsync();

var selectedPermissions = permissions.Where(p => treeNode.PermissionNames.Contains(p.Name));
var selectedPermissions = await _permissionService.FindByNamesAsync(treeNode.PermissionNames);

model.SelectedItems = selectedPermissions
.Select(p => new PermissionViewModel
{
Name = p.Name,
DisplayText = p.Description
}).ToList();
}).ToArray();

var permissions = await _permissionService.GetPermissionsAsync();

model.AllItems = permissions
.Select(p => new PermissionViewModel
{
Name = p.Name,
DisplayText = p.Description
}).ToList();
}).ToArray();
}).Location("Content");
}

Expand All @@ -69,10 +70,8 @@ await context.Updater.TryUpdateModelAsync(model, Prefix,
? []
: model.SelectedPermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries);

var permissions = await _adminMenuPermissionService.GetPermissionsAsync();
treeNode.PermissionNames = permissions
.Where(p => selectedPermissions.Contains(p.Name))
.Select(p => p.Name).ToArray();
var permissions = await _permissionService.FindByNamesAsync(selectedPermissions);
treeNode.PermissionNames = permissions.Select(p => p.Name).ToArray();

return Edit(treeNode, context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
using OrchardCore.Admin;
using OrchardCore.AdminMenu.Services;
using OrchardCore.Navigation;
using OrchardCore.Security.Permissions;

namespace OrchardCore.AdminMenu.AdminNodes;

public class LinkAdminNodeNavigationBuilder : IAdminNodeNavigationBuilder
{
private readonly ILogger _logger;
private readonly IAdminMenuPermissionService _adminMenuPermissionService;
private readonly IPermissionService _permissionService;
private readonly AdminOptions _adminOptions;


public LinkAdminNodeNavigationBuilder(
IAdminMenuPermissionService adminMenuPermissionService,
IPermissionService permissionService,
IOptions<AdminOptions> adminOptions,
ILogger<LinkAdminNodeNavigationBuilder> logger)
{
_adminMenuPermissionService = adminMenuPermissionService;
_permissionService = permissionService;
_adminOptions = adminOptions.Value;
_logger = logger;
}
Expand Down Expand Up @@ -61,11 +61,8 @@ public Task BuildNavigationAsync(MenuItem menuItem, NavigationBuilder builder, I

if (node.PermissionNames.Length > 0)
{
var permissions = await _adminMenuPermissionService.GetPermissionsAsync();

// Find the actual permissions and apply them to the menu.
var selectedPermissions = permissions.Where(p => node.PermissionNames.Contains(p.Name));
itemBuilder.Permissions(selectedPermissions);
itemBuilder.Permissions(await _permissionService.FindByNamesAsync(node.PermissionNames));
}

// Add adminNode's IconClass property values to menuItem.Classes.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using OrchardCore.AdminMenu.Services;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Navigation;
using OrchardCore.Security.Permissions;

namespace OrchardCore.AdminMenu.AdminNodes;

public sealed class PlaceholderAdminNodeDriver : DisplayDriver<MenuItem, PlaceholderAdminNode>
{
private readonly IAdminMenuPermissionService _adminMenuPermissionService;
private readonly IPermissionService _permissionService;

public PlaceholderAdminNodeDriver(IAdminMenuPermissionService adminMenuPermissionService)
public PlaceholderAdminNodeDriver(IPermissionService permissionService)
{
_adminMenuPermissionService = adminMenuPermissionService;
_permissionService = permissionService;
}

public override Task<IDisplayResult> DisplayAsync(PlaceholderAdminNode treeNode, BuildDisplayContext context)
Expand All @@ -29,22 +29,23 @@ public override IDisplayResult Edit(PlaceholderAdminNode treeNode, BuildEditorCo
model.LinkText = treeNode.LinkText;
model.IconClass = treeNode.IconClass;

var permissions = await _adminMenuPermissionService.GetPermissionsAsync();

var selectedPermissions = permissions.Where(p => treeNode.PermissionNames.Contains(p.Name));
var selectedPermissions = await _permissionService.FindByNamesAsync(treeNode.PermissionNames);

model.SelectedItems = selectedPermissions
.Select(p => new PermissionViewModel
{
Name = p.Name,
DisplayText = p.Description
}).ToList();
}).ToArray();

var permissions = await _permissionService.GetPermissionsAsync();

model.AllItems = permissions
.Select(p => new PermissionViewModel
{
Name = p.Name,
DisplayText = p.Description
}).ToList();
}).ToArray();
}).Location("Content");
}

Expand All @@ -59,11 +60,13 @@ await context.Updater.TryUpdateModelAsync(model, Prefix,
treeNode.LinkText = model.LinkText;
treeNode.IconClass = model.IconClass;

var selectedPermissions = (model.SelectedPermissionNames == null ? Array.Empty<string>() : model.SelectedPermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries));
var permissions = await _adminMenuPermissionService.GetPermissionsAsync();
treeNode.PermissionNames = permissions
.Where(p => selectedPermissions.Contains(p.Name))
.Select(p => p.Name).ToArray();
var selectedPermissions =
model.SelectedPermissionNames == null
? []
: model.SelectedPermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries);

var permissions = await _permissionService.FindByNamesAsync(selectedPermissions);
treeNode.PermissionNames = permissions.Select(p => p.Name).ToArray();

return Edit(treeNode, context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
using Microsoft.Extensions.Logging;
using OrchardCore.AdminMenu.Services;
using OrchardCore.Navigation;
using OrchardCore.Security.Permissions;

namespace OrchardCore.AdminMenu.AdminNodes;

public class PlaceholderAdminNodeNavigationBuilder : IAdminNodeNavigationBuilder
{
private readonly ILogger _logger;
private readonly IAdminMenuPermissionService _adminMenuPermissionService;
private readonly IPermissionService _permissionService;

public PlaceholderAdminNodeNavigationBuilder(IAdminMenuPermissionService adminMenuPermissionService, ILogger<PlaceholderAdminNodeNavigationBuilder> logger)
public PlaceholderAdminNodeNavigationBuilder(
IPermissionService permissionService,
ILogger<PlaceholderAdminNodeNavigationBuilder> logger)
{
_adminMenuPermissionService = adminMenuPermissionService;
_permissionService = permissionService;
_logger = logger;
}

Expand All @@ -34,10 +37,8 @@ public Task BuildNavigationAsync(MenuItem menuItem, NavigationBuilder builder, I

if (node.PermissionNames.Length > 0)
{
var permissions = await _adminMenuPermissionService.GetPermissionsAsync();
// Find the actual permissions and apply them to the menu.
var selectedPermissions = permissions.Where(p => node.PermissionNames.Contains(p.Name));
itemBuilder.Permissions(selectedPermissions);
itemBuilder.Permissions(await _permissionService.FindByNamesAsync(node.PermissionNames));
}

// Add adminNode's IconClass property values to menuItem.Classes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,17 @@

namespace OrchardCore.AdminMenu.Services;

[Obsolete("This service is obsolete and will be removed in version 4. Instead, please use IPermissionService")]
public sealed class AdminMenuPermissionService : IAdminMenuPermissionService
{
private readonly IEnumerable<IPermissionProvider> _permissionProviders;
private readonly IPermissionService _permissionService;

// Cached per request.
private List<Permission> _permissions;

public AdminMenuPermissionService(IEnumerable<IPermissionProvider> permissionProviders)
public AdminMenuPermissionService(IPermissionService permissionService)
{
_permissionProviders = permissionProviders;
_permissionService = permissionService;
}

public async Task<IEnumerable<Permission>> GetPermissionsAsync()
{
if (_permissions != null)
{
return _permissions;
}

_permissions = [];

foreach (var permissionProvider in _permissionProviders)
{
var permissions = await permissionProvider.GetPermissionsAsync();

_permissions.AddRange(permissions);
}

return _permissions;
}
=> await _permissionService.GetPermissionsAsync();
}

3 changes: 3 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.AdminMenu/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ public override void ConfigureServices(IServiceCollection services)
{
services.AddPermissionProvider<Permissions>();
services.AddNavigationProvider<AdminMenu>();

#pragma warning disable CS0618 // Type or member is obsolete
services.AddScoped<IAdminMenuPermissionService, AdminMenuPermissionService>();
#pragma warning restore CS0618 // Type or member is obsolete

services.AddScoped<IAdminMenuService, AdminMenuService>();
services.AddScoped<AdminMenuNavigationProvidersCoordinator>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,19 @@
<div class="mb-3">
<div class="w-xl-50">
<input asp-for="SelectedPermissionNames" type="hidden" v-model="selectedNames" />
<vue-multiselect v-model="value"
:options="options"
track-by="name"
label="displayText"
placeholder="@T["Type to search"]"
@@select="onSelect"
:searchable="true"
:close-on-select="true"
:reset-after="true"
:show-labels="true"
:hide-selected="false"
select-label="@T["Select"]"
deselect-label="@T["Remove"]">
<vue-multiselect v-model="value"
:options="options"
track-by="name"
label="displayText"
placeholder="@T["Type to search"]"
@@select="onSelect"
:searchable="true"
:close-on-select="true"
:reset-after="true"
:show-labels="true"
:hide-selected="false"
select-label="@T["Select"]"
deselect-label="@T["Remove"]">
<template slot="option" slot-scope="props">
<div v-cloak><span>{{ props.option.displayText }}</span></div>
</template>
Expand All @@ -137,7 +137,11 @@
</div>
</div>
</div>
<script at="Foot" depends-on="admin-menu-permission-picker">initAdminMenuPermissionsPicker(document.querySelector("#PermissionPicker"))</script>
<script at="Foot" depends-on="admin-menu-permission-picker">
document.addEventListener('DOMContentLoaded', () => {
initAdminMenuPermissionsPicker(document.querySelector("#PermissionPicker"));
});
</script>
</div>

<script at="Foot">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@
</div>
</div>
</div>
<script at="Foot" depends-on="admin-menu-permission-picker">initAdminMenuPermissionsPicker(document.querySelector("#PermissionPicker"))</script>
<script at="Foot" depends-on="admin-menu-permission-picker">
document.addEventListener('DOMContentLoaded', () => {
initAdminMenuPermissionsPicker(document.querySelector("#PermissionPicker"))
});
</script>
</div>

<script at="Foot">
Expand Down
12 changes: 12 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Menu/Assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,17 @@
"Assets/js/activate-links.js"
],
"output": "wwwroot/Scripts/activate-links.js"
},
{
"inputs": [
"Assets/scss/menu-permission-picker.scss"
],
"output": "wwwroot/Styles/menu-permission-picker.css"
},
{
"inputs": [
"Assets/js/menu-permission-picker.js"
],
"output": "wwwroot/Scripts/menu-permission-picker.js"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
function initMenuPermissionsPicker(element) {
// only run script if element exists
if (element) {
var elementId = element.id;
var selectedItems = JSON.parse(element.dataset.selectedItems || "[]");
var allItems = JSON.parse(element.dataset.allItems || "[]");

var vueMultiselect = Vue.component('vue-multiselect', window.VueMultiselect.default);

var vm = new Vue({
el: '#' + elementId,
components: { 'vue-multiselect': vueMultiselect },
data: {
value: null,
arrayOfItems: selectedItems,
options: allItems,
},
computed: {
selectedNames: function () {
return this.arrayOfItems.map(function (x) { return x.name }).join(',');
}
},
methods: {
onSelect: function (selectedOption, name) {
var self = this;

for (i = 0; i < self.arrayOfItems.length; i++) {
if (self.arrayOfItems[i].name === selectedOption.name) {
return;
}
}

self.arrayOfItems.push(selectedOption);
},
remove: function (item) {
this.arrayOfItems.splice(this.arrayOfItems.indexOf(item), 1)
}
}
})

/*Hook for other scripts that might want to have access to the view model*/
var event = new CustomEvent("menu-permission-picker-created", { detail: { vm: vm } });
document.querySelector("body").dispatchEvent(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.vue-multiselect .multiselect--active {
z-index: var(--bs-dropdown-zindex, 1000);
}

[v-cloak] {
display: none;
}
Loading

0 comments on commit abbd3e8

Please sign in to comment.