Skip to content

Commit

Permalink
Increase default firewall strategy for plugin legacy scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
cedric-anne authored Oct 28, 2024
1 parent f9511be commit b475d9c
Show file tree
Hide file tree
Showing 5 changed files with 333 additions and 167 deletions.
8 changes: 0 additions & 8 deletions dependency_injection/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Glpi\DependencyInjection\PublicService;
use Glpi\Http\Firewall;
use Glpi\Http\FirewallInterface;
use Glpi\Log\LegacyGlobalLogger;

return static function (ContainerConfigurator $container): void {
Expand All @@ -61,12 +59,6 @@
$services->load('Glpi\Http\\', $projectDir . '/src/Glpi/Http');
$services->load('Glpi\DependencyInjection\\', $projectDir . '/src/Glpi/DependencyInjection');

$services->set(Firewall::class)
->factory([Firewall::class, 'createDefault'])
->tag('proxy', ['interface' => FirewallInterface::class])
->lazy()
;

/**
* Override Symfony's logger.
* @see \Symfony\Component\HttpKernel\DependencyInjection\LoggerPass
Expand Down
256 changes: 230 additions & 26 deletions phpunit/functional/Glpi/Http/FirewallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@

namespace tests\units\Glpi\Http;

use org\bovigo\vfs\vfsStream;
use Glpi\Exception\Http\AccessDeniedHttpException;
use Glpi\Exception\SessionExpiredException;
use Glpi\Http\Firewall;
use KnowbaseItem;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\HttpFoundation\Request;

class FirewallTest extends \GLPITestCase
class FirewallTest extends \DbTestCase
{
public function testComputeFallbackStrategy()
public function testComputeFallbackStrategy(): void
{
vfsStream::setup(
'glpi',
Expand Down Expand Up @@ -113,7 +118,7 @@ public function testComputeFallbackStrategy()
);

$default_for_core_legacy = 'authenticated';
$default_for_plugins_legacy = 'no_check';
$default_for_plugins_legacy = 'authenticated';
$default_for_symfony_routes = 'central_access';

$default_mapping = [
Expand All @@ -137,26 +142,17 @@ public function testComputeFallbackStrategy()
'/myplugindir/pluginb/Route/To/Something' => $default_for_symfony_routes,
];

foreach ($default_mapping as $path => $expected_strategy) {
$this->dotestComputeFallbackStrategy(
root_doc: '',
path: $path,
expected_strategy: $expected_strategy,
);
$this->dotestComputeFallbackStrategy(
root_doc: '/glpi',
path: '/glpi' . $path,
expected_strategy: $expected_strategy,
);
$this->dotestComputeFallbackStrategy(
root_doc: '/path/to/app',
path: '/path/to/app' . $path,
expected_strategy: $expected_strategy,
);
}

// Hardcoded strategies
foreach (['', '/glpi', '/path/to/app'] as $root_doc) {
// Default strategies
foreach ($default_mapping as $path => $expected_strategy) {
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . $path,
expected_strategy: $expected_strategy,
);
}

// Hardcoded strategies
// `/front/central.php` has a specific strategy only if some get parameters are defined
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
Expand Down Expand Up @@ -220,22 +216,230 @@ public function testComputeFallbackStrategy()
expected_strategy: 'no_check',
);
}

// Specific strategies defined by plugins
Firewall::addPluginStrategyForLegacyScripts('myplugin', '#^.*/foo.php#', 'faq_access');
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/ajax/foo.php',
expected_strategy: 'faq_access',
);
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/front/foo.php',
expected_strategy: 'faq_access',
);
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/front/dir/bar.php',
expected_strategy: $default_for_plugins_legacy, // does not match the pattern
);
Firewall::addPluginStrategyForLegacyScripts('myplugin', '#^/front/dir/#', 'helpdesk_access');
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/ajax/foo.php',
expected_strategy: 'faq_access',
);
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/front/foo.php',
expected_strategy: 'faq_access',
);
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/front/dir/bar.php',
expected_strategy: 'helpdesk_access',
);
Firewall::addPluginStrategyForLegacyScripts('myplugin', '#^/PluginRoute$#', 'helpdesk_access');
$this->dotestComputeFallbackStrategy(
root_doc: $root_doc,
path: $root_doc . '/marketplace/myplugin/PluginRoute',
expected_strategy: $default_for_symfony_routes, // fallback strategies MUST NOT apply to symfony routes
);
Firewall::resetPluginsStrategies();
}
}

