Skip to content

Commit

Permalink
HMAC
Browse files Browse the repository at this point in the history
  • Loading branch information
sgrodzicki committed Aug 23, 2016
1 parent d37159b commit b1d3fed
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 22 deletions.
51 changes: 51 additions & 0 deletions DependencyInjection/Security/Factory/HmacFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Shoplo\ShoploBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;

class HmacFactory implements SecurityFactoryInterface
{
/**
* {@inheritdoc}
*/
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.wsse.' . $id;
$container
->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)
{
}
}
4 changes: 2 additions & 2 deletions DependencyInjection/ShoploExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
70 changes: 70 additions & 0 deletions Entity/Shop.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Shoplo\ShoploBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
* Shop
*
* @ORM\Table(name="shoplo_shops")
* @ORM\Entity(repositoryClass="Shoplo\ShoploBundle\Repository\ShopRepository")
*/
class Shop implements UserInterface
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

public function getId() : int
{
return $this->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()
{
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ShoploBundle
============

Shoplo bundle for Symfony 2
Shoplo bundle for Symfony
9 changes: 9 additions & 0 deletions Repository/ShopRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Shoplo\ShoploBundle\Repository;

use Doctrine\ORM\EntityRepository;

class ShopRepository extends EntityRepository
{
}
8 changes: 0 additions & 8 deletions Resources/config/services.xml

This file was deleted.

12 changes: 12 additions & 0 deletions Resources/config/services.yml
Original file line number Diff line number Diff line change
@@ -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
76 changes: 76 additions & 0 deletions Security/Authentication/Provider/HmacProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace Shoplo\ShoploBundle\Security\Authentication\Provider;

use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Shoplo\ShoploBundle\Security\Authentication\Token\HmacUserToken;

class HmacProvider implements AuthenticationProviderInterface
{
/**
* @var UserProviderInterface
*/
private $userProvider;

/**
* @var string
*/
private $secret;

/**
* @param UserProviderInterface $userProvider
* @param string $secret
*/
public function __construct(UserProviderInterface $userProvider, $secret)
{
$this->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;
}
}
68 changes: 68 additions & 0 deletions Security/Authentication/Token/HmacUserToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Shoplo\ShoploBundle\Security\Authentication\Token;

use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

class HmacUserToken extends AbstractToken
{
/**
* @var string
*/
private $digest;

/**
* @var array
*/
private $payload = [];

/**
* {@inheritdoc}
*/
public function __construct(array $roles = [])
{
parent::__construct($roles);

$this->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;
}
}
56 changes: 56 additions & 0 deletions Security/Firewall/HmacListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Shoplo\ShoploBundle\Security\Firewall;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Shoplo\ShoploBundle\Security\Authentication\Token\HmacUserToken;

class HmacListener implements ListenerInterface
{
protected $tokenStorage;
protected $authenticationManager;

/**
* {@inheritdoc}
*/
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager)
{
$this->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);
}
}
13 changes: 12 additions & 1 deletion ShoploBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new HmacFactory());
}
}
Loading

0 comments on commit b1d3fed

Please sign in to comment.