From f319ee3ff685c931d20730f0a07fa9e95514a98b Mon Sep 17 00:00:00 2001 From: Andrew Paxley Date: Wed, 12 Jul 2023 15:51:48 +1200 Subject: [PATCH] ENH add permissions for build tasks ENH add granular dev url permissions --- src/Dev/DevBuildController.php | 39 +++++++- src/Dev/DevConfigController.php | 41 +++++++- src/Dev/DevelopmentAdmin.php | 95 ++++++++++++++----- src/Dev/TaskRunner.php | 87 +++++++++++++---- src/ORM/DatabaseAdmin.php | 2 + tests/php/Dev/DevAdminControllerTest.php | 77 +++++++++++---- .../DevAdminControllerTest/Controller1.php | 2 +- .../ControllerWithPermissions.php | 39 ++++++++ 8 files changed, 319 insertions(+), 63 deletions(-) create mode 100644 tests/php/Dev/DevAdminControllerTest/ControllerWithPermissions.php diff --git a/src/Dev/DevBuildController.php b/src/Dev/DevBuildController.php index 5f7a949fd7b..f2bcd9f6a4b 100644 --- a/src/Dev/DevBuildController.php +++ b/src/Dev/DevBuildController.php @@ -7,8 +7,11 @@ use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPResponse; use SilverStripe\ORM\DatabaseAdmin; +use SilverStripe\Security\Permission; +use SilverStripe\Security\PermissionProvider; +use SilverStripe\Security\Security; -class DevBuildController extends Controller +class DevBuildController extends Controller implements PermissionProvider { private static $url_handlers = [ @@ -19,6 +22,15 @@ class DevBuildController extends Controller 'build' ]; + protected function init(): void + { + parent::init(); + + if (!$this->canInit()) { + Security::permissionFailure($this); + } + } + public function build(HTTPRequest $request): HTTPResponse { if (Director::is_cli()) { @@ -39,4 +51,29 @@ public function build(HTTPRequest $request): HTTPResponse return $response; } } + + public function canInit(): bool + { + return ( + Director::isDev() + // We need to ensure that DevelopmentAdminTest can simulate permission failures when running + // "dev/tasks" from CLI. + || (Director::is_cli() && DevelopmentAdmin::config()->get('allow_all_cli')) + || Permission::check("ADMIN") + || Permission::check("ALL_DEV_ADMIN") + || Permission::check("CAN_DEV_BUILD") + ); + } + + public function providePermissions(): array + { + return [ + 'CAN_DEV_BUILD' => [ + 'name' => _t(__CLASS__ . '.CAN_DEV_BUILD_DESCRIPTION', 'Can execute a Dev/Build'), + 'help' => _t(__CLASS__ . '.CAN_DEV_BUILD_HELP', 'Can execute a Dev/Build (/dev/build).'), + 'category' => DevelopmentAdmin::permissionsCategory(), + 'sort' => 100 + ], + ]; + } } diff --git a/src/Dev/DevConfigController.php b/src/Dev/DevConfigController.php index 3b5d4886920..2130c9cbe83 100644 --- a/src/Dev/DevConfigController.php +++ b/src/Dev/DevConfigController.php @@ -7,13 +7,16 @@ use SilverStripe\Control\HTTPResponse; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; -use Symfony\Component\Yaml\Yaml; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Security\Permission; +use SilverStripe\Security\PermissionProvider; +use SilverStripe\Security\Security; +use Symfony\Component\Yaml\Yaml; /** * Outputs the full configuration. */ -class DevConfigController extends Controller +class DevConfigController extends Controller implements PermissionProvider { /** @@ -32,6 +35,15 @@ class DevConfigController extends Controller 'audit', ]; + protected function init(): void + { + parent::init(); + + if (!$this->canInit()) { + Security::permissionFailure($this); + } + } + /** * Note: config() method is already defined, so let's just use index() * @@ -129,6 +141,31 @@ public function audit() return $this->getResponse()->setBody($body); } + public function canInit(): bool + { + return ( + Director::isDev() + // We need to ensure that DevelopmentAdminTest can simulate permission failures when running + // "dev/tasks" from CLI. + || (Director::is_cli() && DevelopmentAdmin::config()->get('allow_all_cli')) + || Permission::check("ADMIN") + || Permission::check("ALL_DEV_ADMIN") + || Permission::check("CAN_DEV_CONFIG") + ); + } + + public function providePermissions(): array + { + return [ + 'CAN_DEV_CONFIG' => [ + 'name' => _t(__CLASS__ . '.CAN_DEV_CONFIG_DESCRIPTION', 'Can see Dev/Config'), + 'help' => _t(__CLASS__ . '.CAN_DEV_CONFIG_HELP', 'Can see Dev/Config (/dev/config).'), + 'category' => DevelopmentAdmin::permissionsCategory(), + 'sort' => 100 + ], + ]; + } + /** * Returns all the keys of a multi-dimensional array while maintining any nested structure * diff --git a/src/Dev/DevelopmentAdmin.php b/src/Dev/DevelopmentAdmin.php index 79b2e1c7093..4d0047c3e7a 100644 --- a/src/Dev/DevelopmentAdmin.php +++ b/src/Dev/DevelopmentAdmin.php @@ -11,8 +11,10 @@ use SilverStripe\Versioned\Versioned; use SilverStripe\ORM\DatabaseAdmin; use SilverStripe\Security\Permission; +use SilverStripe\Security\PermissionProvider; use SilverStripe\Security\Security; use Exception; +use SilverStripe\Core\ClassInfo; /** * Base class for development tools. @@ -25,7 +27,7 @@ * @todo cleanup errors() it's not even an allowed action, so can go * @todo cleanup index() html building */ -class DevelopmentAdmin extends Controller +class DevelopmentAdmin extends Controller implements PermissionProvider { private static $url_handlers = [ @@ -84,22 +86,8 @@ protected function init() if (static::config()->get('deny_non_cli') && !Director::is_cli()) { return $this->httpError(404); } - - // Special case for dev/build: Defer permission checks to DatabaseAdmin->init() (see #4957) - $requestedDevBuild = (stripos($this->getRequest()->getURL() ?? '', 'dev/build') === 0) - && (stripos($this->getRequest()->getURL() ?? '', 'dev/build/defaults') === false); - - // We allow access to this controller regardless of live-status or ADMIN permission only - // if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. - $allowAllCLI = static::config()->get('allow_all_cli'); - $canAccess = ( - $requestedDevBuild - || Director::isDev() - || (Director::is_cli() && $allowAllCLI) - // Its important that we don't run this check if dev/build was requested - || Permission::check("ADMIN") - ); - if (!$canAccess) { + + if (!$this->canViewAll() && empty($this->getLinks())) { Security::permissionFailure($this); return; } @@ -114,6 +102,7 @@ protected function init() public function index() { + $links = $this->getLinks(); // Web mode if (!Director::is_cli()) { $renderer = DebugView::create(); @@ -123,7 +112,7 @@ public function index() echo '