How can I give a Grav user, access to manage only one particular plugin?

Hello to all.
My Grav users are not technical, so I provide a reduced set of option in the Grav Admin Panel.

In Configuration I only give access to the Site tab. I do not provide access to other Configuration tabs nor plugins or themes because I do not want to repair a messy website, caused by a possible incorrect use of all Grav power.

But now I have to provide access to the configuration of a particular plugin, so how can I give access only to that particular plugin and not all plugins?, since the Accounts option on Admin Panel only gives me the possibility to toggle Denied or Allowed for the Manage Plugins option, this means give access to control all or none plugins but not one specific plugin only. So how can I give a Grav user, access to manage only one particular plugin?.

By the way, I tried in file: user/accounts/user2.yaml with these plugins permissions setup:

access:
  site:
    login: true
  admin:
    login: true
    super: true
    cache: true
    configuration:
      system: false
      site: true
      media: false
      security: false
      info: false
      pages: true
      users: false
    pages: true
    maintenance: false
    statistics: true
    plugins:
      Admin Panel: false
      Archives: false
      Breadcrumbs: false
      DevTools: false
      Email: false
      Error: false
      Feed: false
      Flex Objects: false
      Form: false
      Login: false
      Pagination: false
      Problems: false
      Random: false
      Related Pages: false
      SimpleSearch: false
      Sitemap: false
      APlugin: true
      Taxonomy List: false
    themes: false
    tools: false
    users: false
    flex-objects: false

But it gave the user access to all plugins
Thanks and regards.

@joejac, AFAIK, Admin does not provide fine-grained permissions. But a plugin might come to the rescue…

The plugin:

  • does not prevent a user to see the list of plugins,
  • does not prevent the user to open a plugin.
  • does prevent a user from saving any changes for all plugins, except for the one required.

Since you said “[…] how can I give a Grav user, access to […]” I assume you want to give permission to only a subset of Admin users.

  • Create a group with any name you want.
  • Assign group to one or more users.
  • Create plugin ‘plugin-guard’ using $ bin/plugin devtools new-plugin
  • Subscribe to event ‘onAdminSave’
    if ($this->isAdmin()) {
      $this->enable([
        'onAdminSave' => ['onAdminSave', 0],
      ]);
    
      return;
    }
    
  • Add the following functions:
    public function onAdminSave(Event $event)
    {
      /** @var Data */
      $data = $event['object'];
    
      $blueprints = $data->blueprints();
      $type = $blueprints['type'];
      $slug = $blueprints['slug'];
      $name = $blueprints['name'];
    
      if ($type === 'plugin' && ($slug !== 'devtools' || ($slug === 'devtools' && !$this->isGroupMember()))) {
        $this->grav['messages']->add("No permission to save plugin $name", 'error');
        $url = $_SERVER['HTTP_REFERER'];
        $this->grav->redirect($url);
      }
    }
    
    protected function isGroupMember(): bool
    {
      /** @var User */
      $user = $this->grav['user'];
      $groups = $user->get('groups');
    
      return $groups && in_array('plugin-user', $groups);
    }
    
  • Replace ‘devtools’ with the name of the plugin you want to give access to
  • Replace ‘plugin-user’ with the name of the group you’ve created.
  • If access permission is for all users and not a group:
    • Remove function isGroupMember
    • Replace if-statement with if ($type === 'plugin' && $slug !== 'devtools')

Improvements:

  • To add some flexibility, add the required plugin, or plugins as a setting in plugin-guard.yaml.
1 Like

@joejac, Alternatively, you could create a plugin which injects some JavaScript into the page which removes all plugins except the required one.

The plugin:

  • does not prevent a user to type in the url of a plugin eg. domain/admin/plugin/devtools
  • does not prevent the user to save the plugin when using direct url
  • does show only the required plugin in the list of plugins.

