Skip to content

Commit

Permalink
Merge pull request silverstripe#10979 from andrewandante/ENH_add_buil…
Browse files Browse the repository at this point in the history
…dtask_permissions

Add buildtask and dev URL permissions
  • Loading branch information
GuySartorelli authored Nov 10, 2023
2 parents 8680b6b + b9da08a commit a1eee2a
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 98 deletions.
7 changes: 1 addition & 6 deletions _config/requestprocessors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ SilverStripe\Core\Injector\Injector:
DevUrlsConfirmationMiddleware: '%$DevUrlsConfirmationMiddleware'

DevUrlsConfirmationMiddleware:
class: SilverStripe\Control\Middleware\PermissionAwareConfirmationMiddleware
class: SilverStripe\Control\Middleware\DevelopmentAdminConfirmationMiddleware
constructor:
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartswith("dev")'
properties:
Expand All @@ -97,8 +97,6 @@ SilverStripe\Core\Injector\Injector:
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\CliBypass'
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\EnvironmentBypass("dev")'
EnforceAuthentication: false
AffectedPermissions:
- ADMIN

---
Name: dev_urls-confirmation-exceptions
Expand All @@ -123,9 +121,6 @@ SilverStripe\Core\Injector\Injector:
DevUrlsConfirmationMiddleware:
properties:
Bypasses:
# dev/build is covered by URLSpecialsMiddleware
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartswith("dev/build")'

# The confirmation form is where people will be redirected for confirmation. We don't want to block it.
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartswith("dev/confirm")'

Expand Down
63 changes: 63 additions & 0 deletions src/Control/Middleware/DevelopmentAdminConfirmationMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace SilverStripe\Control\Middleware;

use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\DevelopmentAdmin;
use SilverStripe\Security\Permission;

/**
* Extends the PermissionAwareConfirmationMiddleware with checks for user permissions
*
* Respects users who don't have enough access and does not
* ask them for confirmation
*
* By default it enforces authentication by redirecting users to a login page.
*
* How it works:
* - if user can bypass the middleware, then pass request further
* - if there are no confirmation items, then pass request further
* - if user is not authenticated and enforceAuthentication is false, then pass request further
* - if user does not have at least one of the affected permissions, then pass request further
* - otherwise, pass handling to the parent (ConfirmationMiddleware)
*/
class DevelopmentAdminConfirmationMiddleware extends PermissionAwareConfirmationMiddleware
{

/**
* Check whether the user has permissions to perform the target operation
* Otherwise we may want to skip the confirmation dialog.
*
* WARNING! The user has to be authenticated beforehand
*
* @param HTTPRequest $request
*
* @return bool
*/
public function hasAccess(HTTPRequest $request)
{
$action = $request->remaining();
if (empty($action)) {
return false;
}

$registeredRoutes = DevelopmentAdmin::config()->get('registered_controllers');
while (!isset($registeredRoutes[$action]) && strpos($action, '/') !== false) {
// Check for the parent route if a specific route isn't found
$action = substr($action, 0, strrpos($action, '/'));
}

if (isset($registeredRoutes[$action]['controller'])) {
$initPermissions = Config::forClass($registeredRoutes[$action]['controller'])->get('init_permissions');
foreach ($initPermissions as $permission) {
if (Permission::check($permission)) {
return true;
}
}
}

return false;
}
}
3 changes: 1 addition & 2 deletions src/Control/Middleware/URLSpecialsMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public function __construct()
parent::__construct(
new ConfirmationMiddleware\GetParameter("flush"),
new ConfirmationMiddleware\GetParameter("isDev"),
new ConfirmationMiddleware\GetParameter("isTest"),
new ConfirmationMiddleware\UrlPathStartswith("dev/build")
new ConfirmationMiddleware\GetParameter("isTest")
);
}

Expand Down
43 changes: 42 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,21 @@ class DevBuildController extends Controller
'build'
];

private static $init_permissions = [
'ADMIN',
'ALL_DEV_ADMIN',
'CAN_DEV_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 +57,27 @@ 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(static::config()->get('init_permissions'))
);
}

public function providePermissions(): array
{
return [
'CAN_DEV_BUILD' => [
'name' => _t(__CLASS__ . '.CAN_DEV_BUILD_DESCRIPTION', 'Can execute /dev/build'),
'help' => _t(__CLASS__ . '.CAN_DEV_BUILD_HELP', 'Can execute the build command (/dev/build).'),
'category' => DevelopmentAdmin::permissionsCategory(),
'sort' => 100
],
];
}
}
45 changes: 43 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,21 @@ class DevConfigController extends Controller
'audit',
];

private static $init_permissions = [
'ADMIN',
'ALL_DEV_ADMIN',
'CAN_DEV_CONFIG',
];

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 +147,29 @@ 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(static::config()->get('init_permissions'))
);
}

public function providePermissions(): array
{
return [
'CAN_DEV_CONFIG' => [
'name' => _t(__CLASS__ . '.CAN_DEV_CONFIG_DESCRIPTION', 'Can view /dev/config'),
'help' => _t(__CLASS__ . '.CAN_DEV_CONFIG_HELP', 'Can view all application configuration (/dev/config).'),
'category' => DevelopmentAdmin::permissionsCategory(),
'sort' => 100
],
];
}

/**
* Returns all the keys of a multi-dimensional array while maintining any nested structure
*
Expand Down
Loading

0 comments on commit a1eee2a

Please sign in to comment.