├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Tests ├── Firewall │ └── GuardAuthenticationListenerTest.php ├── GuardAuthenticatorHandlerTest.php └── Provider │ └── GuardAuthenticationProviderTest.php ├── composer.json ├── phpunit.xml.dist └── src ├── AbstractGuardAuthenticator.php ├── Authenticator └── AbstractFormLoginAuthenticator.php ├── Exception └── CustomAuthenticationException.php ├── Firewall └── GuardAuthenticationListener.php ├── GuardAuthenticatorHandler.php ├── GuardAuthenticatorInterface.php ├── Provider └── GuardAuthenticationProvider.php └── Token ├── GuardTokenInterface.php ├── PostAuthenticationGuardToken.php └── PreAuthenticationGuardToken.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - 7.0 9 | - hhvm 10 | 11 | branches: 12 | only: 13 | - master 14 | 15 | matrix: 16 | fast_finish: true 17 | include: 18 | - php: 5.3 19 | env: deps="low" 20 | - php: 5.5 21 | env: SYMFONY_VERSION=2.6.* 22 | - php: 5.5 23 | env: SYMFONY_VERSION='2.7.*@dev' 24 | 25 | cache: 26 | directories: 27 | - $HOME/.composer/cache 28 | 29 | before_script: 30 | - composer self-update 31 | - if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi 32 | - if [ "$deps" = "low" ]; then export COMPOSER_FLAGS='--prefer-lowest'; fi 33 | - composer update $COMPOSER_FLAGS 34 | 35 | script: 36 | - vendor/bin/phpunit 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) KnpUniversity 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KnpGuard 2 | 3 | Add simple and beautiful authentication to Symfony's security component in Silex 4 | and anywhere else. 5 | 6 | [![Build Status](https://travis-ci.org/knpuniversity/KnpUGuard.svg?branch=master)](https://travis-ci.org/knpuniversity/KnpUGuard) 7 | 8 | **This library is *deprecated* since Symfony 2.8 and won't work with Symfony 3.** 9 | 10 | The original purpose was to get feedback and use-cases from people so that we can merge this feature into Symfony itself 11 | (see [symfony/symfony#14673](https://github.com/symfony/symfony/pull/14673)). 12 | 13 | Now it's good (see [news from Symfony](http://symfony.com/blog/new-in-symfony-2-8-guard-authentication-component)). 14 | 15 | ## Upgrade to Symfony 3 16 | 17 | On Symfony 2.8, use the official [Guard component](https://symfony.com/doc/master/cookbook/security/guard-authentication.html). 18 | 19 | ### Step 1 - Remove the library from your composer.json 20 | 21 | Be sure to be on Symfony 2.8, open `composer.json` file and remove the library: 22 | 23 | Before: 24 | ```json 25 | { 26 | "require": { 27 | "php": ">=5.5", 28 | "symfony/symfony": "~2.8", 29 | "...": "...", 30 | "knpuniversity/guard-bundle": "~0.1@dev" 31 | }, 32 | } 33 | ``` 34 | 35 | Now: 36 | ```json 37 | { 38 | "require": { 39 | "php": ">=5.5", 40 | "symfony/symfony": "~2.8", 41 | "...": "..." 42 | }, 43 | } 44 | ``` 45 | 46 | ### Step 2 - Remove it from your AppKernel 47 | 48 | If you're using the Symfony framework, remove the KnpUGuardBundle from `AppKernel.php`. 49 | 50 | ### Step 3 - Modify firewall(s) 51 | 52 | Open and modify `security.yml` file, replace in your firewall(s) key(s) `knpu_guard` by `guard`: 53 | 54 | Before: 55 | ```yaml 56 | # app/config/security.yml 57 | security: 58 | # ... 59 | 60 | firewalls: 61 | # ... 62 | 63 | main: 64 | anonymous: ~ 65 | logout: ~ 66 | 67 | knpu_guard: 68 | authenticators: 69 | - app.form_login_authenticator 70 | 71 | # maybe other things, like form_login, remember_me, etc 72 | # ... 73 | ``` 74 | 75 | Now: 76 | ```yaml 77 | # app/config/security.yml 78 | security: 79 | # ... 80 | 81 | firewalls: 82 | # ... 83 | 84 | main: 85 | anonymous: ~ 86 | logout: ~ 87 | 88 | guard: 89 | authenticators: 90 | - app.form_login_authenticator 91 | 92 | # maybe other things, like form_login, remember_me, etc 93 | # ... 94 | ``` 95 | 96 | ### Step 4 - Update Authenticator(s) 97 | 98 | Update uses in Authenticator(s) class(es). 99 | 100 | **Warning:** checkCredentials() NOW must return true in order for authentication to be successful. 101 | In KnpUGuard, if you did NOT throw an AuthenticationException, it would pass. 102 | 103 | Before: 104 | ```php 105 | use KnpU\Guard\Authenticator\AbstractFormLoginAuthenticator; 106 | use KnpU\Guard\...; 107 | // ... 108 | 109 | class FormLoginAuthenticator extends AbstractFormLoginAuthenticator 110 | { 111 | // ... 112 | 113 | public function checkCredentials($credentials, UserInterface $user) 114 | { 115 | // ... 116 | 117 | if ($password !== 'correctPassword') { 118 | throw new AuthenticationException(); 119 | } 120 | 121 | // do nothing, allow authentication to pass 122 | } 123 | 124 | // ... 125 | } 126 | ``` 127 | 128 | Now: 129 | ```php 130 | use Symfony\Component\Security\Guard\AbstractFormLoginAuthenticator; 131 | use Symfony\Component\Security\Guard\...; 132 | // ... 133 | 134 | class FormLoginAuthenticator extends AbstractFormLoginAuthenticator 135 | { 136 | // ... 137 | 138 | public function checkCredentials($credentials, UserInterface $user) 139 | { 140 | // ... 141 | 142 | if ($password !== 'correctPassword') { 143 | // returning anything NOT true will cause an authentication failure 144 | return; 145 | // or, you can still throw an AuthenticationException if you want to 146 | // throw new AuthenticationException(); 147 | } 148 | 149 | // return true to make authentication successful 150 | return true; 151 | } 152 | 153 | // ... 154 | } 155 | ``` 156 | 157 | ### Step 5 - Yes we can test it 158 | 159 | That's it! Try it out, and then upgrade to Symfony 3 :). 160 | 161 | - [http://symfony.com/doc/current/cookbook/upgrade/major_version.html](http://symfony.com/doc/current/cookbook/upgrade/major_version.html) 162 | 163 | ## Documentation 164 | 165 | Find a full tutorial here: https://knpuniversity.com/screencast/guard 166 | 167 | ## Basic Usage 168 | 169 | Check out the [Tutorial](https://knpuniversity.com/screencast/guard) for real documentation. 170 | But here's the basic idea. 171 | 172 | Guard works by creating a single class - an **authenticator** - that handles *everything* 173 | about how you want to authenticate your users. And authenticator implements 174 | [KnpU\Guard\GuardAuthenticatorInterface](https://github.com/knpuniversity/KnpUGuard/blob/master/src/GuardAuthenticatorInterface.php)) 175 | 176 | Here are some real-world examples from the tutorial: 177 | 178 | Goal | Code | Tutorial 179 | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------- 180 | Authenticate by reading an `X-TOKEN` header | [ApiTokenAuthenticator.php](https://github.com/knpuniversity/guard-tutorial/blob/finished/src/AppBundle/Security/ApiTokenAuthenticator.php) | [How to Authenticate via an API Token](https://knpuniversity.com/screencast/guard/api-token) 181 | Form login authentication | [FormLoginAuthenticator.php](https://github.com/knpuniversity/guard-tutorial/blob/finished/src/AppBundle/Security/FormLoginAuthenticator.php) | [How to Create a Login Form](https://knpuniversity.com/screencast/guard/login-form) 182 | Social Login (Facebook) | [FacebookAuthenticator.php](https://github.com/knpuniversity/guard-tutorial/blob/finished/src/AppBundle/Security/FacebookAuthenticator.php) | [Social Login with Facebook](https://knpuniversity.com/screencast/guard/social-login) 183 | 184 | ## Contributing 185 | 186 | Find a bug or a use-case that this doesn't support? [Open an Issue](https://github.com/knpuniversity/KnpUGuard/issues) 187 | so we can make things better. 188 | 189 | ## License 190 | 191 | This library is under the MIT license. See the complete license in the LICENSE file. 192 | -------------------------------------------------------------------------------- /Tests/Firewall/GuardAuthenticationListenerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace KnpU\Guard\Tests\Firewall; 13 | 14 | use Symfony\Component\HttpFoundation\Request; 15 | use Symfony\Component\HttpFoundation\Response; 16 | use KnpU\Guard\Firewall\GuardAuthenticationListener; 17 | use KnpU\Guard\Token\PreAuthenticationGuardToken; 18 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 19 | 20 | /** 21 | * @author Ryan Weaver 22 | */ 23 | class GuardAuthenticationListenerTest extends \PHPUnit_Framework_TestCase 24 | { 25 | private $authenticationManager; 26 | private $guardAuthenticatorHandler; 27 | private $event; 28 | private $logger; 29 | private $request; 30 | private $rememberMeServices; 31 | 32 | public function testHandleSuccess() 33 | { 34 | $authenticator = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 35 | $authenticateToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); 36 | $providerKey = 'my_firewall'; 37 | 38 | $credentials = array('username' => 'weaverryan', 'password' => 'all_your_base'); 39 | $authenticator 40 | ->expects($this->once()) 41 | ->method('getCredentials') 42 | ->with($this->equalTo($this->request)) 43 | ->will($this->returnValue($credentials)); 44 | 45 | // a clone of the token that should be created internally 46 | $uniqueGuardKey = 'my_firewall_0'; 47 | $nonAuthedToken = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey); 48 | 49 | $this->authenticationManager 50 | ->expects($this->once()) 51 | ->method('authenticate') 52 | ->with($this->equalTo($nonAuthedToken)) 53 | ->will($this->returnValue($authenticateToken)); 54 | 55 | $this->guardAuthenticatorHandler 56 | ->expects($this->once()) 57 | ->method('authenticateWithToken') 58 | ->with($authenticateToken, $this->request); 59 | 60 | $this->guardAuthenticatorHandler 61 | ->expects($this->once()) 62 | ->method('handleAuthenticationSuccess') 63 | ->with($authenticateToken, $this->request, $authenticator, $providerKey); 64 | 65 | $listener = new GuardAuthenticationListener( 66 | $this->guardAuthenticatorHandler, 67 | $this->authenticationManager, 68 | $providerKey, 69 | array($authenticator), 70 | $this->logger 71 | ); 72 | 73 | $listener->setRememberMeServices($this->rememberMeServices); 74 | // should never be called - our handleAuthenticationSuccess() does not return a Response 75 | $this->rememberMeServices 76 | ->expects($this->never()) 77 | ->method('loginSuccess'); 78 | 79 | $listener->handle($this->event); 80 | } 81 | 82 | public function testHandleSuccessWithRememberMe() 83 | { 84 | $authenticator = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 85 | $authenticateToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); 86 | $providerKey = 'my_firewall_with_rememberme'; 87 | 88 | $authenticator 89 | ->expects($this->once()) 90 | ->method('getCredentials') 91 | ->with($this->equalTo($this->request)) 92 | ->will($this->returnValue(array('username' => 'anything_not_empty'))); 93 | 94 | $this->authenticationManager 95 | ->expects($this->once()) 96 | ->method('authenticate') 97 | ->will($this->returnValue($authenticateToken)); 98 | 99 | $successResponse = new Response('Success!'); 100 | $this->guardAuthenticatorHandler 101 | ->expects($this->once()) 102 | ->method('handleAuthenticationSuccess') 103 | ->will($this->returnValue($successResponse)); 104 | 105 | $listener = new GuardAuthenticationListener( 106 | $this->guardAuthenticatorHandler, 107 | $this->authenticationManager, 108 | $providerKey, 109 | array($authenticator), 110 | $this->logger 111 | ); 112 | 113 | $listener->setRememberMeServices($this->rememberMeServices); 114 | $authenticator->expects($this->once()) 115 | ->method('supportsRememberMe') 116 | ->will($this->returnValue(true)); 117 | // should be called - we do have a success Response 118 | $this->rememberMeServices 119 | ->expects($this->once()) 120 | ->method('loginSuccess'); 121 | 122 | $listener->handle($this->event); 123 | } 124 | 125 | public function testHandleCatchesAuthenticationException() 126 | { 127 | $authenticator = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 128 | $providerKey = 'my_firewall2'; 129 | 130 | $authException = new AuthenticationException('Get outta here crazy user with a bad password!'); 131 | $authenticator 132 | ->expects($this->once()) 133 | ->method('getCredentials') 134 | ->will($this->throwException($authException)); 135 | 136 | // this is not called 137 | $this->authenticationManager 138 | ->expects($this->never()) 139 | ->method('authenticate'); 140 | 141 | $this->guardAuthenticatorHandler 142 | ->expects($this->once()) 143 | ->method('handleAuthenticationFailure') 144 | ->with($authException, $this->request, $authenticator); 145 | 146 | $listener = new GuardAuthenticationListener( 147 | $this->guardAuthenticatorHandler, 148 | $this->authenticationManager, 149 | $providerKey, 150 | array($authenticator), 151 | $this->logger 152 | ); 153 | 154 | $listener->handle($this->event); 155 | } 156 | 157 | public function testReturnNullToSkipAuth() 158 | { 159 | $authenticatorA = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 160 | $authenticatorB = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 161 | $providerKey = 'my_firewall3'; 162 | 163 | $authenticatorA 164 | ->expects($this->once()) 165 | ->method('getCredentials') 166 | ->will($this->returnValue(null)); 167 | $authenticatorB 168 | ->expects($this->once()) 169 | ->method('getCredentials') 170 | ->will($this->returnValue(null)); 171 | 172 | // this is not called 173 | $this->authenticationManager 174 | ->expects($this->never()) 175 | ->method('authenticate'); 176 | 177 | $this->guardAuthenticatorHandler 178 | ->expects($this->never()) 179 | ->method('handleAuthenticationSuccess'); 180 | 181 | $listener = new GuardAuthenticationListener( 182 | $this->guardAuthenticatorHandler, 183 | $this->authenticationManager, 184 | $providerKey, 185 | array($authenticatorA, $authenticatorB), 186 | $this->logger 187 | ); 188 | 189 | $listener->handle($this->event); 190 | } 191 | 192 | protected function setUp() 193 | { 194 | $this->authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager') 195 | ->disableOriginalConstructor() 196 | ->getMock(); 197 | 198 | $this->guardAuthenticatorHandler = $this->getMockBuilder('KnpU\Guard\GuardAuthenticatorHandler') 199 | ->disableOriginalConstructor() 200 | ->getMock(); 201 | 202 | $this->request = new Request(array(), array(), array(), array(), array(), array()); 203 | 204 | $this->event = $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false); 205 | $this->event 206 | ->expects($this->any()) 207 | ->method('getRequest') 208 | ->will($this->returnValue($this->request)); 209 | 210 | $this->logger = $this->getMock('Psr\Log\LoggerInterface'); 211 | $this->rememberMeServices = $this->getMock('Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface'); 212 | } 213 | 214 | protected function tearDown() 215 | { 216 | $this->authenticationManager = null; 217 | $this->guardAuthenticatorHandler = null; 218 | $this->event = null; 219 | $this->logger = null; 220 | $this->request = null; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /Tests/GuardAuthenticatorHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace KnpU\Guard\Tests; 13 | 14 | use Symfony\Component\HttpFoundation\Request; 15 | use Symfony\Component\HttpFoundation\Response; 16 | use KnpU\Guard\GuardAuthenticatorHandler; 17 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 18 | use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; 19 | use Symfony\Component\Security\Http\SecurityEvents; 20 | 21 | /** 22 | * @author Ryan Weaver 23 | */ 24 | class GuardAuthenticatorHandlerTest extends \PHPUnit_Framework_TestCase 25 | { 26 | private $tokenStorage; 27 | private $dispatcher; 28 | private $token; 29 | private $request; 30 | private $guardAuthenticator; 31 | 32 | public function testAuthenticateWithToken() 33 | { 34 | $this->tokenStorage->expects($this->once()) 35 | ->method('setToken') 36 | ->with($this->token); 37 | 38 | $loginEvent = new InteractiveLoginEvent($this->request, $this->token); 39 | 40 | $this->dispatcher 41 | ->expects($this->once()) 42 | ->method('dispatch') 43 | ->with($this->equalTo(SecurityEvents::INTERACTIVE_LOGIN), $this->equalTo($loginEvent)) 44 | ; 45 | 46 | $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher); 47 | $handler->authenticateWithToken($this->token, $this->request); 48 | } 49 | 50 | public function testHandleAuthenticationSuccess() 51 | { 52 | $providerKey = 'my_handleable_firewall'; 53 | $response = new Response('Guard all the things!'); 54 | $this->guardAuthenticator->expects($this->once()) 55 | ->method('onAuthenticationSuccess') 56 | ->with($this->request, $this->token, $providerKey) 57 | ->will($this->returnValue($response)); 58 | 59 | $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher); 60 | $actualResponse = $handler->handleAuthenticationSuccess($this->token, $this->request, $this->guardAuthenticator, $providerKey); 61 | $this->assertSame($response, $actualResponse); 62 | } 63 | 64 | public function testHandleAuthenticationFailure() 65 | { 66 | $this->tokenStorage->expects($this->once()) 67 | ->method('setToken') 68 | ->with(null); 69 | $authException = new AuthenticationException('Bad password!'); 70 | 71 | $response = new Response('Try again, but with the right password!'); 72 | $this->guardAuthenticator->expects($this->once()) 73 | ->method('onAuthenticationFailure') 74 | ->with($this->request, $authException) 75 | ->will($this->returnValue($response)); 76 | 77 | $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher); 78 | $actualResponse = $handler->handleAuthenticationFailure($authException, $this->request, $this->guardAuthenticator); 79 | $this->assertSame($response, $actualResponse); 80 | } 81 | 82 | protected function setUp() 83 | { 84 | $this->tokenStorage = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'); 85 | $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); 86 | $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); 87 | $this->request = new Request(array(), array(), array(), array(), array(), array()); 88 | $this->guardAuthenticator = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 89 | } 90 | 91 | protected function tearDown() 92 | { 93 | $this->tokenStorage = null; 94 | $this->dispatcher = null; 95 | $this->token = null; 96 | $this->request = null; 97 | $this->guardAuthenticator = null; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Tests/Provider/GuardAuthenticationProviderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace KnpU\Guard\Tests\Provider; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; 15 | use KnpU\Guard\Provider\GuardAuthenticationProvider; 16 | use KnpU\Guard\Token\PostAuthenticationGuardToken; 17 | 18 | /** 19 | * @author Ryan Weaver 20 | */ 21 | class GuardAuthenticationProviderTest extends \PHPUnit_Framework_TestCase 22 | { 23 | private $userProvider; 24 | private $userChecker; 25 | private $preAuthenticationToken; 26 | 27 | public function testAuthenticate() 28 | { 29 | $providerKey = 'my_cool_firewall'; 30 | 31 | $authenticatorA = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 32 | $authenticatorB = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 33 | $authenticatorC = $this->getMock('KnpU\Guard\GuardAuthenticatorInterface'); 34 | $authenticators = array($authenticatorA, $authenticatorB, $authenticatorC); 35 | 36 | // called 2 times - for authenticator A and B (stops on B because of match) 37 | $this->preAuthenticationToken->expects($this->exactly(2)) 38 | ->method('getGuardProviderKey') 39 | // it will return the "1" index, which will match authenticatorB 40 | ->will($this->returnValue('my_cool_firewall_1')); 41 | 42 | $enteredCredentials = array( 43 | 'username' => '_weaverryan_test_user', 44 | 'password' => 'guard_auth_ftw', 45 | ); 46 | $this->preAuthenticationToken->expects($this->atLeastOnce()) 47 | ->method('getCredentials') 48 | ->will($this->returnValue($enteredCredentials)); 49 | 50 | // authenticators A and C are never called 51 | $authenticatorA->expects($this->never()) 52 | ->method('getUser'); 53 | $authenticatorC->expects($this->never()) 54 | ->method('getUser'); 55 | 56 | $mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); 57 | $authenticatorB->expects($this->once()) 58 | ->method('getUser') 59 | ->with($enteredCredentials, $this->userProvider) 60 | ->will($this->returnValue($mockedUser)); 61 | // checkCredentials is called 62 | $authenticatorB->expects($this->once()) 63 | ->method('checkCredentials') 64 | ->with($enteredCredentials, $mockedUser); 65 | $authedToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); 66 | $authenticatorB->expects($this->once()) 67 | ->method('createAuthenticatedToken') 68 | ->with($mockedUser, $providerKey) 69 | ->will($this->returnValue($authedToken)); 70 | 71 | // user checker should be called 72 | $this->userChecker->expects($this->once()) 73 | ->method('checkPreAuth') 74 | ->with($mockedUser); 75 | $this->userChecker->expects($this->once()) 76 | ->method('checkPostAuth') 77 | ->with($mockedUser); 78 | 79 | $provider = new GuardAuthenticationProvider($authenticators, $this->userProvider, $providerKey, $this->userChecker); 80 | $actualAuthedToken = $provider->authenticate($this->preAuthenticationToken); 81 | $this->assertSame($authedToken, $actualAuthedToken); 82 | } 83 | 84 | public function testGuardWithNoLongerAuthenticatedTriggersLogout() 85 | { 86 | $providerKey = 'my_firewall_abc'; 87 | 88 | // create a token and mark it as NOT authenticated anymore 89 | // this mimics what would happen if a user "changed" between request 90 | $mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); 91 | $token = new PostAuthenticationGuardToken($mockedUser, $providerKey, array('ROLE_USER')); 92 | $token->setAuthenticated(false); 93 | 94 | $provider = new GuardAuthenticationProvider(array(), $this->userProvider, $providerKey, $this->userChecker); 95 | $actualToken = $provider->authenticate($token); 96 | // this should return the anonymous user 97 | $this->assertEquals(new AnonymousToken($providerKey, 'anon.'), $actualToken); 98 | } 99 | 100 | protected function setUp() 101 | { 102 | $this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); 103 | $this->userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface'); 104 | $this->preAuthenticationToken = $this->getMockBuilder('KnpU\Guard\Token\PreAuthenticationGuardToken') 105 | ->disableOriginalConstructor() 106 | ->getMock(); 107 | } 108 | 109 | protected function tearDown() 110 | { 111 | $this->userProvider = null; 112 | $this->userChecker = null; 113 | $this->preAuthenticationToken = null; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knpuniversity/guard", 3 | "description": "Provides Guard-style authentication in Symfony's security component", 4 | "keywords": ["security"], 5 | "homepage": "http://knpuniversity.com", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Ryan Weaver", 10 | "email": "weaverryan@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.9", 15 | "symfony/security-core": "~2.6", 16 | "symfony/security-http": "~2.6" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "~4.7" 20 | }, 21 | "autoload": { 22 | "psr-4": { "KnpU\\Guard\\": "src/" } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./Tests/ 7 | 8 | 9 | 10 | 11 | 12 | ./ 13 | 14 | ./Resources 15 | ./Tests 16 | ./vendor 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/AbstractGuardAuthenticator.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface 14 | { 15 | /** 16 | * Shortcut to create a PostAuthenticationGuardToken for you, if you don't really 17 | * care about which authenticated token you're using. 18 | * 19 | * @param UserInterface $user 20 | * @param string $providerKey 21 | * 22 | * @return PostAuthenticationGuardToken 23 | */ 24 | public function createAuthenticatedToken(UserInterface $user, $providerKey) 25 | { 26 | return new PostAuthenticationGuardToken( 27 | $user, 28 | $providerKey, 29 | $user->getRoles() 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Authenticator/AbstractFormLoginAuthenticator.php: -------------------------------------------------------------------------------- 1 | getSession()->set(Security::AUTHENTICATION_ERROR, $exception); 48 | $url = $this->getLoginUrl(); 49 | 50 | return new RedirectResponse($url); 51 | } 52 | 53 | /** 54 | * Override to change what happens after successful authentication 55 | * 56 | * @param Request $request 57 | * @param TokenInterface $token 58 | * @param string $providerKey 59 | * @return RedirectResponse 60 | */ 61 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) 62 | { 63 | // if the user hit a secure page and start() was called, this was 64 | // the URL they were on, and probably where you want to redirect to 65 | $targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path'); 66 | 67 | if (!$targetPath) { 68 | $targetPath = $this->getDefaultSuccessRedirectUrl(); 69 | } 70 | 71 | return new RedirectResponse($targetPath); 72 | } 73 | 74 | public function supportsRememberMe() 75 | { 76 | return true; 77 | } 78 | 79 | /** 80 | * Override to control what happens when the user hits a secure page 81 | * but isn't logged in yet. 82 | * 83 | * @param Request $request 84 | * @param AuthenticationException|null $authException 85 | * @return RedirectResponse 86 | */ 87 | public function start(Request $request, AuthenticationException $authException = null) 88 | { 89 | $url = $this->getLoginUrl(); 90 | 91 | return new RedirectResponse($url); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Exception/CustomAuthenticationException.php: -------------------------------------------------------------------------------- 1 | setSafeMessage($safeMessage, $messageData); 35 | 36 | return $exception; 37 | } 38 | 39 | /** 40 | * Set a message that will be shown to the user 41 | * 42 | * @param string $messageKey The message or message key 43 | * @param array $messageData Data to be passed into the translator 44 | */ 45 | public function setSafeMessage($messageKey, array $messageData) 46 | { 47 | $this->messageKey = $messageKey; 48 | $this->messageData = $messageData; 49 | } 50 | 51 | public function getMessageKey() 52 | { 53 | return $this->messageKey !== null ? $this->messageKey : parent::getMessageKey(); 54 | } 55 | 56 | public function getMessageData() 57 | { 58 | return $this->messageData !== null ? $this->messageData : parent::getMessageData(); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function serialize() 65 | { 66 | return serialize(array( 67 | $this->messageKey, 68 | $this->messageData, 69 | parent::serialize(), 70 | )); 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function unserialize($str) 77 | { 78 | list($this->messageKey, $this->messageData, $parentData) = unserialize($str); 79 | 80 | parent::unserialize($parentData); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Firewall/GuardAuthenticationListener.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class GuardAuthenticationListener implements ListenerInterface 24 | { 25 | private $guardHandler; 26 | private $authenticationManager; 27 | private $providerKey; 28 | private $guardAuthenticators; 29 | private $logger; 30 | private $rememberMeServices; 31 | 32 | /** 33 | * @param GuardAuthenticatorHandler $guardHandler The Guard handler 34 | * @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance 35 | * @param string $providerKey The provider (i.e. firewall) key 36 | * @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider 37 | * @param LoggerInterface $logger A LoggerInterface instance 38 | */ 39 | public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, array $guardAuthenticators, LoggerInterface $logger = null) 40 | { 41 | if (empty($providerKey)) { 42 | throw new \InvalidArgumentException('$providerKey must not be empty.'); 43 | } 44 | 45 | $this->guardHandler = $guardHandler; 46 | $this->authenticationManager = $authenticationManager; 47 | $this->providerKey = $providerKey; 48 | $this->guardAuthenticators = $guardAuthenticators; 49 | $this->logger = $logger; 50 | } 51 | 52 | /** 53 | * Iterates over each authenticator to see if each wants to authenticate the request. 54 | * 55 | * @param GetResponseEvent $event 56 | */ 57 | public function handle(GetResponseEvent $event) 58 | { 59 | if (null !== $this->logger) { 60 | $this->logger->info('Checking for guard authentication credentials.', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators))); 61 | } 62 | 63 | foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { 64 | // get a key that's unique to *this* guard authenticator 65 | // this MUST be the same as GuardAuthenticationProvider 66 | $uniqueGuardKey = $this->providerKey.'_'.$key; 67 | 68 | $this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event); 69 | } 70 | } 71 | 72 | private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorInterface $guardAuthenticator, GetResponseEvent $event) 73 | { 74 | $request = $event->getRequest(); 75 | try { 76 | if (null !== $this->logger) { 77 | $this->logger->info('Calling getCredentials on guard configurator.', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); 78 | } 79 | 80 | // allow the authenticator to fetch authentication info from the request 81 | $credentials = $guardAuthenticator->getCredentials($request); 82 | 83 | // allow null to be returned to skip authentication 84 | if (null === $credentials) { 85 | return; 86 | } 87 | 88 | // create a token with the unique key, so that the provider knows which authenticator to use 89 | $token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey); 90 | 91 | if (null !== $this->logger) { 92 | $this->logger->info('Passing guard token information to the GuardAuthenticationProvider', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); 93 | } 94 | // pass the token into the AuthenticationManager system 95 | // this indirectly calls GuardAuthenticationProvider::authenticate() 96 | $token = $this->authenticationManager->authenticate($token); 97 | 98 | if (null !== $this->logger) { 99 | $this->logger->info('Guard authentication successful!', array('token' => $token, 'authenticator' => get_class($guardAuthenticator))); 100 | } 101 | 102 | // sets the token on the token storage, etc 103 | $this->guardHandler->authenticateWithToken($token, $request); 104 | } catch (AuthenticationException $e) { 105 | // oh no! Authentication failed! 106 | 107 | if (null !== $this->logger) { 108 | $this->logger->info('Guard authentication failed.', array('exception' => $e, 'authenticator' => get_class($guardAuthenticator))); 109 | } 110 | 111 | $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator); 112 | 113 | if ($response instanceof Response) { 114 | $event->setResponse($response); 115 | } 116 | 117 | return; 118 | } 119 | 120 | // success! 121 | $response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey); 122 | if ($response instanceof Response) { 123 | if (null !== $this->logger) { 124 | $this->logger->info('Guard authenticator set success response.', array('response' => $response, 'authenticator' => get_class($guardAuthenticator))); 125 | } 126 | 127 | $event->setResponse($response); 128 | } else { 129 | if (null !== $this->logger) { 130 | $this->logger->info('Guard authenticator set no success response: request continues.', array('authenticator' => get_class($guardAuthenticator))); 131 | } 132 | } 133 | 134 | // attempt to trigger the remember me functionality 135 | $this->triggerRememberMe($guardAuthenticator, $request, $token, $response); 136 | } 137 | 138 | /** 139 | * Should be called if this listener will support remember me. 140 | * 141 | * @param RememberMeServicesInterface $rememberMeServices 142 | */ 143 | public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices) 144 | { 145 | $this->rememberMeServices = $rememberMeServices; 146 | } 147 | 148 | /** 149 | * Checks to see if remember me is supported in the authenticator and 150 | * on the firewall. If it is, the RememberMeServicesInterface is notified. 151 | * 152 | * @param GuardAuthenticatorInterface $guardAuthenticator 153 | * @param Request $request 154 | * @param TokenInterface $token 155 | * @param Response $response 156 | */ 157 | private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) 158 | { 159 | if (!$guardAuthenticator->supportsRememberMe()) { 160 | return; 161 | } 162 | 163 | if (null === $this->rememberMeServices) { 164 | if (null !== $this->logger) { 165 | $this->logger->info('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator))); 166 | } 167 | 168 | return; 169 | } 170 | 171 | if (!$response instanceof Response) { 172 | throw new \LogicException(sprintf( 173 | '%s::onAuthenticationSuccess *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.', 174 | get_class($guardAuthenticator) 175 | )); 176 | } 177 | 178 | $this->rememberMeServices->loginSuccess($request, $response, $token); 179 | } 180 | } -------------------------------------------------------------------------------- /src/GuardAuthenticatorHandler.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class GuardAuthenticatorHandler 24 | { 25 | private $tokenStorage; 26 | 27 | private $dispatcher; 28 | 29 | public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null) 30 | { 31 | $this->tokenStorage = $tokenStorage; 32 | $this->dispatcher = $eventDispatcher; 33 | } 34 | 35 | /** 36 | * Authenticates the given token in the system. 37 | * 38 | * @param TokenInterface $token 39 | * @param Request $request 40 | */ 41 | public function authenticateWithToken(TokenInterface $token, Request $request) 42 | { 43 | $this->tokenStorage->setToken($token); 44 | 45 | if (null !== $this->dispatcher) { 46 | $loginEvent = new InteractiveLoginEvent($request, $token); 47 | $this->dispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent); 48 | } 49 | } 50 | 51 | /** 52 | * Returns the "on success" response for the given GuardAuthenticator. 53 | * 54 | * @param TokenInterface $token 55 | * @param Request $request 56 | * @param GuardAuthenticatorInterface $guardAuthenticator 57 | * @param string $providerKey The provider (i.e. firewall) key 58 | * 59 | * @return null|Response 60 | */ 61 | public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey) 62 | { 63 | $response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey); 64 | 65 | // check that it's a Response or null 66 | if ($response instanceof Response || null === $response) { 67 | return $response; 68 | } 69 | 70 | throw new \UnexpectedValueException(sprintf( 71 | 'The %s::onAuthenticationSuccess method must return null or a Response object. You returned %s.', 72 | get_class($guardAuthenticator), 73 | is_object($response) ? get_class($response) : gettype($response) 74 | )); 75 | } 76 | 77 | /** 78 | * Convenience method for authenticating the user and returning the 79 | * Response *if any* for success. 80 | * 81 | * @param UserInterface $user 82 | * @param Request $request 83 | * @param GuardAuthenticatorInterface $authenticator 84 | * @param string $providerKey The provider (i.e. firewall) key 85 | * 86 | * @return Response|null 87 | */ 88 | public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, GuardAuthenticatorInterface $authenticator, $providerKey) 89 | { 90 | // create an authenticated token for the User 91 | $token = $authenticator->createAuthenticatedToken($user, $providerKey); 92 | // authenticate this in the system 93 | $this->authenticateWithToken($token, $request); 94 | 95 | // return the success metric 96 | return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey); 97 | } 98 | 99 | /** 100 | * Handles an authentication failure and returns the Response for the 101 | * GuardAuthenticator. 102 | * 103 | * @param AuthenticationException $authenticationException 104 | * @param Request $request 105 | * @param GuardAuthenticatorInterface $guardAuthenticator 106 | * 107 | * @return null|Response 108 | */ 109 | public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator) 110 | { 111 | $this->tokenStorage->setToken(null); 112 | 113 | $response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException); 114 | if ($response instanceof Response || null === $response) { 115 | // returning null is ok, it means they want the request to continue 116 | return $response; 117 | } 118 | 119 | throw new \UnexpectedValueException(sprintf( 120 | 'The %s::onAuthenticationFailure method must return null or a Response object. You returned %s.', 121 | get_class($guardAuthenticator), 122 | is_object($response) ? get_class($response) : gettype($response) 123 | )); 124 | } 125 | } -------------------------------------------------------------------------------- /src/GuardAuthenticatorInterface.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface 24 | { 25 | /** 26 | * Get the authentication credentials from the request and return them 27 | * as any type (e.g. an associate array). If you return null, authentication 28 | * will be skipped. 29 | * 30 | * Whatever value you return here will be passed to getUser() and checkCredentials() 31 | * 32 | * For example, for a form login, you might: 33 | * 34 | * return array( 35 | * 'username' => $request->request->get('_username'), 36 | * 'password' => $request->request->get('_password'), 37 | * ); 38 | * 39 | * Or for an API token that's on a header, you might use: 40 | * 41 | * return array('api_key' => $request->headers->get('X-API-TOKEN')); 42 | * 43 | * @param Request $request 44 | * 45 | * @return mixed|null 46 | */ 47 | public function getCredentials(Request $request); 48 | 49 | /** 50 | * Return a UserInterface object based on the credentials. 51 | * 52 | * The *credentials* are the return value from getCredentials() 53 | * 54 | * You may throw an AuthenticationException if you wish. If you return 55 | * null, then a UsernameNotFoundException is thrown for you. 56 | * 57 | * @param mixed $credentials 58 | * @param UserProviderInterface $userProvider 59 | * 60 | * @throws AuthenticationException 61 | * 62 | * @return UserInterface|null 63 | */ 64 | public function getUser($credentials, UserProviderInterface $userProvider); 65 | 66 | /** 67 | * Throw an AuthenticationException if the credentials are invalid. 68 | * 69 | * The *credentials* are the return value from getCredentials() 70 | * 71 | * @param mixed $credentials 72 | * @param UserInterface $user 73 | * 74 | * @throws AuthenticationException 75 | */ 76 | public function checkCredentials($credentials, UserInterface $user); 77 | 78 | /** 79 | * Create an authenticated token for the given user. 80 | * 81 | * If you don't care about which token class is used or don't really 82 | * understand what a "token" is, you can skip this method by extending 83 | * the AbstractGuardAuthenticator class from your authenticator. 84 | * 85 | * @see AbstractGuardAuthenticator 86 | * 87 | * @param UserInterface $user 88 | * @param string $providerKey The provider (i.e. firewall) key 89 | * 90 | * @return GuardTokenInterface 91 | */ 92 | public function createAuthenticatedToken(UserInterface $user, $providerKey); 93 | 94 | /** 95 | * Called when authentication executed, but failed (e.g. wrong username password). 96 | * 97 | * This should return the Response sent back to the user, like a 98 | * RedirectResponse to the login page or a 403 response. 99 | * 100 | * If you return null, the request will continue, but the user will 101 | * not be authenticated. This is probably not what you want to do. 102 | * 103 | * @param Request $request 104 | * @param AuthenticationException $exception 105 | * 106 | * @return Response|null 107 | */ 108 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception); 109 | 110 | /** 111 | * Called when authentication executed and was successful! 112 | * 113 | * This should return the Response sent back to the user, like a 114 | * RedirectResponse to the last page they visited. 115 | * 116 | * If you return null, the current request will continue, and the user 117 | * will be authenticated. This makes sense, for example, with an API. 118 | * 119 | * @param Request $request 120 | * @param TokenInterface $token 121 | * @param string $providerKey The provider (i.e. firewall) key 122 | * 123 | * @return Response|null 124 | */ 125 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey); 126 | 127 | /** 128 | * Does this method support remember me cookies? 129 | * 130 | * Remember me cookie will be set if *all* of the following are met: 131 | * A) This method returns true 132 | * B) The remember_me key under your firewall is configured 133 | * C) The "remember me" functionality is activated. This is usually 134 | * done by having a _remember_me checkbox in your form, but 135 | * can be configured by the "always_remember_me" and "remember_me_parameter" 136 | * parameters under the "remember_me" firewall key 137 | * 138 | * @return bool 139 | */ 140 | public function supportsRememberMe(); 141 | } 142 | -------------------------------------------------------------------------------- /src/Provider/GuardAuthenticationProvider.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class GuardAuthenticationProvider implements AuthenticationProviderInterface 23 | { 24 | /** 25 | * @var GuardAuthenticatorInterface[] 26 | */ 27 | private $guardAuthenticators; 28 | private $userProvider; 29 | private $providerKey; 30 | private $userChecker; 31 | 32 | /** 33 | * @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener 34 | * @param UserProviderInterface $userProvider The user provider 35 | * @param string $providerKey The provider (i.e. firewall) key 36 | * @param UserCheckerInterface $userChecker 37 | */ 38 | public function __construct(array $guardAuthenticators, UserProviderInterface $userProvider, $providerKey, UserCheckerInterface $userChecker) 39 | { 40 | $this->guardAuthenticators = $guardAuthenticators; 41 | $this->userProvider = $userProvider; 42 | $this->providerKey = $providerKey; 43 | $this->userChecker = $userChecker; 44 | } 45 | 46 | /** 47 | * Finds the correct authenticator for the token and calls it. 48 | * 49 | * @param GuardTokenInterface $token 50 | * 51 | * @return TokenInterface 52 | */ 53 | public function authenticate(TokenInterface $token) 54 | { 55 | if (!$this->supports($token)) { 56 | throw new \InvalidArgumentException('GuardAuthenticationProvider only supports GuardTokenInterface.'); 57 | } 58 | 59 | if (!$token instanceof PreAuthenticationGuardToken) { 60 | /* 61 | * The listener *only* passes PreAuthenticationGuardToken instances. 62 | * This means that an authenticated token (e.g. PostAuthenticationGuardToken) 63 | * is being passed here, which happens if that token becomes 64 | * "not authenticated" (e.g. happens if the user changes between 65 | * requests). In this case, the user should be logged out, so 66 | * we will return an AnonymousToken to accomplish that. 67 | */ 68 | 69 | // this should never happen - but technically, the token is 70 | // authenticated... so it could just be returned 71 | if ($token->isAuthenticated()) { 72 | return $token; 73 | } 74 | 75 | // cause the logout - the token is not authenticated 76 | return new AnonymousToken($this->providerKey, 'anon.'); 77 | } 78 | 79 | // find the *one* GuardAuthenticator that this token originated from 80 | foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { 81 | // get a key that's unique to *this* guard authenticator 82 | // this MUST be the same as GuardAuthenticationListener 83 | $uniqueGuardKey = $this->providerKey.'_'.$key; 84 | 85 | if ($uniqueGuardKey == $token->getGuardProviderKey()) { 86 | return $this->authenticateViaGuard($guardAuthenticator, $token); 87 | } 88 | } 89 | 90 | // no matching authenticator found - but there will be multiple GuardAuthenticationProvider 91 | // instances that will be checked if you have multiple firewalls. 92 | } 93 | 94 | private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token) 95 | { 96 | // get the user from the GuardAuthenticator 97 | $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider); 98 | 99 | if (null === $user) { 100 | throw new UsernameNotFoundException(sprintf( 101 | 'Null returned from %s::getUser()', 102 | get_class($guardAuthenticator) 103 | )); 104 | } 105 | 106 | if (!$user instanceof UserInterface) { 107 | throw new \UnexpectedValueException(sprintf( 108 | 'The %s::getUser method must return a UserInterface. You returned %s.', 109 | get_class($guardAuthenticator), 110 | is_object($user) ? get_class($user) : gettype($user) 111 | )); 112 | } 113 | 114 | // check the preAuth UserChecker 115 | $this->userChecker->checkPreAuth($user); 116 | // check the credentials 117 | $guardAuthenticator->checkCredentials($token->getCredentials(), $user); 118 | // check the postAuth UserChecker 119 | $this->userChecker->checkPostAuth($user); 120 | 121 | // turn the UserInterface into a TokenInterface 122 | $authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $this->providerKey); 123 | if (!$authenticatedToken instanceof TokenInterface) { 124 | throw new \UnexpectedValueException(sprintf( 125 | 'The %s::createAuthenticatedToken method must return a TokenInterface. You returned %s.', 126 | get_class($guardAuthenticator), 127 | is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken) 128 | )); 129 | } 130 | 131 | return $authenticatedToken; 132 | } 133 | 134 | public function supports(TokenInterface $token) 135 | { 136 | return $token instanceof GuardTokenInterface; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Token/GuardTokenInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface GuardTokenInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Token/PostAuthenticationGuardToken.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface 18 | { 19 | private $providerKey; 20 | 21 | /** 22 | * @param UserInterface $user The user! 23 | * @param string $providerKey The provider (firewall) key 24 | * @param RoleInterface[]|string[] $roles An array of roles 25 | * 26 | * @throws \InvalidArgumentException 27 | */ 28 | public function __construct(UserInterface $user, $providerKey, array $roles) 29 | { 30 | parent::__construct($roles); 31 | 32 | if (empty($providerKey)) { 33 | throw new \InvalidArgumentException('$providerKey (i.e. firewall key) must not be empty.'); 34 | } 35 | 36 | $this->setUser($user); 37 | $this->providerKey = $providerKey; 38 | 39 | // this token is meant to be used after authentication success, so it is always authenticated 40 | // you could set it as non authenticated later if you need to 41 | parent::setAuthenticated(true); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function setAuthenticated($isAuthenticated) 48 | { 49 | if ($isAuthenticated) { 50 | throw new \LogicException('Cannot set this token to trusted after instantiation.'); 51 | } 52 | 53 | parent::setAuthenticated(false); 54 | } 55 | 56 | /** 57 | * This is meant to be only an authenticated token, where credentials 58 | * have already been used and are thus cleared. 59 | * 60 | * {@inheritdoc} 61 | */ 62 | public function getCredentials() 63 | { 64 | return array(); 65 | } 66 | 67 | /** 68 | * Returns the provider (firewall) key. 69 | * 70 | * @return string 71 | */ 72 | public function getProviderKey() 73 | { 74 | return $this->providerKey; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function serialize() 81 | { 82 | return serialize(array($this->providerKey, parent::serialize())); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function unserialize($serialized) 89 | { 90 | list($this->providerKey, $parentStr) = unserialize($serialized); 91 | parent::unserialize($parentStr); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Token/PreAuthenticationGuardToken.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class PreAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface 17 | { 18 | private $credentials; 19 | private $guardProviderKey; 20 | 21 | /** 22 | * @param mixed $credentials 23 | * @param string $guardProviderKey Unique key that bind this token to a specific GuardAuthenticatorInterface 24 | */ 25 | public function __construct($credentials, $guardProviderKey) 26 | { 27 | $this->credentials = $credentials; 28 | $this->guardProviderKey = $guardProviderKey; 29 | 30 | parent::__construct(array()); 31 | 32 | // never authenticated 33 | parent::setAuthenticated(false); 34 | } 35 | 36 | public function getGuardProviderKey() 37 | { 38 | return $this->guardProviderKey; 39 | } 40 | 41 | /** 42 | * Returns the user credentials, which might be an array of anything you 43 | * wanted to put in there (e.g. username, password, favoriteColor). 44 | * 45 | * @return mixed The user credentials 46 | */ 47 | public function getCredentials() 48 | { 49 | return $this->credentials; 50 | } 51 | 52 | public function setAuthenticated($authenticated) 53 | { 54 | throw new \LogicException('The PreAuthenticationGuardToken is *always* not authenticated.'); 55 | } 56 | } 57 | --------------------------------------------------------------------------------