Skip to content

Commit

Permalink
ENH add permissions for build tasks
Browse files Browse the repository at this point in the history
ENH add granular dev url permissions
  • Loading branch information
andrewandante committed Oct 19, 2023
1 parent cbd358a commit f319ee3
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 63 deletions.
39 changes: 38 additions & 1 deletion src/Dev/DevBuildController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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()) {
Expand All @@ -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
],
];
}
}
41 changes: 39 additions & 2 deletions src/Dev/DevConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{

/**
Expand All @@ -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()
*
Expand Down Expand Up @@ -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
*
Expand Down
95 changes: 71 additions & 24 deletions src/Dev/DevelopmentAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 = [
Expand Down Expand Up @@ -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;
}
Expand All @@ -114,6 +102,7 @@ protected function init()

public function index()
{
$links = $this->getLinks();
// Web mode
if (!Director::is_cli()) {
$renderer = DebugView::create();
Expand All @@ -123,7 +112,7 @@ public function index()

echo '<div class="options"><ul>';
$evenOdd = "odd";
foreach (self::get_links() as $action => $description) {
foreach ($links as $action => $description) {
echo "<li class=\"$evenOdd\"><a href=\"{$base}dev/$action\"><b>/dev/$action:</b>"
. " $description</a></li>\n";
$evenOdd = ($evenOdd == "odd") ? "even" : "odd";
Expand All @@ -135,7 +124,7 @@ public function index()
} else {
echo "SILVERSTRIPE DEVELOPMENT TOOLS\n--------------------------\n\n";
echo "You can execute any of the following commands:\n\n";
foreach (self::get_links() as $action => $description) {
foreach ($links as $action => $description) {
echo " sake dev/$action: $description\n";
}
echo "\n\n";
Expand Down Expand Up @@ -165,9 +154,6 @@ public function runRegisteredController(HTTPRequest $request)
}
}




/*
* Internal methods
*/
Expand All @@ -190,6 +176,33 @@ protected static function get_links()
return $links;
}

protected function getLinks(): array
{
$canViewAll = $this->canViewAll();
$links = [];
$reg = Config::inst()->get(static::class, 'registered_controllers');
foreach ($reg as $registeredController) {
if (isset($registeredController['links'])) {
if (!ClassInfo::exists($registeredController['controller'])) {
continue;
}

if (!$canViewAll) {
// Check access to controller
$controllerSingleton = singleton($registeredController['controller']);
if (!$controllerSingleton->hasMethod('canInit') || !$controllerSingleton->canInit()) {
continue;
}
}

foreach ($registeredController['links'] as $url => $desc) {
$links[$url] = $desc;
}
}
}
return $links;
}

protected function getRegisteredController($baseUrlPart)
{
$reg = Config::inst()->get(static::class, 'registered_controllers');
Expand All @@ -203,8 +216,6 @@ protected function getRegisteredController($baseUrlPart)
}




/*
* Unregistered (hidden) actions
*/
Expand Down Expand Up @@ -258,4 +269,40 @@ public function errors()
{
$this->redirect("Debug_");
}

public function providePermissions(): array
{
return [
'ALL_DEV_ADMIN' => [
'name' => _t(__CLASS__ . '.ALL_DEV_ADMIN_DESCRIPTION', 'Can view and run all /dev endpoints'),
'help' => _t(__CLASS__ . '.ALL_DEV_ADMIN_HELP', 'Can view and run all endpoints at /dev).'),
'category' => static::permissionsCategory(),
'sort' => 100
],
];
}

public static function permissionsCategory(): string
{
return _t(__CLASS__ . 'PERMISSIONS_CATEGORY', 'Dev permissions');
}

protected function canViewAll(): bool
{
// 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');
return (
$requestedDevBuild
|| Director::isDev()
|| (Director::is_cli() && $allowAllCLI)
// Its important that we don't run this check if dev/build was requested
|| Permission::check("ADMIN")
|| Permission::check("ALL_DEV_ADMIN")
);
}
}
Loading

0 comments on commit f319ee3

Please sign in to comment.