private function dotestComputeFallbackStrategy(
string $root_doc,
string $path,
string $expected_strategy
) {
): void {
$instance = new Firewall(
$root_doc,
vfsStream::url('glpi'),
[vfsStream::url('glpi/myplugindir'), vfsStream::url('glpi/marketplace')]
);

$request = new Request();
$request->server->set('SCRIPT_FILENAME', $root_doc . '/index.php');
$request->server->set('SCRIPT_NAME', $root_doc . '/index.php');
$request->server->set('REQUEST_URI', $path);

$this->assertEquals(
$expected_strategy,
$instance->computeFallbackStrategy($path)
$instance->computeFallbackStrategy($request),
$path
);
}

public static function provideStrategy(): iterable
{
yield ['strategy' => Firewall::STRATEGY_AUTHENTICATED];
yield ['strategy' => Firewall::STRATEGY_CENTRAL_ACCESS];
yield ['strategy' => Firewall::STRATEGY_FAQ_ACCESS];
yield ['strategy' => Firewall::STRATEGY_HELPDESK_ACCESS];
}

#[DataProvider('provideStrategy')]
public function testApplyStrategyWhenLoggedOut(string $strategy): void
{
$this->expectException(SessionExpiredException::class);

$instance = new Firewall();
$instance->applyStrategy($strategy);
}

#[DataProvider('provideStrategy')]
public function testApplyStrategyWhenSessionIsCorrupted(string $strategy): void
{
$this->login();

$_SESSION = [];

$this->expectException(SessionExpiredException::class);

$instance = new Firewall();
$instance->applyStrategy($strategy);
}

public static function provideStrategyResults(): iterable
{
$central_users = [
TU_USER => TU_PASS,
'glpi' => 'glpi',
'tech' => 'tech',
'normal' => 'normal',
];

foreach ($central_users as $login => $pass) {
yield [
'strategy' => Firewall::STRATEGY_AUTHENTICATED,
'credentials' => [$login, $pass],
'exception' => null,
];
yield [
'strategy' => Firewall::STRATEGY_CENTRAL_ACCESS,
'credentials' => [$login, $pass],
'exception' => null,
];
yield [
'strategy' => Firewall::STRATEGY_FAQ_ACCESS,
'credentials' => [$login, $pass],
'exception' => null,
];
yield [
'strategy' => Firewall::STRATEGY_HELPDESK_ACCESS,
'credentials' => [$login, $pass],
'exception' => new AccessDeniedHttpException('The current profile does not use the simplified interface'),
];
}

$helpdesk_users = [
'post-only' => 'postonly',
];
foreach ($helpdesk_users as $login => $pass) {
yield [
'strategy' => Firewall::STRATEGY_AUTHENTICATED,
'credentials' => [$login, $pass],
'exception' => null,
];
yield [
'strategy' => Firewall::STRATEGY_CENTRAL_ACCESS,
'credentials' => [$login, $pass],
'exception' => new AccessDeniedHttpException('The current profile does not use the standard interface'),
];
yield [
'strategy' => Firewall::STRATEGY_FAQ_ACCESS,
'credentials' => [$login, $pass],
'exception' => null,
];
yield [
'strategy' => Firewall::STRATEGY_HELPDESK_ACCESS,
'credentials' => [$login, $pass],
'exception' => null,
];
}
}

#[DataProvider('provideStrategyResults')]
public function testApplyStrategyWithUser(string $strategy, array $credentials, ?\Throwable $exception): void
{
$this->login(...$credentials);

if ($exception !== null) {
$this->expectExceptionObject($exception);
}

$instance = new Firewall();
$instance->applyStrategy($strategy);
}

public static function provideFaqAccessStrategyResults(): iterable
{
yield [
'use_public_faq' => false,
'knowbase_rights' => KnowbaseItem::READFAQ,
'exception' => null,
];

yield [
'use_public_faq' => false,
'knowbase_rights' => READ,
'exception' => null,
];

yield [
'use_public_faq' => false,
'knowbase_rights' => 0,
'exception' => new AccessDeniedHttpException('Missing FAQ right'),
];

yield [
'use_public_faq' => true,
'knowbase_rights' => KnowbaseItem::READFAQ,
'exception' => null,
];

yield [
'use_public_faq' => true,
'knowbase_rights' => READ,
'exception' => null,
];

yield [
'use_public_faq' => true,
'knowbase_rights' => 0,
'exception' => null,
];
}

#[DataProvider('provideFaqAccessStrategyResults')]
public function testApplyStrategyFaqAccess(bool $use_public_faq, int $knowbase_rights, ?\Throwable $exception): void
{
/** @var array $CFG_GLPI */
global $CFG_GLPI;

$this->login();

$CFG_GLPI['use_public_faq'] = $use_public_faq;

$_SESSION['glpiactiveprofile']['knowbase'] = $knowbase_rights;

if ($exception !== null) {
$this->expectExceptionObject($exception);
}

$instance = new Firewall();
$instance->applyStrategy(Firewall::STRATEGY_FAQ_ACCESS);
}
}
Loading

0 comments on commit b475d9c

Please sign in to comment.