summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Core/Authentication/Token/AnonymousToken.php8
-rw-r--r--Core/Authentication/Token/PreAuthenticatedToken.php2
-rw-r--r--Core/Authentication/Token/UsernamePasswordToken.php2
-rw-r--r--Core/Role/Role.php3
-rw-r--r--Core/Role/RoleInterface.php2
-rw-r--r--Core/composer.json2
-rw-r--r--Csrf/composer.json2
-rw-r--r--Guard/Token/PostAuthenticationGuardToken.php8
-rw-r--r--Guard/composer.json2
-rw-r--r--Http/Firewall/UsernamePasswordJsonAuthenticationListener.php154
-rw-r--r--Http/Tests/Firewall/SwitchUserListenerTest.php13
-rw-r--r--Http/composer.json2
-rw-r--r--Tests/Http/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php145
-rw-r--r--composer.json2
14 files changed, 324 insertions, 23 deletions
diff --git a/Core/Authentication/Token/AnonymousToken.php b/Core/Authentication/Token/AnonymousToken.php
index 76c88ba..33b480c 100644
--- a/Core/Authentication/Token/AnonymousToken.php
+++ b/Core/Authentication/Token/AnonymousToken.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\Security\Core\Authentication\Token;
-use Symfony\Component\Security\Core\Role\RoleInterface;
+use Symfony\Component\Security\Core\Role\Role;
/**
* AnonymousToken represents an anonymous token.
@@ -25,9 +25,9 @@ class AnonymousToken extends AbstractToken
/**
* Constructor.
*
- * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client
- * @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string
- * @param RoleInterface[] $roles An array of roles
+ * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client
+ * @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string
+ * @param Role[] $roles An array of roles
*/
public function __construct($secret, $user, array $roles = array())
{
diff --git a/Core/Authentication/Token/PreAuthenticatedToken.php b/Core/Authentication/Token/PreAuthenticatedToken.php
index a5460f5..99fb082 100644
--- a/Core/Authentication/Token/PreAuthenticatedToken.php
+++ b/Core/Authentication/Token/PreAuthenticatedToken.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\Security\Core\Authentication\Token;
-use Symfony\Component\Security\Core\Role\RoleInterface;
+use Symfony\Component\Security\Core\Role\Role;
/**
* PreAuthenticatedToken implements a pre-authenticated token.
diff --git a/Core/Authentication/Token/UsernamePasswordToken.php b/Core/Authentication/Token/UsernamePasswordToken.php
index 71d19ad..7f94677 100644
--- a/Core/Authentication/Token/UsernamePasswordToken.php
+++ b/Core/Authentication/Token/UsernamePasswordToken.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\Security\Core\Authentication\Token;
-use Symfony\Component\Security\Core\Role\RoleInterface;
+use Symfony\Component\Security\Core\Role\Role;
/**
* UsernamePasswordToken implements a username and password token.
diff --git a/Core/Role/Role.php b/Core/Role/Role.php
index 5b50981..7cb4698 100644
--- a/Core/Role/Role.php
+++ b/Core/Role/Role.php
@@ -12,8 +12,7 @@
namespace Symfony\Component\Security\Core\Role;
/**
- * Role is a simple implementation of a RoleInterface where the role is a
- * string.
+ * Role is a simple implementation representing a role identified by a string.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
diff --git a/Core/Role/RoleInterface.php b/Core/Role/RoleInterface.php
index 3d4cbea..a0621ba 100644
--- a/Core/Role/RoleInterface.php
+++ b/Core/Role/RoleInterface.php
@@ -18,6 +18,8 @@ namespace Symfony\Component\Security\Core\Role;
* supported by at least one AccessDecisionManager.
*
* @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @deprecated The RoleInterface is deprecated since version 3.3 and will be removed in 4.0. Extend the Symfony\Component\Security\Core\Role\Role class instead.
*/
interface RoleInterface
{
diff --git a/Core/composer.json b/Core/composer.json
index 25cc061..b062419 100644
--- a/Core/composer.json
+++ b/Core/composer.json
@@ -44,7 +44,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
}
}
diff --git a/Csrf/composer.json b/Csrf/composer.json
index 4047fd5..913cc45 100644
--- a/Csrf/composer.json
+++ b/Csrf/composer.json
@@ -36,7 +36,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
}
}
diff --git a/Guard/Token/PostAuthenticationGuardToken.php b/Guard/Token/PostAuthenticationGuardToken.php
index 5b353d9..f566b71 100644
--- a/Guard/Token/PostAuthenticationGuardToken.php
+++ b/Guard/Token/PostAuthenticationGuardToken.php
@@ -12,7 +12,7 @@
namespace Symfony\Component\Security\Guard\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
-use Symfony\Component\Security\Core\Role\RoleInterface;
+use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\User\UserInterface;
/**
@@ -28,9 +28,9 @@ class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenIn
private $providerKey;
/**
- * @param UserInterface $user The user!
- * @param string $providerKey The provider (firewall) key
- * @param RoleInterface[]|string[] $roles An array of roles
+ * @param UserInterface $user The user!
+ * @param string $providerKey The provider (firewall) key
+ * @param (Role|string)[] $roles An array of roles
*
* @throws \InvalidArgumentException
*/
diff --git a/Guard/composer.json b/Guard/composer.json
index 4980923..4bf473a 100644
--- a/Guard/composer.json
+++ b/Guard/composer.json
@@ -32,7 +32,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
}
}
diff --git a/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php
new file mode 100644
index 0000000..bf3c621
--- /dev/null
+++ b/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php
@@ -0,0 +1,154 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Firewall;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\PropertyAccess\Exception\AccessException;
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Security;
+use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
+use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Symfony\Component\Security\Http\SecurityEvents;
+
+/**
+ * UsernamePasswordJsonAuthenticationListener is a stateless implementation of
+ * an authentication via a JSON document composed of a username and a password.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class UsernamePasswordJsonAuthenticationListener implements ListenerInterface
+{
+ private $tokenStorage;
+ private $authenticationManager;
+ private $providerKey;
+ private $successHandler;
+ private $failureHandler;
+ private $options;
+ private $logger;
+ private $eventDispatcher;
+ private $propertyAccessor;
+
+ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null)
+ {
+ $this->tokenStorage = $tokenStorage;
+ $this->authenticationManager = $authenticationManager;
+ $this->providerKey = $providerKey;
+ $this->successHandler = $successHandler;
+ $this->failureHandler = $failureHandler;
+ $this->logger = $logger;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->options = array_merge(array('username_path' => 'username', 'password_path' => 'password'), $options);
+ $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(GetResponseEvent $event)
+ {
+ $request = $event->getRequest();
+ $data = json_decode($request->getContent());
+
+ if (!$data instanceof \stdClass) {
+ throw new BadCredentialsException('Invalid JSON.');
+ }
+
+ try {
+ $username = $this->propertyAccessor->getValue($data, $this->options['username_path']);
+ } catch (AccessException $e) {
+ throw new BadCredentialsException(sprintf('The key "%s" must be provided.', $this->options['username_path']));
+ }
+
+ try {
+ $password = $this->propertyAccessor->getValue($data, $this->options['password_path']);
+ } catch (AccessException $e) {
+ throw new BadCredentialsException(sprintf('The key "%s" must be provided.', $this->options['password_path']));
+ }
+
+ if (!is_string($username)) {
+ throw new BadCredentialsException(sprintf('The key "%s" must be a string.', $this->options['username_path']));
+ }
+
+ if (strlen($username) > Security::MAX_USERNAME_LENGTH) {
+ throw new BadCredentialsException('Invalid username.');
+ }
+
+ if (!is_string($password)) {
+ throw new BadCredentialsException(sprintf('The key "%s" must be a string.', $this->options['password_path']));
+ }
+
+ try {
+ $token = new UsernamePasswordToken($username, $password, $this->providerKey);
+
+ $this->authenticationManager->authenticate($token);
+ $response = $this->onSuccess($request, $token);
+ } catch (AuthenticationException $e) {
+ $response = $this->onFailure($request, $e);
+ }
+
+ $event->setResponse($response);
+ }
+
+ private function onSuccess(Request $request, TokenInterface $token)
+ {
+ if (null !== $this->logger) {
+ $this->logger->info('User has been authenticated successfully.', array('username' => $token->getUsername()));
+ }
+
+ $this->tokenStorage->setToken($token);
+
+ if (null !== $this->eventDispatcher) {
+ $loginEvent = new InteractiveLoginEvent($request, $token);
+ $this->eventDispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
+ }
+
+ $response = $this->successHandler->onAuthenticationSuccess($request, $token);
+
+ if (!$response instanceof Response) {
+ throw new \RuntimeException('Authentication Success Handler did not return a Response.');
+ }
+
+ return $response;
+ }
+
+ private function onFailure(Request $request, AuthenticationException $failed)
+ {
+ if (null !== $this->logger) {
+ $this->logger->info('Authentication request failed.', array('exception' => $failed));
+ }
+
+ $token = $this->tokenStorage->getToken();
+ if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey()) {
+ $this->tokenStorage->setToken(null);
+ }
+
+ $response = $this->failureHandler->onAuthenticationFailure($request, $failed);
+
+ if (!$response instanceof Response) {
+ throw new \RuntimeException('Authentication Failure Handler did not return a Response.');
+ }
+
+ return $response;
+ }
+}
diff --git a/Http/Tests/Firewall/SwitchUserListenerTest.php b/Http/Tests/Firewall/SwitchUserListenerTest.php
index b80f8c6..920163a 100644
--- a/Http/Tests/Firewall/SwitchUserListenerTest.php
+++ b/Http/Tests/Firewall/SwitchUserListenerTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Security\Http\Tests\Firewall;
+use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\Security\Http\SecurityEvents;
@@ -66,7 +67,7 @@ class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
*/
public function testExitUserThrowsAuthenticationExceptionIfOriginalTokenCannotBeFound()
{
- $token = $this->getToken(array($this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleInterface')->getMock()));
+ $token = $this->getToken(array(new Role('the role')));
$this->tokenStorage->expects($this->any())->method('getToken')->will($this->returnValue($token));
$this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue('_exit'));
@@ -216,7 +217,7 @@ class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
*/
public function testSwitchUserIsDisallowed()
{
- $token = $this->getToken(array($this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleInterface')->getMock()));
+ $token = $this->getToken(array(new Role('the role')));
$this->tokenStorage->expects($this->any())->method('getToken')->will($this->returnValue($token));
$this->request->expects($this->any())->method('get')->with('_switch_user')->will($this->returnValue('kuba'));
@@ -231,8 +232,8 @@ class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
public function testSwitchUser()
{
- $token = $this->getToken(array($this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleInterface')->getMock()));
- $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock();
+ $token = $this->getToken(array(new Role('the role')));
+ $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
$user->expects($this->any())->method('getRoles')->will($this->returnValue(array()));
$this->tokenStorage->expects($this->any())->method('getToken')->will($this->returnValue($token));
@@ -261,8 +262,8 @@ class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
public function testSwitchUserKeepsOtherQueryStringParameters()
{
- $token = $this->getToken(array($this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleInterface')->getMock()));
- $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock();
+ $token = $this->getToken(array(new Role('the role')));
+ $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
$user->expects($this->any())->method('getRoles')->will($this->returnValue(array()));
$this->tokenStorage->expects($this->any())->method('getToken')->will($this->returnValue($token));
diff --git a/Http/composer.json b/Http/composer.json
index add5d3a..87adbf0 100644
--- a/Http/composer.json
+++ b/Http/composer.json
@@ -43,7 +43,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
}
}
diff --git a/Tests/Http/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php b/Tests/Http/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php
new file mode 100644
index 0000000..6b99a6d
--- /dev/null
+++ b/Tests/Http/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Tests\Http\Firewall;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\KernelInterface;
+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\Core\Security;
+use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
+use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
+use Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class UsernamePasswordJsonAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var UsernamePasswordJsonAuthenticationListener
+ */
+ private $listener;
+
+ private function createListener(array $options = array(), $success = true)
+ {
+ $tokenStorage = $this->getMock(TokenStorageInterface::class);
+ $authenticationManager = $this->getMock(AuthenticationManagerInterface::class);
+
+ if ($success) {
+ $authenticationManager->method('authenticate')->willReturn(true);
+ } else {
+ $authenticationManager->method('authenticate')->willThrowException(new AuthenticationException());
+ }
+
+ $authenticationSuccessHandler = $this->getMock(AuthenticationSuccessHandlerInterface::class);
+ $authenticationSuccessHandler->method('onAuthenticationSuccess')->willReturn(new Response('ok'));
+ $authenticationFailureHandler = $this->getMock(AuthenticationFailureHandlerInterface::class);
+ $authenticationFailureHandler->method('onAuthenticationFailure')->willReturn(new Response('ko'));
+
+ $this->listener = new UsernamePasswordJsonAuthenticationListener($tokenStorage, $authenticationManager, 'providerKey', $authenticationSuccessHandler, $authenticationFailureHandler, $options);
+ }
+
+ public function testHandleSuccess()
+ {
+ $this->createListener();
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"username": "dunglas", "password": "foo"}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ $this->assertEquals('ok', $event->getResponse()->getContent());
+ }
+
+ public function testHandleFailure()
+ {
+ $this->createListener(array(), false);
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"username": "dunglas", "password": "foo"}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ $this->assertEquals('ko', $event->getResponse()->getContent());
+ }
+
+ public function testUsePath()
+ {
+ $this->createListener(array('username_path' => 'user.login', 'password_path' => 'user.pwd'));
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"user": {"login": "dunglas", "pwd": "foo"}}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ $this->assertEquals('ok', $event->getResponse()->getContent());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
+ */
+ public function testAttemptAuthenticationNoUsername()
+ {
+ $this->createListener();
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"usr": "dunglas", "password": "foo"}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
+ */
+ public function testAttemptAuthenticationNoPassword()
+ {
+ $this->createListener();
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"username": "dunglas", "pass": "foo"}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
+ */
+ public function testAttemptAuthenticationUsernameNotAString()
+ {
+ $this->createListener();
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"username": 1, "password": "foo"}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
+ */
+ public function testAttemptAuthenticationPasswordNotAString()
+ {
+ $this->createListener();
+ $request = new Request(array(), array(), array(), array(), array(), array(), '{"username": "dunglas", "password": 1}');
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
+ */
+ public function testAttemptAuthenticationUsernameTooLong()
+ {
+ $this->createListener();
+ $username = str_repeat('x', Security::MAX_USERNAME_LENGTH + 1);
+ $request = new Request(array(), array(), array(), array(), array(), array(), sprintf('{"username": "%s", "password": 1}', $username));
+ $event = new GetResponseEvent($this->getMock(KernelInterface::class), $request, KernelInterface::MASTER_REQUEST);
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/composer.json b/composer.json
index 430ea54..3a63c8e 100644
--- a/composer.json
+++ b/composer.json
@@ -56,7 +56,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "3.3-dev"
}
}
}