Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Keep It Simple Security (Symfony Cafe 28-01-2016)

Keep It Simple Security (Symfony Cafe 28-01-2016)

Oleg Zinchenko

January 29, 2016
Tweet

More Decks by Oleg Zinchenko

Other Decks in Technology

Transcript

  1. security.yml security: firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: pattern:

    ^/ form_login: login_path: /login check_path: /login_check provider: fos_userbundle logout: true anonymous: true access_control: - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/user, role: ROLE_USER } - { path: ^/admin/, role: ROLE_ADMIN }
  2. Token <?php namespace AppBundle\Security\Authentication\Token; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; class WsseUserToken extends AbstractToken

    { public $created; public $digest; public $nonce; public function __construct(array $roles = array()) { parent::__construct($roles); // If the user has roles, consider it authenticated $this->setAuthenticated(count($roles) > 0); } public function getCredentials() { return ''; } }
  3. Listener <?php namespace AppBundle\Security\Firewall; use AppBundle\Security\Authentication\Token\WsseUserToken; class WsseListener implements ListenerInterface

    { protected $tokenStorage; protected $authenticationManager; public function handle(GetResponseEvent $event) { $request = $event->getRequest(); $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/'; if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) { return; } $token = new WsseUserToken(); $token->setUser($matches[1]); ... try { $authToken = $this->authenticationManager->authenticate($token); $this->tokenStorage->setToken($authToken); return; } catch (AuthenticationException $failed) { ... } $response = new Response(); $response->setStatusCode(Response::HTTP_FORBIDDEN); $event->setResponse($response); } }
  4. Authentication Manager <?php namespace AppBundle\Security\Authentication\Provider; use AppBundle\Security\Authentication\Token\WsseUserToken; class WsseProvider implements

    AuthenticationProviderInterface { private $userProvider; public function authenticate(TokenInterface $token) { $user = $this->userProvider->loadUserByUsername($token->getUsername()); if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) { $authenticatedToken = new WsseUserToken($user->getRoles()); $authenticatedToken->setUser($user); return $authenticatedToken; } throw new AuthenticationException('The WSSE authentication failed.'); } protected function validateDigest($digest, $nonce, $created, $secret) { ... } public function supports(TokenInterface $token) { return $token instanceof WsseUserToken; } }
  5. Factory <?php namespace AppBundle\DependencyInjection\Security\Factory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; class WsseFactory implements SecurityFactoryInterface

    { public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) { $providerId = 'security.authentication.provider.wsse.'.$id; $container ->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider')) ->replaceArgument(0, new Reference($userProvider)) ; $listenerId = 'security.authentication.listener.wsse.'.$id; $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener')); return array($providerId, $listenerId, $defaultEntryPoint); } public function getPosition() { return 'pre_auth'; } public function getKey() { return 'wsse'; } public function addConfiguration(NodeDefinition $node) { } }
  6. Voter (1) <?php namespace AppBundle\Security; use Symfony\Component\Security\Core\Authorization\Voter\Voter; class PostVoter extends

    Voter { const VIEW = 'view'; const EDIT = 'edit'; protected function supports($attribute, $subject) { if (!in_array($attribute, array(self::VIEW, self::EDIT))) { return false; } if (!$subject instanceof Post) { return false; } return true; } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $user = $token->getUser(); if (!$user instanceof User) { return false; } $post = $subject; switch($attribute) { case self::VIEW: return $this->canView($post, $user); case self::EDIT: return $this->canEdit($post, $user); } throw new \LogicException('This code should not be reached!'); } }
  7. Voter (2) <?php private function canView(Post $post, User $user) {

    if ($this->canEdit($post, $user)) { return true; } return !$post->isPrivate(); } private function canEdit(Post $post, User $user) { return $user === $post->getOwner(); } }