kiss: keep it simple security - oleg zinchenko - symfony cafe kyiv

21

Upload: grossum-software-outsourcing

Post on 12-Apr-2017

141 views

Category:

Software


2 download

TRANSCRIPT

cystbearErlanger

Symfony expert

MongoDB adept

OSS doerhttps://twitter.com/1cdecoderhttps://github.com/cystbearhttp://trinity.ck.ua/

+ = ❤

security.ymlsecurity: 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 }

Good Parts

TokenListenerAuthentication Manager/ProviderFactory

Token<?phpnamespace 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 ''; }}

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);

}

}

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;

}

}

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)

{

}

}

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!');

}

}

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();

}

}