diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0c9b01a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/.github export-ignore +/Tests export-ignore +/phpunit.xml.dist export-ignore diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..4926fc0 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + +permissions: + contents: read + +jobs: + tests: + name: PHP ${{ matrix.php }}, ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: lowest deps + php: '7.4' + flags: --prefer-lowest --prefer-stable + - name: Symfony 3.4 + php: '7.4' + pins: symfony/config:3.4.* symfony/dependency-injection:3.4.* symfony/http-kernel:3.4.* symfony/security-core:3.4.* symfony/yaml:3.4.* + - name: Symfony 4.4 + php: '7.4' + pins: symfony/config:4.4.* symfony/dependency-injection:4.4.* symfony/http-kernel:4.4.* symfony/security-core:4.4.* symfony/yaml:4.4.* + - name: Symfony 4.4 + php: '8.1' + pins: symfony/config:4.4.* symfony/dependency-injection:4.4.* symfony/http-kernel:4.4.* symfony/security-core:4.4.* symfony/yaml:4.4.* + - name: Symfony 5.4 + php: '8.1' + pins: symfony/config:5.4.* symfony/dependency-injection:5.4.* symfony/http-kernel:5.4.* symfony/security-core:5.4.* symfony/yaml:5.4.* + - name: Symfony 6.4 + php: '8.2' + pins: symfony/config:6.4.* symfony/dependency-injection:6.4.* symfony/http-kernel:6.4.* symfony/security-core:6.4.* symfony/yaml:6.4.* + - name: latest deps + php: '8.3' + env: + PINS: ${{ matrix.pins }} + COMPOSER_NO_SECURITY_BLOCKING: 1 + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: | + if [ -n "$PINS" ]; then composer require --no-update --no-interaction $PINS; fi + composer update --no-interaction --no-progress ${{ matrix.flags }} + composer show | grep -E '^(docplanner/gatekeeper|symfony/(config|dependency-injection|http-kernel|security|security-core|yaml)) ' + + - name: PHPUnit + run: vendor/bin/phpunit diff --git a/Tests/GateKeeperBundleTest.php b/Tests/GateKeeperBundleTest.php new file mode 100644 index 0000000..2f64d5e --- /dev/null +++ b/Tests/GateKeeperBundleTest.php @@ -0,0 +1,97 @@ +container = new ContainerBuilder(); + $this->container->registerExtension($bundle->getContainerExtension()); + $this->container->loadFromExtension('gate_keeper', ['repository_service' => 'test.repository']); + $this->container->register('test.repository', InMemoryRepository::class); + $bundle->build($this->container); + $this->container->addCompilerPass(new PublicVoterPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); + $this->container->compile(); + } + + public function testContainerDumps(): void + { + $this->assertStringContainsString('class GateKeeperTestContainer', (new PhpDumper($this->container))->dump(['class' => 'GateKeeperTestContainer'])); + } + + public function testGateKeeperServiceIsPublic(): void + { + $gateKeeper = $this->container->get(GateKeeper::class); + + $this->assertInstanceOf(GateKeeper::class, $gateKeeper); + $this->assertTrue($gateKeeper->hasAccess('GATE_ON')); + $this->assertFalse($gateKeeper->hasAccess('GATE_OFF')); + $this->assertFalse($gateKeeper->hasAccess('GATE_MISSING')); + } + + public function testVoter(): void + { + $this->assertArrayHasKey('gatekeeper.voter', $this->container->findTaggedServiceIds('security.voter')); + + $voter = $this->container->get('gatekeeper.voter'); + $token = new TestToken(); + + $this->assertSame(VoterInterface::ACCESS_GRANTED, $voter->vote($token, null, ['GATE_ON'])); + $this->assertSame(VoterInterface::ACCESS_DENIED, $voter->vote($token, null, ['GATE_OFF'])); + $this->assertSame(VoterInterface::ACCESS_ABSTAIN, $voter->vote($token, null, ['ROLE_ADMIN'])); + } +} + +class InMemoryRepository implements RepositoryInterface +{ + public function save(ModelInterface $gateKeeperModel) {} + public function update(ModelInterface $gateKeeperModel) {} + public function delete(ModelInterface $gateKeeperModel) {} + + public function get($name) + { + $accesses = ['GATE_ON' => 'allow-all', 'GATE_OFF' => 'deny-all']; + + return isset($accesses[$name]) ? new Model($accesses[$name]) : null; + } +} + +class Model implements ModelInterface +{ + private $access; + + public function __construct($access) { $this->access = $access; } + public function getAccess() { return $this->access; } + public function getGate() { return ''; } +} + +class TestToken extends AbstractToken +{ + public function __construct() { parent::__construct([]); } + public function getCredentials() { return ''; } +} + +class PublicVoterPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $container->getDefinition('gatekeeper.voter')->setPublic(true); + } +} diff --git a/composer.json b/composer.json index e72d93f..d51a891 100644 --- a/composer.json +++ b/composer.json @@ -20,19 +20,26 @@ "require": { "php": "^5.4 || ^7.4 || ^8.0", "docplanner/gatekeeper": "2.*", - "symfony/dependency-injection": "^2.8 || ^3.0 || ^4.0", - "symfony/http-kernel": "^2.8 || ^3.0 || ^4.0", - "symfony/config": "^2.8 || ^3.0 || ^4.0", - "symfony/security": "^2.8 || ^3.0 || ^4.0" + "symfony/dependency-injection": "^2.8 || ^3.0 || ^4.0 || ^5.4 || ^6.4", + "symfony/http-kernel": "^2.8 || ^3.0 || ^4.0 || ^5.4 || ^6.4", + "symfony/config": "^2.8 || ^3.0 || ^4.0 || ^5.4 || ^6.4", + "symfony/security-core": "^2.8 || ^3.0 || ^4.0 || ^5.4 || ^6.4", + "symfony/yaml": "^2.8 || ^3.0 || ^4.0 || ^5.4 || ^6.4" }, "autoload": { "psr-4": { "GateKeeperBundle\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "3.1.x-dev" } + }, + "require-dev": { + "phpunit/phpunit": "^9.6" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..82745c3 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,12 @@ + + + + + Tests + + +