Skip to content

Instantly share code, notes, and snippets.

@jmikola
Created November 11, 2011 21:12
Show Gist options
  • Save jmikola/1359290 to your computer and use it in GitHub Desktop.
Save jmikola/1359290 to your computer and use it in GitHub Desktop.
Custom Symfony2 Voter for recent-activity and has-password security checks
<?xml version="1.0" encoding="utf-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="security.last_activity_listener.class">Acme\DemoBundle\Listener\SecurityLastActivityListener</parameter>
<parameter key="security.authorization.voter.class">Acme\DemoBundle\Security\Authorization\Voter</parameter>
<parameter key="security.authorization.last_activity_timeout">900</parameter>
<parameter key="security.authorization.last_activity_attribute">_last_activity_at</parameter>
</parameters>
<services>
<service id="security.authorization.voter" class="%security.authorization.voter.class%">
<tag name="security.voter" />
<argument>%security.authorization.last_activity_attribute%</argument>
<argument>%security.authorization.last_activity_timeout%</argument>
</service>
<service id="security.last_activity_listener" class="%security.last_activity_listener.class%">
<tag name="kernel.event_listener" event="security.interactive_login" method="onSecurityInteractiveLogin" />
<tag name="kernel.event_listener" event="kernel.response" method="onCoreResponse" priority="10" />
<argument type="service" id="security.context" />
<argument type="service" id="security.authorization.voter" />
<argument>%security.authorization.last_activity_attribute%</argument>
<argument type="service" id="logger" on-invalid="null" />
</service>
</services>
</container>
<?php
namespace Acme\DemoBundle\Listener;
use Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/**
* Initializes and updates the last activity timestamp in the current security
* token if it has not expired. In conjunction with AuthenticationTrustResolver,
* this listener allows for two-tier authentication.
*/
class SecurityLastActivityListener
{
/**
* @var Symfony\Component\Security\Core\SecurityContextInterface
*/
private $context;
/**
* @var Acme\DemoBundle\Security\Authorization\Voter
*/
private $voter;
/**
* @var integer
*/
private $lastActivityAttribute;
/**
* @var Symfony\Component\HttpKernel\Log\LoggerInterface
*/
private $logger;
/**
* Constructor.
*
* @param Symfony\Component\Security\Core\SecurityContextInterface $context
* @param Acme\DemoBundle\Security\Authorization\Voter $voter
* @param Symfony\Component\HttpKernel\Log\LoggerInterface $logger
*/
public function __construct(SecurityContextInterface $context, Voter $voter, $lastActivityAttribute, LoggerInterface $logger = null)
{
$this->context = $context;
$this->voter = $voter;
$this->lastActivityAttribute = $lastActivityAttribute;
$this->logger = $logger;
}
/**
* Listens to the "security.interactive_login" event and initializes the
* last activity timestamp when the user first logs in.
*
* @param Event $event
* @throws \InvalidArgumentException If the event's "token" parameter is invalid
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$token = $event->getAuthenticationToken();
$token->setAttribute($this->lastActivityAttribute, new \DateTime());
if (null !== $this->logger) {
$this->logger->debug('Initializing last activity timestamp in SecurityContext token');
}
}
/**
* Listens to the "core.response" event and updates the last activity
* timestamp if it is within the configured timeout interval.
*
* If this method does not update the timestamp, the same timeout interval
* will cause AuthenticationTrustResolver to consider the token's activity
* expired (so "fully authenticated" is considered "remembered") during
* subsequent requests.
*
* @param Event $event
* @return Response
*/
public function onCoreResponse(FilterResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
if (!($token = $this->context->getToken()) instanceof TokenInterface) {
return;
}
if (VoterInterface::ACCESS_GRANTED === $this->voter->vote($token, $this, array(Voter::USER_RECENTLY_AUTHENTICATED))) {
$token->setAttribute($this->lastActivityAttribute, new \DateTime());
if (null !== $this->logger) {
$this->logger->debug('Updating last activity timestamp in SecurityContext token');
}
}
}
}
<?php
namespace Acme\DemoBundle\Security\Authorization;
use Acme\DemoBundle\Model\User;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class Voter implements VoterInterface
{
const USER_HAS_PASSWORD = 'USER_HAS_PASSWORD';
const USER_RECENTLY_AUTHENTICATED = 'USER_RECENTLY_AUTHENTICATED';
private $lastActivityAttribute;
private $lastActivityTimeout;
public function __construct($lastActivityAttribute, $lastActivityTimeout)
{
$this->lastActivityAttribute = $lastActivityAttribute;
$this->lastActivityTimeout = $lastActivityTimeout;
}
public function supportsAttribute($attribute)
{
return in_array(
$attribute,
array(
self::USER_RECENTLY_AUTHENTICATED,
self::USER_HAS_PASSWORD
)
);
}
public function supportsClass($class)
{
return true;
}
public function vote(TokenInterface $token, $object, array $attributes)
{
$result = VoterInterface::ACCESS_ABSTAIN;
if (!$token->getUser() instanceof User) {
return VoterInterface::ACCESS_DENIED;
}
foreach ($attributes as $attribute) {
if (!$this->supportsAttribute($attribute)) {
continue;
}
$result = VoterInterface::ACCESS_DENIED;
if (self::USER_RECENTLY_AUTHENTICATED === $attribute &&
false === $this->isLastActivityExpired($token)) {
$result = VoterInterface::ACCESS_GRANTED;
}
if (self::USER_HAS_PASSWORD === $attribute &&
$this->isUserWithPassword($token)) {
$result = VoterInterface::ACCESS_GRANTED;
}
}
return $result;
}
/**
* Determines if the last activity timestamp in the token is expired. If no
* timestamp exists in the token, null will be returned since expiration
* cannot be determined.
*
* @param TokenInterface $token
* @return boolean or null if no timestamp exists in the token
* @throws \LogicException if the last activity timestamp in the token is invalid
*/
private function isLastActivityExpired(TokenInterface $token)
{
if ($token->hasAttribute($this->lastActivityAttribute)) {
$lastActivityAt = $token->getAttribute($this->lastActivityAttribute);
if (!$lastActivityAt instanceof \DateTime) {
throw new \LogicException(sprintf('The token attribute "%s" is not a DateTime object', $this->lastActivityAttribute));
}
return $lastActivityAt->getTimestamp() + $this->lastActivityTimeout <= time();
}
return true;
}
/**
* Checks whether the token contains a user with an empty password.
*
* @param TokenInterface $token
* @return boolean Whether the user's password is empty, or null if the token has no user
*/
private function isUserWithPassword(TokenInterface $token)
{
if ($token instanceof UsernamePasswordToken && ($user = $token->getUser()) instanceof UserInterface) {
return strlen($user->getPassword()) > 0;
}
return false;
}
}
@clubdesarrolladores
Copy link

nice

@mdjaman
Copy link

mdjaman commented Aug 14, 2014

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment