diff --git a/.travis.yml b/.travis.yml index 878fb37..acc76c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ php: - 5.3 - 5.4 - 5.5 + - hhvm + +matrix: + allow_failures: + - php: hhvm before_script: - composer install --dev --prefer-source diff --git a/src/BaconAuthentication/AuthenticationEvent.php b/src/BaconAuthentication/AuthenticationEvent.php index ace0d39..4820554 100644 --- a/src/BaconAuthentication/AuthenticationEvent.php +++ b/src/BaconAuthentication/AuthenticationEvent.php @@ -29,10 +29,6 @@ class AuthenticationEvent extends Event */ protected $request; - /** - * @var ResponseInterface - */ - protected $response; /** * @var ResultInterface|null @@ -57,24 +53,6 @@ public function setRequest(RequestInterface $request) return $this; } - /** - * @return ResponseInterface - */ - public function getResponse() - { - return $this->response; - } - - /** - * @param ResponseInterface $response - * @return AuthenticationEvent - */ - public function setResponse(ResponseInterface $response) - { - $this->response = $response; - return $this; - } - /** * @return ResultInterface|null */ diff --git a/src/BaconAuthentication/AuthenticationServiceInterface.php b/src/BaconAuthentication/AuthenticationServiceInterface.php index c3bad2c..f5dcb87 100644 --- a/src/BaconAuthentication/AuthenticationServiceInterface.php +++ b/src/BaconAuthentication/AuthenticationServiceInterface.php @@ -21,10 +21,9 @@ interface AuthenticationServiceInterface * Authenticates a request. * * @param RequestInterface $request - * @param ResponseInterface $response - * @return ResultInterface + * @return Result\ResultInterface */ - public function authenticate(RequestInterface $request, ResponseInterface $response); + public function authenticate(RequestInterface $request); /** * Resets all credentials to anonymous state. diff --git a/src/BaconAuthentication/PluggableAuthenticationService.php b/src/BaconAuthentication/PluggableAuthenticationService.php index 02ce82d..1f49212 100644 --- a/src/BaconAuthentication/PluggableAuthenticationService.php +++ b/src/BaconAuthentication/PluggableAuthenticationService.php @@ -22,6 +22,7 @@ use Zend\EventManager\EventManagerAwareInterface; use Zend\EventManager\EventManager; use Zend\EventManager\EventManagerInterface; +use Zend\EventManager\ListenerAggregateInterface; use Zend\Stdlib\PriorityQueue; use Zend\Stdlib\RequestInterface; use Zend\Stdlib\ResponseInterface; @@ -112,8 +113,8 @@ public function addPlugin($plugin, $priority = 1) $isValid = true; } - if ($plugin instanceof EventAwarePluginInterface) { - $plugin->attachToEvents($this->getEventManager()); + if ($plugin instanceof ListenerAggregateInterface) { + $plugin->attach($this->getEventManager()); $isValid = true; } @@ -132,17 +133,15 @@ public function addPlugin($plugin, $priority = 1) * * @see AuthenticationServiceInterface::authenticate() * @param RequestInterface $request - * @param ResponseInterface $response * @return ResultInterface * @throws Exception\RuntimeException */ - public function authenticate(RequestInterface $request, ResponseInterface $response) + public function authenticate(RequestInterface $request) { $events = $this->getEventManager(); $event = new AuthenticationEvent(); $event->setTarget($this) - ->setRequest($request) - ->setResponse($response); + ->setRequest($request); $shortCircuit = function ($result) { return ($result instanceof ResultInterface); @@ -153,12 +152,15 @@ public function authenticate(RequestInterface $request, ResponseInterface $respo if ($eventResult->stopped()) { $result = $eventResult->last(); } else { - $result = $this->runAuthenticationPlugins($request, $response); + $result = $this->runAuthenticationPlugins($request); } if ($result === null) { - if ($this->challenge($request, $response)) { - $result = new Result(Result::STATE_CHALLENGE); + + $result = $this->challenge($request); + + if ($result) { + $result = new Result(Result::STATE_CHALLENGE, $result); $event->setResult($result); } } else { @@ -220,14 +222,13 @@ public function resetCredentials(RequestInterface $request) * Runs all authentication plugins to get a result. * * @param RequestInterface $request - * @param ResponseInterface $response * @return ResultInterface|null */ - protected function runAuthenticationPlugins(RequestInterface $request, ResponseInterface $response) + protected function runAuthenticationPlugins(RequestInterface $request) { foreach ($this->extractionPlugins as $extractionPlugin) { /* @var $extractionPlugin ExtractionPluginInterface */ - $credentials = $extractionPlugin->extractCredentials($request, $response); + $credentials = $extractionPlugin->extractCredentials($request); if ($credentials === null) { continue; @@ -254,19 +255,20 @@ protected function runAuthenticationPlugins(RequestInterface $request, ResponseI * Tries to initiate a challenge. * * @param RequestInterface $request - * @param ResponseInterface $response * @return bool */ - protected function challenge(RequestInterface $request, ResponseInterface $response) + protected function challenge(RequestInterface $request) { + /* @var $challengePlugin ChallengePluginInterface */ foreach ($this->challengePlugins as $challengePlugin) { - /* @var $challengePlugin ChallengePluginInterface */ - if ($challengePlugin->challenge($request, $response)) { - return true; + + $response = $challengePlugin->challenge($request); + if ($response) { + return $response; } } - return false; + return null; } /** diff --git a/src/BaconAuthentication/Plugin/ChallengePluginInterface.php b/src/BaconAuthentication/Plugin/ChallengePluginInterface.php index 0385aae..e97116a 100644 --- a/src/BaconAuthentication/Plugin/ChallengePluginInterface.php +++ b/src/BaconAuthentication/Plugin/ChallengePluginInterface.php @@ -23,9 +23,9 @@ interface ChallengePluginInterface * If the response can be modified to trigger a challenge, true should be * returned, false otherwise. * - * @param RequestInterface $request - * @param ResponseInterface $response - * @return bool + * @param RequestInterface $request + * + * @return null|\Zend\Stdlib\ResponseInterface */ - public function challenge(RequestInterface $request, ResponseInterface $response); + public function challenge(RequestInterface $request); } diff --git a/src/BaconAuthentication/Plugin/EventAwarePluginInterface.php b/src/BaconAuthentication/Plugin/EventAwarePluginInterface.php deleted file mode 100644 index a306ef0..0000000 --- a/src/BaconAuthentication/Plugin/EventAwarePluginInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -getHeaders()->addHeaderLine( - 'WWW-Authenticate', - 'Basic realm="' . addslashes($this->realm) . '"' - ); + $response = new HttpResponse(); $response->setStatusCode(401); + $response->getHeaders()->addHeaderLine('WWW-Authenticate', 'Basic realm="' . addslashes($this->realm) . '"'); - return true; + return $response; } } diff --git a/src/BaconAuthentication/Plugin/HttpPost.php b/src/BaconAuthentication/Plugin/HttpPost.php index 1741734..d641e3e 100644 --- a/src/BaconAuthentication/Plugin/HttpPost.php +++ b/src/BaconAuthentication/Plugin/HttpPost.php @@ -94,10 +94,9 @@ public function setInputFilter(InputFilterInterface $inputFilter) * * @see ExtractionPluginInterface::extractCredentials() * @param RequestInterface $request - * @param ResponseInterface $response * @return null|Parameters */ - public function extractCredentials(RequestInterface $request, ResponseInterface $response) + public function extractCredentials(RequestInterface $request) { if (!$request instanceof HttpRequest) { return null; @@ -139,21 +138,18 @@ public function extractCredentials(RequestInterface $request, ResponseInterface * * @see ChallengePluginInterface::challenge() * @param RequestInterface $request - * @param ResponseInterface $response - * @return bool + * @return null|HttpResponse */ - public function challenge(RequestInterface $request, ResponseInterface $response) + public function challenge(RequestInterface $request) { - if (!$response instanceof HttpResponse) { - return false; + if (!$request instanceof HttpRequest) { + return null; } - $response->getHeaders()->addHeaderLine( - 'Location', - $this->loginFormUrl - ); + $response = new HttpResponse(); $response->setStatusCode(302); + $response->getHeaders()->addHeaderLine('Location', $this->loginFormUrl); - return true; + return $response; } } diff --git a/src/BaconAuthentication/Plugin/Session.php b/src/BaconAuthentication/Plugin/Session.php index 1aec3ce..1fc7359 100644 --- a/src/BaconAuthentication/Plugin/Session.php +++ b/src/BaconAuthentication/Plugin/Session.php @@ -12,6 +12,7 @@ use BaconAuthentication\AuthenticationEvent; use BaconAuthentication\Result\Result; use Zend\EventManager\EventManagerInterface; +use Zend\EventManager\ListenerAggregateInterface; use Zend\Session\ManagerInterface; use Zend\Session\Container as SessionContainer; use Zend\Stdlib\RequestInterface; @@ -19,9 +20,7 @@ /** * Plugin responsible for tracking the identity between multiple HTTP requests. */ -class Session implements - EventAwarePluginInterface, - ResetPluginInterface +class Session implements ListenerAggregateInterface, ResetPluginInterface { /** * Default session namespace. @@ -33,6 +32,11 @@ class Session implements */ protected $session; + /** + * @var array + */ + protected $listeners = array(); + /** * @param string|null $namespace * @param ManagerInterface $manager @@ -53,10 +57,25 @@ public function __construct($namespace = null, ManagerInterface $manager = null) * @param EventManagerInterface $events * @return void */ - public function attachToEvents(EventManagerInterface $events) + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach( + AuthenticationEvent::EVENT_AUTHENTICATE_PRE, + array($this, 'checkSession') + ); + + $this->listeners[] = $events->attach( + AuthenticationEvent::EVENT_AUTHENTICATE_POST, + array($this, 'storeIdentifier') + ); + } + + public function detach(EventManagerInterface $events) { - $events->attach(AuthenticationEvent::EVENT_AUTHENTICATE_PRE, array($this, 'checkSession')); - $events->attach(AuthenticationEvent::EVENT_AUTHENTICATE_POST, array($this, 'storeIdentifier')); + foreach ($this->listeners as $index => $callback) { + $events->detach($callback); + unset($this->listeners[$index]); + } } /** diff --git a/tests/BaconAuthenticationTest/AuthenticationEventTest.php b/tests/BaconAuthenticationTest/AuthenticationEventTest.php index 3cbc462..dfecfec 100644 --- a/tests/BaconAuthenticationTest/AuthenticationEventTest.php +++ b/tests/BaconAuthenticationTest/AuthenticationEventTest.php @@ -24,10 +24,6 @@ public static function setterGetterProvider() 'Request', 'Zend\Stdlib\RequestInterface', ), - array( - 'Response', - 'Zend\Stdlib\ResponseInterface', - ), array( 'Result', 'BaconAuthentication\Result\ResultInterface', diff --git a/tests/BaconAuthenticationTest/PluggableAuthenticationServiceTest.php b/tests/BaconAuthenticationTest/PluggableAuthenticationServiceTest.php index 7943760..8c611cc 100644 --- a/tests/BaconAuthenticationTest/PluggableAuthenticationServiceTest.php +++ b/tests/BaconAuthenticationTest/PluggableAuthenticationServiceTest.php @@ -32,9 +32,9 @@ public function testAddEventAwarePlugin() { $service = new PluggableAuthenticationService(); - $plugin = $this->getMock('BaconAuthentication\Plugin\EventAwarePluginInterface'); + $plugin = $this->getMock('Zend\EventManager\ListenerAggregateInterface'); $plugin->expects($this->once()) - ->method('attachToEvents') + ->method('attach') ->with($this->equalTo($service->getEventManager())); $service->addPlugin($plugin); @@ -49,8 +49,7 @@ public function testAuthenticateWithoutResult() $service = new PluggableAuthenticationService(); $service->authenticate( - $this->getMock('Zend\Stdlib\RequestInterface'), - $this->getMock('Zend\Stdlib\ResponseInterface') + $this->getMock('Zend\Stdlib\RequestInterface') ); } diff --git a/tests/BaconAuthenticationTest/Plugin/HttpBasicAuthTest.php b/tests/BaconAuthenticationTest/Plugin/HttpBasicAuthTest.php index f65fcd2..c53d653 100644 --- a/tests/BaconAuthenticationTest/Plugin/HttpBasicAuthTest.php +++ b/tests/BaconAuthenticationTest/Plugin/HttpBasicAuthTest.php @@ -31,10 +31,7 @@ public function testExtractionWithIncompatibleRequest() public function testExtractionWithoutCredentials() { $plugin = new HttpBasicAuth(); - $credentials = $plugin->extractCredentials( - $this->getMock('Zend\Http\PhpEnvironment\Request'), - $this->getMock('Zend\Stdlib\ResponseInterface') - ); + $credentials = $plugin->extractCredentials($this->getMock('Zend\Http\PhpEnvironment\Request')); $this->assertNull($credentials); } @@ -53,80 +50,52 @@ public function testExtractionWithCredentials() })); $plugin = new HttpBasicAuth(); - $credentials = $plugin->extractCredentials( - $request, - $this->getMock('Zend\Stdlib\ResponseInterface') - ); + $credentials = $plugin->extractCredentials($request); $this->assertInstanceOf('Zend\Stdlib\ParametersInterface', $credentials); $this->assertEquals('foo', $credentials->get('identity')); $this->assertEquals('bar', $credentials->get('password')); } - public function testChallengeWithIncompatibleResponse() + public function testChallengeWithIncompatibleRequest() { $plugin = new HttpBasicAuth(); $challenge = $plugin->challenge( - $this->getMock('Zend\Stdlib\RequestInterface'), - $this->getMock('Zend\Stdlib\ResponseInterface') + $this->getMock('Zend\Stdlib\RequestInterface') ); - $this->assertFalse($challenge); + $this->assertNull($challenge); } - public function testChallengeWithCompatibleResponse() + public function testChallengeWithCompatibleRequest() { - $headers = $this->getMock('Zend\Http\Headers'); - $headers->expects($this->once()) - ->method('addHeaderLine') - ->with( - $this->equalTo('WWW-Authenticate'), - $this->equalTo('Basic realm="BaconAuthentication"') - ); - - $response = $this->getMock('Zend\Http\Response'); - $response->expects($this->once()) - ->method('getHeaders') - ->will($this->returnValue($headers)); - $response->expects($this->once()) - ->method('setStatusCode') - ->with($this->equalTo(401)); + $plugin = new HttpBasicAuth(); + $request = $this->getMock('Zend\Http\PhpEnvironment\Request'); - $plugin = new HttpBasicAuth(); - $challenge = $plugin->challenge( - $this->getMock('Zend\Stdlib\RequestInterface'), - $response - ); + /** @var \Zend\Http\Response $challenge */ + $challenge = $plugin->challenge($request); + + $this->assertInstanceOf('Zend\Http\Response', $challenge); + $this->assertEquals(401, $challenge->getStatusCode()); - $this->assertTrue($challenge); + $header = $challenge->getHeaders()->get('WWW-Authenticate'); + $this->assertEquals($header[0]->getFieldValue(), 'Basic realm="BaconAuthentication"'); } public function testChallengeWithCustomRealm() { - $headers = $this->getMock('Zend\Http\Headers'); - $headers->expects($this->once()) - ->method('addHeaderLine') - ->with( - $this->equalTo('WWW-Authenticate'), - $this->equalTo('Basic realm="foo\\"baz"') - ); - - $response = $this->getMock('Zend\Http\Response'); - $response->expects($this->once()) - ->method('getHeaders') - ->will($this->returnValue($headers)); - $response->expects($this->once()) - ->method('setStatusCode') - ->with($this->equalTo(401)); - $plugin = new HttpBasicAuth(); $plugin->setRealm('foo"baz'); - $challenge = $plugin->challenge( - $this->getMock('Zend\Stdlib\RequestInterface'), - $response - ); + $request = $this->getMock('Zend\Http\PhpEnvironment\Request'); + + /** @var \Zend\Http\Response $challenge */ + $challenge = $plugin->challenge($request); + + $this->assertInstanceOf('Zend\Http\Response', $challenge); + $this->assertEquals(401, $challenge->getStatusCode()); - $this->assertTrue($challenge); + $header = $challenge->getHeaders()->get('WWW-Authenticate'); + $this->assertEquals($header[0]->getFieldValue(), 'Basic realm="foo\"baz"'); } } diff --git a/tests/BaconAuthenticationTest/Plugin/HttpPostTest.php b/tests/BaconAuthenticationTest/Plugin/HttpPostTest.php index e50686f..fc1258f 100644 --- a/tests/BaconAuthenticationTest/Plugin/HttpPostTest.php +++ b/tests/BaconAuthenticationTest/Plugin/HttpPostTest.php @@ -53,10 +53,7 @@ public function testExtractionWithCredentials() })); $plugin = new HttpPost(null); - $credentials = $plugin->extractCredentials( - $request, - $this->getMock('Zend\Stdlib\ResponseInterface') - ); + $credentials = $plugin->extractCredentials($request); $this->assertInstanceOf('Zend\Stdlib\ParametersInterface', $credentials); $this->assertEquals('foo', $credentials->get('identity')); @@ -80,10 +77,7 @@ public function testExtractionWithDifferentFieldNames() $this->assertSame($plugin, $plugin->setIdentityField('username')); $this->assertSame($plugin, $plugin->setPasswordField('secret')); - $credentials = $plugin->extractCredentials( - $request, - $this->getMock('Zend\Stdlib\ResponseInterface') - ); + $credentials = $plugin->extractCredentials($request); $this->assertInstanceOf('Zend\Stdlib\ParametersInterface', $credentials); $this->assertEquals('foo', $credentials->get('identity')); @@ -117,10 +111,7 @@ public function testExtractionWithInputFilter() $plugin = new HttpPost(null); $this->assertSame($plugin, $plugin->setInputFilter($inputFilter)); - $credentials = $plugin->extractCredentials( - $request, - $this->getMock('Zend\Stdlib\ResponseInterface') - ); + $credentials = $plugin->extractCredentials($request); $this->assertInstanceOf('Zend\Stdlib\ParametersInterface', $credentials); $this->assertEquals('baz', $credentials->get('identity')); @@ -148,10 +139,7 @@ public function testExtractionWithInputFilterAndInvalidInput() $plugin = new HttpPost(null); $this->assertSame($plugin, $plugin->setInputFilter($inputFilter)); - $credentials = $plugin->extractCredentials( - $request, - $this->getMock('Zend\Stdlib\ResponseInterface') - ); + $credentials = $plugin->extractCredentials($request); $this->assertInstanceOf('BaconAuthentication\Result\ResultInterface', $credentials); $this->assertTrue($credentials->isFailure()); @@ -168,33 +156,17 @@ public function testChallengeWithIncompatibleResponse() $this->getMock('Zend\Stdlib\ResponseInterface') ); - $this->assertFalse($challenge); + $this->assertNull($challenge); } public function testChallengeWithCompatibleResponse() { - $headers = $this->getMock('Zend\Http\Headers'); - $headers->expects($this->once()) - ->method('addHeaderLine') - ->with( - $this->equalTo('Location'), - $this->equalTo('/login') - ); - - $response = $this->getMock('Zend\Http\Response'); - $response->expects($this->once()) - ->method('getHeaders') - ->will($this->returnValue($headers)); - $response->expects($this->once()) - ->method('setStatusCode') - ->with($this->equalTo(302)); - $plugin = new HttpPost('/login'); - $challenge = $plugin->challenge( - $this->getMock('Zend\Stdlib\RequestInterface'), - $response - ); + /** @var \Zend\Http\Response $challenge */ + $challenge = $plugin->challenge($this->getMock('Zend\Http\PhpEnvironment\Request')); - $this->assertTrue($challenge); + $this->assertInstanceOf('Zend\Http\Response', $challenge); + $this->assertEquals(302, $challenge->getStatusCode()); + $this->assertEquals('/login', $challenge->getHeaders()->get('location')->getFieldValue()); } } diff --git a/tests/BaconAuthenticationTest/Plugin/SessionTest.php b/tests/BaconAuthenticationTest/Plugin/SessionTest.php index 4a3afe3..5e3b1be 100644 --- a/tests/BaconAuthenticationTest/Plugin/SessionTest.php +++ b/tests/BaconAuthenticationTest/Plugin/SessionTest.php @@ -52,7 +52,7 @@ public function testCustomNamespace() $this->assertEquals('foobar', $this->getContainer($session)->getName()); } - public function testAttachToEvents() + public function testAttach() { $session = new Session(); @@ -67,7 +67,33 @@ public function testAttachToEvents() $this->equalTo(array($session, 'storeIdentifier')) )); - $session->attachToEvents($events); + $session->attach($events); + } + + /** + * @covers \BaconAuthentication\Plugin\Session::detach + */ + public function testDetach() + { + $listener = new Session(); + $eventManager = $this->getMock('Zend\\EventManager\\EventManagerInterface'); + $callback = $this->getMockBuilder('Zend\\Stdlib\\CallbackHandler')->disableOriginalConstructor()->getMock(); + + $eventManager + ->expects($this->exactly(2)) + ->method('attach') + ->will($this->returnValue($callback)); + + $listener->attach($eventManager); + + $eventManager + ->expects($this->exactly(2)) + ->method('detach') + ->with($callback); + + // Run it twice to make sure the array listener array is empty the second time + $listener->detach($eventManager); + $listener->detach($eventManager); } public function testIdentifierStorageWithSuccessfulResult()