diff --git a/app/AppKernel.php b/app/AppKernel.php index 6f4d9e7a..adaa54b8 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -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(), diff --git a/app/config/config.yml b/app/config/config.yml index 163b94d7..4834ad21 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -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%' diff --git a/app/config/nelmio_cors.yml b/app/config/nelmio_cors.yml new file mode 100644 index 00000000..19930a5f --- /dev/null +++ b/app/config/nelmio_cors.yml @@ -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: ~ diff --git a/app/config/security.yml b/app/config/security.yml index d1cd7eef..8953dea5 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -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: .* diff --git a/composer.json b/composer.json index d4b87de2..332af807 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index ac287df3..58dc64ee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "63eebc4a6336833846f6f20eb2b4cfb9", + "content-hash": "1a793b6468c7e4ba4903efe445fbc34b", "packages": [ { "name": "cebe/markdown", @@ -2057,6 +2057,64 @@ ], "time": "2019-12-20T14:15:16+00:00" }, + { + "name": "nelmio/cors-bundle", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "10a24c10f242440211ed31075e74f81661c690d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/10a24c10f242440211ed31075e74f81661c690d9", + "reference": "10a24c10f242440211ed31075e74f81661c690d9", + "shasum": "" + }, + "require": { + "symfony/framework-bundle": "^2.7 || ^3.0 || ^4.0" + }, + "require-dev": { + "matthiasnoback/symfony-dependency-injection-test": "^1.0 || ^2.0", + "mockery/mockery": "^0.9 || ^1.0", + "symfony/phpunit-bridge": "^2.7 || ^3.0 || ^4.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony2 application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "time": "2019-06-17T08:53:14+00:00" + }, { "name": "okvpn/cron-bundle", "version": "0.1.1", diff --git a/docs/webhook.md b/docs/webhook.md index dec7d0a8..9f5f0638 100644 --- a/docs/webhook.md +++ b/docs/webhook.md @@ -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 diff --git a/src/Packagist/WebBundle/DependencyInjection/CompilerPass/ApiFirewallCompilerPass.php b/src/Packagist/WebBundle/DependencyInjection/CompilerPass/ApiFirewallCompilerPass.php new file mode 100644 index 00000000..0378fb40 --- /dev/null +++ b/src/Packagist/WebBundle/DependencyInjection/CompilerPass/ApiFirewallCompilerPass.php @@ -0,0 +1,99 @@ +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; + } +} diff --git a/src/Packagist/WebBundle/PackagistWebBundle.php b/src/Packagist/WebBundle/PackagistWebBundle.php index 31fe3e36..db0d5312 100644 --- a/src/Packagist/WebBundle/PackagistWebBundle.php +++ b/src/Packagist/WebBundle/PackagistWebBundle.php @@ -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; @@ -33,5 +34,6 @@ public function build(ContainerBuilder $container) $extension->addSecurityListenerFactory(new ApiHttpBasicFactory()); $container->addCompilerPass(new WorkerLocatorPass()); + $container->addCompilerPass(new ApiFirewallCompilerPass()); } }