Try the following:

  • Create plugin ‘plugin-guard’ using $ bin/plugin devtools new-plugin
  • Subscribe to event ‘onAssetsInitialized’
    if ($this->isAdmin()) {
      $this->enable([
        'onAssetsInitialized' => ['onAssetsInitialized', 0]
      ]);
    
      return;
    }
    
  • Add the following function:
    public function onAssetsInitialized(Event $event)
    {
      $url = $_SERVER['REQUEST_URI'];
    
      if (str_ends_with($url, '/admin/plugins')) {
        $this->grav['assets']->addInlineJs(
          "
          const plugins = document.body.querySelectorAll('[data-gpm-plugin]');
    
          plugins.forEach((plugin) => {
             if (plugin.getAttribute('data-gpm-plugin') !== 'devtools') {
                plugin.remove();
             }
          });
          ",
          ['group' => 'bottom']
        );
      }
    }
    
  • Replace ‘devtools’ with the name of the plugin you want to keep in the list of plugins.

Improvements:

  • To add some flexibility, add the required plugin, or plugins as a setting in plugin-guard.yaml.
  • You could also decide to only clear the list of plugins for users who are not part of a specific group.
  • Of course, you can mix and match with previous suggestion.
2 Likes

Hello pamtbaau, and thanks, I would say “pamtbaau comes to the rescue…” :sweat_smile:

  1. I followed your first proposal, but I had to upgrade composer to version 2.4.4 since version 1.5.2, does not run with PHP 8.1.12. Then, I was able to create the “plugin-guard” :man_police_officer:, I followed all the steps above, and I created a group called “administradores” with the option “Manage Plugins” Allowed and assigned this group to the user, but the plugin:

  2. Allows to enable any plugin, and should not enable or disable any plugin except the plugin allowed on the plugin-guard.php file.

  3. Does not save changes on any plugin, that is good, but does not allow to change any settings in the allowed plugin either.

  4. After clicking the save button on any plugin, the browser shows a blank page, if I hit enter on the URL it refresh the page and shows the current plugin page well, but with no change saved on the allowed plugin.

  5. This is the php file of the plugin: plugin-guard.php allowing my-plugin

<?php
namespace Grav\Plugin;

use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;

/**
 * Class PluginGuardPlugin
 * @package Grav\Plugin
 */
class PluginGuardPlugin extends Plugin
{
    /**
     * @return array
     *
     * The getSubscribedEvents() gives the core a list of events
     *     that the plugin wants to listen to. The key of each
     *     array section is the event that the plugin listens to
     *     and the value (in the form of an array) contains the
     *     callable (or function) as well as the priority. The
     *     higher the number the higher the priority.
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onPluginsInitialized' => [
                // Uncomment following line when plugin requires Grav < 1.7
                // ['autoload', 100000],
                ['onPluginsInitialized', 0]
            ]
        ];
    }

    /**
     * Composer autoload
     *
     * @return ClassLoader
     */
    public function autoload(): ClassLoader
    {
        return require __DIR__ . '/vendor/autoload.php';
    }

    /**
     * Initialize the plugin
     */
    public function onPluginsInitialized(): void
    {
        // Proceed if we are in the admin plugin
        if ($this->isAdmin()) {
          $this->enable([
            'onAdminSave' => ['onAdminSave', 0],
          ]);
          
          return;
        }

        // Enable the main events we are interested in
        $this->enable([
            // Put your main events here
        ]);
    }
    
    public function onAdminSave(Event $event)
{
  /** @var Data */
  $data = $event['object'];

  $blueprints = $data->blueprints();
  $type = $blueprints['type'];
  $slug = $blueprints['slug'];
  $name = $blueprints['name'];

  if ($type === 'plugin' && ($slug !== 'my-plugin' || ($slug === 'my-plugin' && !$this->isGroupMember()))) {
    $this->grav['messages']->add("No tiene permisos para salvar cambios en el plugin $name", 'error');
    $url = $_SERVER['HTTP_REFERER'];
    $this->grav->redirect($url);
  }
}

protected function isGroupMember(): bool
{
  /** @var User */
  $user = $this->grav['user'];
  $groups = $user->get('groups');

  return $groups && in_array('administradores', $groups);
}
}

I do not know how to make it work, or if I made a mistake, sorry.
Regards
joejac

@joejac

  1. Allows to enable any plugin, and should not enable or disable any plugin except the plugin allowed on the plugin-guard.php file.

You didn’t ask for that… :wink:

Anyway, try the following in onPluginsInitialized:

if ($this->isAdmin()) {
  /** @var Route */
  $routeObject = $this->grav['route'];
  $route = $routeObject->getRoute();
  $task = $this->grav['task'] ?? $this->grav['action'];
  $tasks = ['enable', 'disable'];

  $isPluginsPage = str_starts_with($route, '/admin/plugins');
  $isDevTools = $route ===  '/admin/plugins/devtools';
  $isTask = in_array($task, $tasks);

  if ($isPluginsPage && $isTask && (!$isDevTools || ($isDevTools && !$this->isGroupMember()))) {
    $this->grav['messages']->add("No permission to enable/disable plugin", 'error');
    $url = $_SERVER['HTTP_REFERER'];
    $this->grav->redirect($url);
  }

  $this->enable([
    ...etc.

Again, replace ‘devtools’ with the slug of your plugin.

  1. Does not save changes on any plugin, that is good, but does not allow to change any settings in the allowed plugin either.
  2. After clicking the save button on any plugin, the browser shows a blank page, […]

Sorry, but I cannot reproduce this using my own plugin. Changes are being saved and no blank page.

I would suggest:

  • Check any error log
  • Fire-up the debugger, check variables and see what happens.
  • Try other proposed solution.
1 Like

Thanks @pamtbaau and sorry for not being more specific.
I included your last lines on onPluginsInitialized, but I got an error related to the event, that I can not resolve. Here is the information:

  1. Server error log: (I double checked permissions and ownership on all the Grav installation and they are fine)
[Mon Nov 21 01:15:40.245429 2022] [fcgid:warn] [pid 5367] (104)Connection reset by peer: [client IP:21760] mod_fcgid: error reading data from FastCGI server
[Mon Nov 21 01:15:40.245494 2022] [core:error] [pid 5367] [client IP:21760] End of script output before headers: index.php
  1. Grav Error log, only the last error line, because they are very long, and Discourse forum complained:
[2022-11-21 11:49:14] grav.CRITICAL: An exception has been thrown during the rendering of a template ("Unable to write in the cache directory (/home/cliente/public_html/enew/cache/twig/34)."). - Trace: #0 /home/cliente/public_html/enew/cache/twig/6a/6afe0e59b97cbb86a2ba64e12fa304bb8ea37921cd63562a55aea3c1a1d5f6fb.php(220): Twig\Template->displayBlock() #1 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_7c430519b1f1129042e4f8a62687105459661805e27de8a0a801e876f4ef37df->block_content() #2 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(175): Twig\Template->displayBlock() #3 /home/cliente/public_html/enew/cache/twig/d8/d80a475daf4a4a9cba86a6484ad82b0a26c9bc805fd9664a88be4c0d7b5a35b0.php(563): Twig\Template->displayParentBlock() #4 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_17a4b274669635bdff13f76970ff86aa2b7b93121488c0f0a733ac235d0f12ee->block_content() #5 /home/cliente/public_html/enew/cache/twig/4b/4b955d564f1d0b952d13c37bcfa22f8d8d598a1f3cae18e9b09093195ccb894b.php(375): Twig\Template->displayBlock() #6 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_fc4c01926f3fd7d3e286e806e760849f1618bf3693cbcbf23197e260f0ddadd9->block_content_wrapper() #7 /home/cliente/public_html/enew/cache/twig/4b/4b955d564f1d0b952d13c37bcfa22f8d8d598a1f3cae18e9b09093195ccb894b.php(301): Twig\Template->displayBlock() #8 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_fc4c01926f3fd7d3e286e806e760849f1618bf3693cbcbf23197e260f0ddadd9->block_page() #9 /home/cliente/public_html/enew/cache/twig/4b/4b955d564f1d0b952d13c37bcfa22f8d8d598a1f3cae18e9b09093195ccb894b.php(255): Twig\Template->displayBlock() #10 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_fc4c01926f3fd7d3e286e806e760849f1618bf3693cbcbf23197e260f0ddadd9->block_body() #11 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(234): Twig\Template->displayBlock() #12 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(175): Twig\Template->displayBlock() #13 /home/cliente/public_html/enew/cache/twig/6a/6afe0e59b97cbb86a2ba64e12fa304bb8ea37921cd63562a55aea3c1a1d5f6fb.php(143): Twig\Template->displayParentBlock() #14 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_7c430519b1f1129042e4f8a62687105459661805e27de8a0a801e876f4ef37df->block_body() #15 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(175): Twig\Template->displayBlock() #16 /home/cliente/public_html/enew/cache/twig/d8/d80a475daf4a4a9cba86a6484ad82b0a26c9bc805fd9664a88be4c0d7b5a35b0.php(129): Twig\Template->displayParentBlock() #17 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(214): __TwigTemplate_17a4b274669635bdff13f76970ff86aa2b7b93121488c0f0a733ac235d0f12ee->block_body() #18 /home/cliente/public_html/enew/cache/twig/4b/4b955d564f1d0b952d13c37bcfa22f8d8d598a1f3cae18e9b09093195ccb894b.php(79): Twig\Template->displayBlock() #19 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(453): __TwigTemplate_fc4c01926f3fd7d3e286e806e760849f1618bf3693cbcbf23197e260f0ddadd9->doDisplay() #20 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(420): Twig\Template->displayWithErrorHandling() #21 /home/cliente/public_html/enew/cache/twig/c4/c46f52a8140cb76eb3f258fee97561f6e19d7376bdb2da7ecba2e7a44be6771f.php(37): Twig\Template->display() #22 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(453): __TwigTemplate_d11cb2bccffb87c9740e101457f1f5cf4a8af8d1b7b0a4c4b25f78c06ff0444d->doDisplay() #23 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(420): Twig\Template->displayWithErrorHandling() #24 /home/cliente/public_html/enew/cache/twig/6a/6afe0e59b97cbb86a2ba64e12fa304bb8ea37921cd63562a55aea3c1a1d5f6fb.php(118): Twig\Template->display() #25 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(453): __TwigTemplate_7c430519b1f1129042e4f8a62687105459661805e27de8a0a801e876f4ef37df->doDisplay() #26 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(420): Twig\Template->displayWithErrorHandling() #27 /home/cliente/public_html/enew/cache/twig/d8/d80a475daf4a4a9cba86a6484ad82b0a26c9bc805fd9664a88be4c0d7b5a35b0.php(76): Twig\Template->display() #28 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(453): __TwigTemplate_17a4b274669635bdff13f76970ff86aa2b7b93121488c0f0a733ac235d0f12ee->doDisplay() #29 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(420): Twig\Template->displayWithErrorHandling() #30 /home/cliente/public_html/enew/cache/twig/f4/f4875bf43bced1a2eaa6b9b9367a9bc31551d38314f95c7972ff58b312d253a2.php(67): Twig\Template->display() #31 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(453): __TwigTemplate_6614e10d2d1d2107a0f55c02accc12c71662a9d4fc0cf295d5b0172631cdb4c8->doDisplay() #32 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(420): Twig\Template->displayWithErrorHandling() #33 /home/cliente/public_html/enew/vendor/twig/twig/src/Template.php(432): Twig\Template->display() #34 /home/cliente/public_html/enew/vendor/twig/twig/src/TemplateWrapper.php(47): Twig\Template->render() #35 /home/cliente/public_html/enew/vendor/twig/twig/src/Environment.php(384): Twig\TemplateWrapper->render() #36 /home/cliente/public_html/enew/system/src/Grav/Common/Twig/Twig.php(439): Twig\Environment->render() #37 /home/cliente/public_html/enew/system/src/Grav/Common/Service/OutputServiceProvider.php(36): Grav\Common\Twig\Twig->processSite() #38 /home/cliente/public_html/enew/vendor/pimple/pimple/src/Pimple/Container.php(122): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}() #39 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/RenderProcessor.php(40): Pimple\Container->offsetGet() #40 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\RenderProcessor->process() #41 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #42 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php(38): Grav\Framework\RequestHandler\RequestHandler->handle() #43 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\DebuggerAssetsProcessor->process() #44 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #45 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/PagesProcessor.php(112): Grav\Framework\RequestHandler\RequestHandler->handle() #46 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\PagesProcessor->process() #47 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #48 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/TwigProcessor.php(38): Grav\Framework\RequestHandler\RequestHandler->handle() #49 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\TwigProcessor->process() #50 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #51 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/AssetsProcessor.php(39): Grav\Framework\RequestHandler\RequestHandler->handle() #52 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\AssetsProcessor->process() #53 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #54 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/SchedulerProcessor.php(40): Grav\Framework\RequestHandler\RequestHandler->handle() #55 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\SchedulerProcessor->process() #56 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #57 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/BackupsProcessor.php(39): Grav\Framework\RequestHandler\RequestHandler->handle() #58 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\BackupsProcessor->process() #59 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #60 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/TasksProcessor.php(69): Grav\Framework\RequestHandler\RequestHandler->handle() #61 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\TasksProcessor->process() #62 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #63 /home/cliente/public_html/enew/user/plugins/admin/classes/plugin/Router.php(65): Grav\Framework\RequestHandler\RequestHandler->handle() #64 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Plugin\Admin\Router->process() #65 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #66 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/RequestProcessor.php(64): Grav\Framework\RequestHandler\RequestHandler->handle() #67 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\RequestProcessor->process() #68 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #69 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/ThemesProcessor.php(38): Grav\Framework\RequestHandler\RequestHandler->handle() #70 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\ThemesProcessor->process() #71 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #72 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/PluginsProcessor.php(39): Grav\Framework\RequestHandler\RequestHandler->handle() #73 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\PluginsProcessor->process() #74 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #75 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/InitializeProcessor.php(130): Grav\Framework\RequestHandler\RequestHandler->handle() #76 /home/cliente/public_html/enew/system/src/Grav/Common/Debugger.php(546): Grav\Common\Processors\InitializeProcessor::Grav\Common\Processors\{closure}() #77 /home/cliente/public_html/enew/system/src/Grav/Common/Processors/InitializeProcessor.php(131): Grav\Common\Debugger->profile() #78 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Common\Processors\InitializeProcessor->process() #79 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #80 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Middlewares/MultipartRequestSupport.php(40): Grav\Framework\RequestHandler\RequestHandler->handle() #81 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(50): Grav\Framework\RequestHandler\Middlewares\MultipartRequestSupport->process() #82 /home/cliente/public_html/enew/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php(62): Grav\Framework\RequestHandler\RequestHandler->handle() #83 /home/cliente/public_html/enew/system/src/Grav/Common/Grav.php(312): Grav\Framework\RequestHandler\RequestHandler->handle() #84 /home/cliente/public_html/enew/index.php(47): Grav\Common\Grav->process() #85 {main} [] []

  1. Debugger gives 101 deprecated messages, similar to this one for example:
Your site is using following deprecated features:
grav

array:4 [
  "message" => "Grav\Common\User\Group::groupNames() is deprecated since Grav 1.7, use $grav['user_groups'] Flex UserGroupCollection instead"
  "file" => "/home/cliente/public_html/enew/system/src/Grav/Common/User/Group.php"
  "line" => 45
  "trace" => array:10 [
    0 => array:3 [
      "call" => "Grav\Common\User\Group::groupNames()"
      "file" => "system/src/Grav/Common/Data/Blueprint.php"
      "line" => 446
    ]
    1 => array:3 [
      "call" => "Grav\Common\Data\Blueprint->dynamicData($array, 'options', $array)"
      "file" => "system/src/Grav/Common/Data/Blueprint.php"
      "line" => 178
    ]
    2 => array:3 [
      "call" => "Grav\Common\Data\Blueprint->init()"
      "file" => "system/src/Grav/Common/Data/Blueprints.php"
      "line" => 111
    ]
    3 => array:3 [
      "call" => "Grav\Common\Data\Blueprints->loadFile('user/account')"
      "file" => "system/src/Grav/Common/Data/Blueprints.php"
      "line" => 50
    ]
    4 => array:3 [
      "call" => "Grav\Common\Data\Blueprints->get('user/account')"
      "file" => "system/src/Grav/Common/User/DataUser/User.php"
      "line" => 234
    ]
    5 => array:1 [
      "call" => "Grav\Common\User\DataUser\User->__wakeup()"
    ]
    6 => array:3 [
      "call" => "session_start($array)"
      "file" => "system/src/Grav/Framework/Session/Session.php"
      "line" => 211
    ]
    7 => array:3 [
      "call" => "Grav\Framework\Session\Session->start()"
      "file" => "system/src/Grav/Common/Session.php"
      "line" => 48
    ]
    8 => array:3 [
      "call" => "Grav\Common\Session->init()"
      "file" => "system/src/Grav/Common/Processors/InitializeProcessor.php"
      "line" => 450
    ]
    9 => array:3 [
      "call" => "Grav\Common\Processors\InitializeProcessor->initializeSession(Grav\Common\Config\Config $object)"
      "file" => "system/src/Grav/Common/Processors/InitializeProcessor.php"
      "line" => 109
    ]
  ]
]
array:4 [
  "message" => "Grav\Common\User\Group::groups() is deprecated since Grav 1.7, use $grav['user_groups'] Flex UserGroupCollection instead"
  "file" => "/home/cliente/public_html/enew/system/src/Grav/Common/User/Group.php"
  "line" => 32
  "trace" => array:11 [
    0 => array:3 [
      "call" => "Grav\Common\User\Group::groups()"
      "file" => "system/src/Grav/Common/User/Group.php"
      "line" => 49
    ]
    1 => array:3 [
      "call" => "Grav\Common\User\Group::groupNames()"
      "file" => "system/src/Grav/Common/Data/Blueprint.php"
      "line" => 446
    ]
    2 => array:3 [
      "call" => "Grav\Common\Data\Blueprint->dynamicData($array, 'options', $array)"
      "file" => "system/src/Grav/Common/Data/Blueprint.php"
      "line" => 178
    ]
    3 => array:3 [
      "call" => "Grav\Common\Data\Blueprint->init()"
      "file" => "system/src/Grav/Common/Data/Blueprints.php"
      "line" => 111
    ]
    4 => array:3 [
      "call" => "Grav\Common\Data\Blueprints->loadFile('user/account')"
      "file" => "system/src/Grav/Common/Data/Blueprints.php"
      "line" => 50
    ]
    5 => array:3 [
      "call" => "Grav\Common\Data\Blueprints->get('user/account')"
      "file" => "system/src/Grav/Common/User/DataUser/User.php"
      "line" => 234
    ]
    6 => array:1 [
      "call" => "Grav\Common\User\DataUser\User->__wakeup()"
    ]
    7 => array:3 [
      "call" => "session_start($array)"
      "file" => "system/src/Grav/Framework/Session/Session.php"
      "line" => 211
    ]
    8 => array:3 [
      "call" => "Grav\Framework\Session\Session->start()"
      "file" => "system/src/Grav/Common/Session.php"
      "line" => 48
    ]
    9 => array:3 [
      "call" => "Grav\Common\Session->init()"
      "file" => "system/src/Grav/Common/Processors/InitializeProcessor.php"
      "line" => 450
    ]
    10 => array:3 [
      "call" => "Grav\Common\Processors\InitializeProcessor->initializeSession(Grav\Common\Config\Config $object)"
      "file" => "system/src/Grav/Common/Processors/InitializeProcessor.php"
      "line" => 109
    ]
  ]
]

unknownarray:4 [ "message" => "Constant FILTER_SANITIZE_STRING is deprecated" "file" => "/home/cliente/...
unknownarray:4 [ "message" => "Constant FILTER_SANITIZE_STRING is deprecated" "file" => "/home/cliente/...
unknownarray:4 [ "message" => "preg_replace(): Passing null to parameter #3 ($subject) of type array|stri...
unknownarray:3 [ "message" => "Constant FILTER_SANITIZE_STRING is deprecated" "file" => "/home/cliente/...
unknownarray:3 [ "message" => "explode(): Passing null to parameter #2 ($string) of type string is deprec...
gravarray:4 [ "message" => "Grav\Common\User\Group::groupNames() is deprecated since Grav 1.7, use $gr...
gravarray:4 [ "message" => "Grav\Common\User\Group::groups() is deprecated since Grav 1.7, use $grav['...
unknownarray:3 [ "message" => "Constant FILTER_SANITIZE_STRING is deprecated" "file" => "/home/cliente/..

Debugger 8 Messages:

Grav v1.7.37.1 - PHP 8.1.12
infoEnvironment Name: client.com.ve
debugRegistered flex types: pages, user-accounts, user-groups
infoAdmin v1.10.37.1
infoCache: [true] Setting: [auto] Driver: [file]
debug[]
infoRouted to page (type: config)
infoPage cache missed, rebuilding pages..

3.1 The other debugger options shows values, I have not seen error there: Request
Timeline
Exceptions (There is no exceptions)
Config
Plugins
Streams

  1. PHP Error reported by Grav just when saving the Configuration on Admin > Config > System
/home/equimed/public_html/enew/user/plugins/plugin-guard/plugin-guard.php

  if ($isPluginsPage && $isTask && (!$isTaxonomyFilter || ($isTaxonomyFilter && !$this->isGroupMember()))) {
    $this->grav['messages']->add("No hay permisos para habilitar o deshabilitar el plugin", 'error');
    $url = $_SERVER['HTTP_REFERER'];
    $this->grav->redirect($url);
  }
        
          $this->enable([
            'onAdminSave' => ['onAdminSave', 0],
          ]);
          
          return;
        }
 
        // Enable the main events we are interested in
        $this->enable([
            // Put your main events here
        ]);
    }
    
    public function onAdminSave(Event $event)
{
  /** @var Data */
  $data = $event['object'];
 
  $blueprints = $data->blueprints();
  $type = $blueprints['type'];
  $slug = $blueprints['slug'];
  $name = $blueprints['name'];
 
  if ($type === 'plugin' && ($slug !== 'taxonomy-filter' || ($slug === 'taxonomy-filter' && !$this->isGroupMember()))) {
    $this->grav['messages']->add("No tiene permisos para salvar cambios en el plugin $name", 'error');
    $url = $_SERVER['HTTP_REFERER'];
    $this->grav->redirect($url);
  }
}
 
protected function isGroupMember(): bool
{
  /** @var User */
  $user = $this->grav['user'];

Arguments

    "Grav\Plugin\PluginGuardPlugin::onAdminSave(): Argument #1 ($event) must be of type Grav\Plugin\Event, RocketTheme\Toolbox\Event\Event given, called in /home/cliente/public_html/enew/vendor/symfony/event-dispatcher/EventDispatcher.php on line 264 "


4.1 This is the plugin-guard.php, including the last change:

<?php
namespace Grav\Plugin;

use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;

/**
 * Class PluginGuardPlugin
 * @package Grav\Plugin
 */
class PluginGuardPlugin extends Plugin
{
    /**
     * @return array
     *
     * The getSubscribedEvents() gives the core a list of events
     *     that the plugin wants to listen to. The key of each
     *     array section is the event that the plugin listens to
     *     and the value (in the form of an array) contains the
     *     callable (or function) as well as the priority. The
     *     higher the number the higher the priority.
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onPluginsInitialized' => [
                // Uncomment following line when plugin requires Grav < 1.7
                // ['autoload', 100000],
                ['onPluginsInitialized', 0]
            ]
        ];
    }

    /**
     * Composer autoload
     *
     * @return ClassLoader
     */
    public function autoload(): ClassLoader
    {
        return require __DIR__ . '/vendor/autoload.php';
    }

    /**
     * Initialize the plugin
     */
    public function onPluginsInitialized(): void
    {
        // Proceed if we are in the admin plugin
        if ($this->isAdmin()) {
        /** @var Route */
  $routeObject = $this->grav['route'];
  $route = $routeObject->getRoute();
  $task = $this->grav['task'] ?? $this->grav['action'];
  $tasks = ['enable', 'disable'];

  $isPluginsPage = str_starts_with($route, '/admin/plugins');
  $isTaxonomyFilter = $route ===  '/admin/plugins/taxonomy-filter';
  $isTask = in_array($task, $tasks);

  if ($isPluginsPage && $isTask && (!$isTaxonomyFilter || ($isTaxonomyFilter && !$this->isGroupMember()))) {
    $this->grav['messages']->add("No hay permisos para habilitar o deshabilitar el plugin", 'error');
    $url = $_SERVER['HTTP_REFERER'];
    $this->grav->redirect($url);
  }
        
          $this->enable([
            'onAdminSave' => ['onAdminSave', 0],
          ]);
          
          return;
        }

        // Enable the main events we are interested in
        $this->enable([
            // Put your main events here
        ]);
    }
    
    public function onAdminSave(Event $event)
{
  /** @var Data */
  $data = $event['object'];

  $blueprints = $data->blueprints();
  $type = $blueprints['type'];
  $slug = $blueprints['slug'];
  $name = $blueprints['name'];

  if ($type === 'plugin' && ($slug !== 'taxonomy-filter' || ($slug === 'taxonomy-filter' && !$this->isGroupMember()))) {
    $this->grav['messages']->add("No tiene permisos para salvar cambios en el plugin $name", 'error');
    $url = $_SERVER['HTTP_REFERER'];
    $this->grav->redirect($url);
  }
}

protected function isGroupMember(): bool
{
  /** @var User */
  $user = $this->grav['user'];
  $groups = $user->get('groups');

  return $groups && in_array('administradores', $groups);
}
}

Thanks in advance for any additional information to solve this issue.
Regards

@joejac, Sorry again… I cannot reproduce any of the errors you’ve mentioned. Everything is working as expected. In my website that is…

By the way, with “fire-up the debugger”, I meant the XDebug debugger with which you can step through the code line by line and inspect the value of variables.

Hello @pamtbaau I understand, did you had the chance to see the code of my plugin-guard.php? I want to ensure I understood correct all your instructions and I did no mistakes with the code, before inspecting the variables.
Thanks and regards

@joejac, Yes, I did have a look at your code and even:

  • Created group ‘administradores’
  • Assigned the group to a user
  • Created plugin ‘taxonomy.filter’
  • And ran your code without any error

On top of that:

  • I added multiple languages
  • And tested for a site running in a subdirectory…

All is working as expected

1 Like

Hello @pamtbaau ,

  1. I added:
use Grav\Common\Theme;
use RocketTheme\Toolbox\Event\Event;

and now your plugin-guard is working for me :partying_face:, including the enable/disable section, and an if condition to allow the main Administrator to manage all plugins. Thanks a lot!.

  1. I tried to add your second proposal, the function onAssetsInitialized, to the original plugin-guard, in order to have one plugin only, but with no luck.

Hello @pamtbaau
I created a plugin for showing the selected plugin only, I called plugin-filter:

  1. Changing the position of the parameters inside the if with the URL worked for me now.
public function onAssetsInitialized(Event $event)
{
  if (!$this->isAdminMember()) {

  $url = $_SERVER['REQUEST_URI'];
 
  if (str_ends_with($url, '/admin/plugins')) {
    $this->grav['assets']->addInlineJs(
      "
      const plugins = document.body.querySelectorAll('[data-gpm-plugin]');

      plugins.forEach((plugin) => {
         if (plugin.getAttribute('data-gpm-plugin') !== 'taxonomy-filter') {
            plugin.remove();
         }
      });
      ",
      ['group' => 'bottom']
    );
  }
}

}

protected function isAdminMember(): bool
{
  /** @var User */
  $user = $this->grav['user'];
  $groups = $user->get('groups');

  return $groups && in_array('administrator', $groups);
}

Thanks a lot.

Hello, only one last question @pamtbaau
Like you said

Via the URL, the user can type the plugin name and view its plugin admin page, he can not modify it, plugin-guard prevents to save, enable or disable, but does not prevent to remove the plugin, I know I did not asked for that :see_no_evil: but how can I disable the “Remove Plugin” button?, I was not able to find an event “Before Delete or Remove” in the documentation.

Thanks and regards.

@joejac,

  • Instead of spending time and money on building concrete walls to prevent a trusted (and hopefully instructed) Admin user from making changes despite all warnings, you might also think “We are all consenting adults”, and spend time/money somewhere else.

    The “Remove Plugin” button:

    • Has a red colour, a signal for “danger!”
    • It contains a “danger” flag
    • If that isn’t enough and the user is still eager to press the button, the user gets the popup warning “Are you sure…” which must be explicitly confirmed.

    How far (money and time wise) does one have to go to protect Admin from users like this?

  • Git exists to consolidate a state and revert unwanted changes.

  • If one can inject Javascript into Admin to remove plugins from the list, one can also inject Javascript to remove a button.

    document.body.querySelector('[data-remodal-target=\'remove-package\']').remove();
    
1 Like

Hello, pamtbaau
You are right in your observations and I agree with you.
All my questions were very well answered, everything is fine now.
Thank you @pamtbaau for your valuable help and dedication to this forum.
Best regards.
joejac