From b1d3fedf643bd28c7de07ee9ea93352f16233660 Mon Sep 17 00:00:00 2001 From: Sebastian Grodzicki Date: Tue, 23 Aug 2016 11:33:04 +0200 Subject: [PATCH] HMAC --- .../Security/Factory/HmacFactory.php | 51 +++++++++++++ DependencyInjection/ShoploExtension.php | 4 +- Entity/Shop.php | 70 +++++++++++++++++ README.md | 2 +- Repository/ShopRepository.php | 9 +++ Resources/config/services.xml | 8 -- Resources/config/services.yml | 12 +++ .../Authentication/Provider/HmacProvider.php | 76 +++++++++++++++++++ .../Authentication/Token/HmacUserToken.php | 68 +++++++++++++++++ Security/Firewall/HmacListener.php | 56 ++++++++++++++ ShoploBundle.php | 13 +++- composer.json | 23 +++--- 12 files changed, 370 insertions(+), 22 deletions(-) create mode 100644 DependencyInjection/Security/Factory/HmacFactory.php create mode 100644 Entity/Shop.php create mode 100644 Repository/ShopRepository.php delete mode 100644 Resources/config/services.xml create mode 100644 Resources/config/services.yml create mode 100644 Security/Authentication/Provider/HmacProvider.php create mode 100644 Security/Authentication/Token/HmacUserToken.php create mode 100644 Security/Firewall/HmacListener.php diff --git a/DependencyInjection/Security/Factory/HmacFactory.php b/DependencyInjection/Security/Factory/HmacFactory.php new file mode 100644 index 0000000..1c1cc52 --- /dev/null +++ b/DependencyInjection/Security/Factory/HmacFactory.php @@ -0,0 +1,51 @@ +setDefinition($providerId, new DefinitionDecorator('shoplo.security.authentication.provider')) + ->replaceArgument(0, new Reference($userProvider)); + + $listenerId = 'security.authentication.listener.wsse.' . $id; + $container->setDefinition($listenerId, new DefinitionDecorator('shoplo.security.authentication.listener')); + + return [$providerId, $listenerId, $defaultEntryPoint]; + } + + /** + * {@inheritdoc} + */ + public function getPosition() + { + return 'pre_auth'; + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return 'shoplo_hmac'; + } + + /** + * {@inheritdoc} + */ + public function addConfiguration(NodeDefinition $node) + { + } +} diff --git a/DependencyInjection/ShoploExtension.php b/DependencyInjection/ShoploExtension.php index 1191daf..1162401 100644 --- a/DependencyInjection/ShoploExtension.php +++ b/DependencyInjection/ShoploExtension.php @@ -22,7 +22,7 @@ public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.xml'); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.yml'); } } diff --git a/Entity/Shop.php b/Entity/Shop.php new file mode 100644 index 0000000..5c4154c --- /dev/null +++ b/Entity/Shop.php @@ -0,0 +1,70 @@ +id; + } + + /** + * {@inheritdoc} + */ + public function getRoles() + { + return [ + 'ROLE_USER', + ]; + } + + /** + * {@inheritdoc} + */ + public function getPassword() + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getSalt() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getUsername() + { + return $this->getId(); + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + } +} diff --git a/README.md b/README.md index 76de128..2ed57a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ ShoploBundle ============ -Shoplo bundle for Symfony 2 \ No newline at end of file +Shoplo bundle for Symfony \ No newline at end of file diff --git a/Repository/ShopRepository.php b/Repository/ShopRepository.php new file mode 100644 index 0000000..574937b --- /dev/null +++ b/Repository/ShopRepository.php @@ -0,0 +1,9 @@ + - - - - - diff --git a/Resources/config/services.yml b/Resources/config/services.yml new file mode 100644 index 0000000..4b84da6 --- /dev/null +++ b/Resources/config/services.yml @@ -0,0 +1,12 @@ +services: + shoplo.security.authentication.provider: + class: Shoplo\ShoploBundle\Security\Authentication\Provider\HmacProvider + arguments: + - '' + - '%shoplo_app_secret%' + public: false + + shoplo.security.authentication.listener: + class: Shoplo\ShoploBundle\Security\Firewall\HmacListener + arguments: ['@security.token_storage', '@security.authentication.manager'] + public: false diff --git a/Security/Authentication/Provider/HmacProvider.php b/Security/Authentication/Provider/HmacProvider.php new file mode 100644 index 0000000..d692045 --- /dev/null +++ b/Security/Authentication/Provider/HmacProvider.php @@ -0,0 +1,76 @@ +userProvider = $userProvider; + $this->secret = $secret; + } + + /** + * {@inheritdoc} + */ + public function authenticate(TokenInterface $token) + { + $user = $this->userProvider->loadUserByUsername($token->getUsername()); + + /** @var HmacUserToken $token */ + if ($user && $this->validateDigest($token->getDigest(), $token->getPayload())) { + $authenticatedToken = new HmacUserToken($user->getRoles()); + $authenticatedToken->setUser($user); + + return $authenticatedToken; + } + + throw new AuthenticationException('The HMAC authentication failed.'); + } + + /** + * Validate HMAC digest + * + * @param string $digest + * @param array $payload + * + * @see https://docs.shoplo.com/api/webhook + * + * @return bool + */ + protected function validateDigest($digest, array $payload) + { + $algo = 'sha256'; + $data = http_build_query($payload); + $key = $this->secret; + $hash = hash_hmac($algo, $data, $key); + $expected = base64_encode($hash); + + return hash_equals($expected, $digest); + } + + public function supports(TokenInterface $token) + { + return $token instanceof HmacUserToken; + } +} diff --git a/Security/Authentication/Token/HmacUserToken.php b/Security/Authentication/Token/HmacUserToken.php new file mode 100644 index 0000000..ca53362 --- /dev/null +++ b/Security/Authentication/Token/HmacUserToken.php @@ -0,0 +1,68 @@ +setAuthenticated(count($roles) > 0); + } + + /** + * {@inheritdoc} + */ + public function getCredentials() + { + return ''; + } + + /** + * @return string + */ + public function getDigest() + { + return $this->digest; + } + + /** + * @param string $digest + */ + public function setDigest($digest) + { + $this->digest = $digest; + } + + /** + * @return array + */ + public function getPayload() + { + return $this->payload; + } + + /** + * @param array $payload + */ + public function setPayload(array $payload) + { + $this->payload = $payload; + } +} diff --git a/Security/Firewall/HmacListener.php b/Security/Firewall/HmacListener.php new file mode 100644 index 0000000..5cadb73 --- /dev/null +++ b/Security/Firewall/HmacListener.php @@ -0,0 +1,56 @@ +tokenStorage = $tokenStorage; + $this->authenticationManager = $authenticationManager; + } + + /** + * {@inheritdoc} + */ + public function handle(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if (!$request->headers->has('shoplo-shop-id') || !$request->headers->has('shoplo-hmac-sha256')) { + return; + } + + $token = new HmacUserToken(); + $token->setUser($request->headers->get('shoplo-shop-id')); + $token->setDigest($request->headers->get('shoplo-hmac-sha256')); + $token->setPayload($request->request->all()); + + try { + $authToken = $this->authenticationManager->authenticate($token); + $this->tokenStorage->setToken($authToken); + + return; + } catch (AuthenticationException $failed) { + } + + // By default deny authorization + $response = new Response(); + $response->setStatusCode(Response::HTTP_FORBIDDEN); + $event->setResponse($response); + } +} diff --git a/ShoploBundle.php b/ShoploBundle.php index d745e75..1a2db50 100644 --- a/ShoploBundle.php +++ b/ShoploBundle.php @@ -2,9 +2,20 @@ namespace Shoplo\ShoploBundle; +use Shoplo\ShoploBundle\DependencyInjection\Security\Factory\HmacFactory; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\DependencyInjection\ContainerBuilder; class ShoploBundle extends Bundle { + /** + * {@inheritdoc} + */ + public function build(ContainerBuilder $container) + { + parent::build($container); -} \ No newline at end of file + $extension = $container->getExtension('security'); + $extension->addSecurityListenerFactory(new HmacFactory()); + } +} diff --git a/composer.json b/composer.json index 985f394..401b4f4 100644 --- a/composer.json +++ b/composer.json @@ -2,8 +2,10 @@ "name": "shoplo/shoplo-bundle", "type": "symfony-bundle", "description": "Symfony ShoploBundle", - "keywords": ["Shoplo"], - "homepage": "http://www.shoplo.com", + "keywords": [ + "Shoplo" + ], + "homepage": "https://github.com/Shoplo/ShoploBundle", "license": "MIT", "authors": [ { @@ -16,15 +18,16 @@ } ], "minimum-stability": "dev", + "prefer-stable": true, "require": { - "php": ">=5.3.2", - "symfony/framework-bundle": "2.1.*", - "symfony/security-bundle": "2.1.*" + "php": "^7.0", + "symfony/framework-bundle": "~2.3|~3.0", + "symfony/security-bundle": "~2.3|~3.0", + "doctrine/orm": "^2.5" }, "autoload": { - "psr-0": { - "Shoplo\\ShoploBundle": "" + "psr-4": { + "Shoplo\\ShoploBundle\\": "" } - }, - "target-dir": "Shoplo/ShoploBundle" -} \ No newline at end of file + } +}