Skip to content

Commit

Permalink
Configures API security firewalls to be able to work with session
Browse files Browse the repository at this point in the history
- allow overwrite cors defaults from docker
  • Loading branch information
vtsykun committed Jan 18, 2020
1 parent e23aa29 commit 6dc05b2
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 5 deletions.
1 change: 1 addition & 0 deletions app/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function registerBundles()
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new FOS\UserBundle\FOSUserBundle(),
new Snc\RedisBundle\SncRedisBundle(),
new Nelmio\CorsBundle\NelmioCorsBundle(),
new Okvpn\Bundle\CronBundle\OkvpnCronBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
Expand Down
1 change: 1 addition & 0 deletions app/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ imports:
- { resource: defaults.yml }
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: nelmio_cors.yml } # Easy overwrite using docker volume if need allow cors

framework:
secret: '%secret%'
Expand Down
11 changes: 11 additions & 0 deletions app/config/nelmio_cors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
nelmio_cors:
defaults:
allow_credentials: false
allow_origin: []
allow_headers: []
allow_methods: []
expose_headers: []
max_age: 0
hosts: []
origin_regex: false
forced_allow_origin_value: ~
2 changes: 2 additions & 0 deletions app/config/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ security:
packages:
pattern: (^(/packages.json$|/p/|/zipball/|/feeds/.+(\.rss|\.atom)|/packages/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?(\.json|/changelog)|/packages/list\.json|/downloads/|/api/))+
api_basic: true
stateless: true
context: main

main:
pattern: .*
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"ezyang/htmlpurifier": "^4.6",
"friendsofsymfony/user-bundle": "^2.1",
"incenteev/composer-parameter-handler": "^2.0",
"nelmio/cors-bundle": "^1.5",
"knplabs/knp-menu-bundle": "^2.1",
"okvpn/cron-bundle": "^0.1.0",
"oro/doctrine-extensions": "^1.2",
Expand Down
60 changes: 59 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions docs/webhook.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,12 @@ Payload

Gitlab auto webhook
-------------------
You can use event `new_repo` to adds a hook to a specified Gitlab project.
It can be useful, if you don't have a Gold Gitlab Plan that allow configure webhooks for your group,
so you need add it manually for each repository.
You can use the event `new_repo` to add a hook to the specified Gitlab project.
It can be useful, if you don't have a Gold Gitlab Plan that allows configure webhooks for your group,
so you need add it manually for each a new repository.

POST `https://{{ host }}/api/v4/projects/{{ repo }}/hooks?private_token=xxxxxxxxxxxxxxxx`


Options:

```json
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Packagist\WebBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
* Configures API security firewalls to be able to work in two modes: stateless and statefull (with session and api_key).
*/
final class ApiFirewallCompilerPass implements CompilerPassInterface
{
/** @var array */
private $contextListeners = [];

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container): void
{
$securityConfigs = $container->getExtensionConfig('security');
if (empty($securityConfigs[0]['firewalls'])) {
return;
}

foreach ($securityConfigs[0]['firewalls'] as $name => $config) {
if ($this->isStatelessFirewallWithContext($config)) {
$this->configureStatelessFirewallWithContext($container, $name, $config);
}
}
}

/**
* Checks whether a firewall is stateless and have context parameter
*
* @param array $firewallConfig
*
* @return bool
*/
private function isStatelessFirewallWithContext(array $firewallConfig): bool
{
return ($firewallConfig['stateless'] ?? null) && ($firewallConfig['context'] ?? null);
}

/**
* @param ContainerBuilder $container
* @param string $firewallName
* @param array $firewallConfig
*/
private function configureStatelessFirewallWithContext(
ContainerBuilder $container,
string $firewallName,
array $firewallConfig
): void {
$contextId = 'security.firewall.map.context.' . $firewallName;
if (!$container->hasDefinition($contextId)) {
return;
}

$contextDef = $container->getDefinition($contextId);
$contextKey = $firewallConfig['context'];

// add the context listener
$listenerId = $this->createContextListener($container, $contextKey);
/** @var IteratorArgument $listeners */
$listeners = $contextDef->getArgument(0);
$contextListeners = array_merge([new Reference($listenerId)], $listeners->getValues());

$contextDef->replaceArgument(0, $contextListeners);
}

/**
* @param ContainerBuilder $container
* @param string $contextKey
*
* @return string
*/
private function createContextListener(ContainerBuilder $container, $contextKey): string
{
if (isset($this->contextListeners[$contextKey])) {
return $this->contextListeners[$contextKey];
}

$listenerId = 'packagist.context_listener.' . $contextKey;
$container
->setDefinition($listenerId, new ChildDefinition('security.context_listener'))
->replaceArgument(2, $contextKey)
->replaceArgument(4, null); // Remove event dispatcher to prevent save session for stateless api.

$this->contextListeners[$contextKey] = $listenerId;

return $listenerId;
}
}
2 changes: 2 additions & 0 deletions src/Packagist/WebBundle/PackagistWebBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Packagist\WebBundle;

use Packagist\WebBundle\DependencyInjection\CompilerPass\ApiFirewallCompilerPass;
use Packagist\WebBundle\DependencyInjection\CompilerPass\WorkerLocatorPass;
use Packagist\WebBundle\DependencyInjection\Security\ApiHttpBasicFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
Expand All @@ -33,5 +34,6 @@ public function build(ContainerBuilder $container)
$extension->addSecurityListenerFactory(new ApiHttpBasicFactory());

$container->addCompilerPass(new WorkerLocatorPass());
$container->addCompilerPass(new ApiFirewallCompilerPass());
}
}

0 comments on commit 6dc05b2

Please sign in to comment.