diff --git a/Classes/Controller/TerminalCommandController.php b/Classes/Controller/TerminalCommandController.php index cf65105..1a4d9a6 100644 --- a/Classes/Controller/TerminalCommandController.php +++ b/Classes/Controller/TerminalCommandController.php @@ -79,6 +79,11 @@ class TerminalCommandController extends ActionController public function getCommandsAction(): void { + if (!$this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess')) { + $this->view->assign('value', ['success' => false, 'result' => []]); + return; + } + $commandNames = $this->terminalCommandService->getCommandNames(); $availableCommandNames = array_filter($commandNames, function ($commandName) { diff --git a/Configuration/Policy.yaml b/Configuration/Policy.yaml index 5faa65b..dfc0d75 100755 --- a/Configuration/Policy.yaml +++ b/Configuration/Policy.yaml @@ -3,6 +3,9 @@ privilegeTargets: 'Shel.Neos.Terminal:ExecuteCommands': matcher: 'method(Shel\Neos\Terminal\Controller\TerminalCommandController->(?!initialize).*Action())' + 'Shel.Neos.Terminal:GetCommands': + matcher: 'method(Shel\Neos\Terminal\Controller\TerminalCommandController->getCommandsAction())' + 'Shel\Neos\Terminal\Security\TerminalCommandPrivilege': 'Shel.Neos.Terminal:Command.All': matcher: '*' @@ -16,6 +19,13 @@ privilegeTargets: matcher: 'nodeRepair' roles: + 'Neos.Flow:Everybody': + privileges: + # Allow everybody to load commands to prevent 403 errors for users without access in the UI. + # The command list will still be empty in the response as all commands have their own privileges. + - privilegeTarget: 'Shel.Neos.Terminal:GetCommands' + permission: GRANT + 'Shel.Neos.Terminal:TerminalUser': label: 'Terminal user' description: 'Grants access to run read-only eel and search terminal commands' @@ -26,6 +36,7 @@ roles: permission: GRANT - privilegeTarget: 'Shel.Neos.Terminal:Command.Search' permission: GRANT + 'Neos.Neos:Administrator': privileges: - privilegeTarget: 'Shel.Neos.Terminal:ExecuteCommands' diff --git a/Resources/Private/JavaScript/Terminal/src/manifest.js b/Resources/Private/JavaScript/Terminal/src/manifest.js index e21a766..5b679d1 100755 --- a/Resources/Private/JavaScript/Terminal/src/manifest.js +++ b/Resources/Private/JavaScript/Terminal/src/manifest.js @@ -17,6 +17,7 @@ manifest('Shel.Neos.Terminal:Terminal', {}, (globalRegistry, { store, frontendCo globalRegistry.get('reducers').set('Shel.Neos.Terminal', { reducer }); globalRegistry.get('containers').set('PrimaryToolbar/Middle/Terminal', Terminal); + // TODO: Don't register hotkey if terminal is not accessible for the current user if (frontendConfiguration.hotkeys !== null && frontendConfiguration.hotkeys.length !== 0) { globalRegistry.get('hotkeys').set('Shel.Neos.Terminal.toggle', { description: 'Toggle Neos Terminal', @@ -24,7 +25,7 @@ manifest('Shel.Neos.Terminal:Terminal', {}, (globalRegistry, { store, frontendCo }); } - // Register commands for command bar if installed + // Register commands for command bar if installed and commands are available const commandBarRegistry = globalRegistry.get('Shel.Neos.CommandBar'); if (commandBarRegistry) { commandBarRegistry.set('plugins/terminal', terminalCommandRegistry.getCommandsForCommandBar); diff --git a/Resources/Private/JavaScript/Terminal/src/registry/TerminalCommandRegistry.tsx b/Resources/Private/JavaScript/Terminal/src/registry/TerminalCommandRegistry.tsx index 3fec1fb..f037c1f 100644 --- a/Resources/Private/JavaScript/Terminal/src/registry/TerminalCommandRegistry.tsx +++ b/Resources/Private/JavaScript/Terminal/src/registry/TerminalCommandRegistry.tsx @@ -26,7 +26,7 @@ class TerminalCommandRegistry { private commands: CommandList; private loading = false; - public getCommands = async () => { + public getCommands = async (): Promise => { // Wait for commands to be loaded if another call already requested them let i = 0; while (this.loading) { @@ -56,29 +56,31 @@ class TerminalCommandRegistry { return this.i18nRegistry.translate(id, fallback, params, packageKey, sourceName); }; - public getCommandsForCommandBar = async () => { + public getCommandsForCommandBar = async (): Promise> => { const commands = await this.getCommands(); const invokeCommand = this.invokeCommand; - return { - 'shel.neos.terminal': { - name: 'Terminal', - description: 'Execute terminal commands', - icon: 'terminal', - subCommands: Object.values(commands).reduce((acc, { name, description }) => { - acc[name] = { - name, - icon: 'terminal', - description: this.translate(description), - action: async function* (arg) { - yield* invokeCommand(name, arg); - }, - canHandleQueries: true, - executeManually: true, - }; - return acc; - }, {}), - }, - }; + return Object.keys(commands).length > 0 + ? { + 'shel.neos.terminal': { + name: 'Terminal', + description: 'Execute terminal commands', + icon: 'terminal', + subCommands: Object.values(commands).reduce((acc, { name, description }) => { + acc[name] = { + name, + icon: 'terminal', + description: this.translate(description), + action: async function* (arg) { + yield* invokeCommand(name, arg); + }, + canHandleQueries: true, + executeManually: true, + }; + return acc; + }, {}), + }, + } + : {}; }; public invokeCommand = async function* (commandName: string, arg = '') {