diff --git a/config/module.config.php b/config/module.config.php index 82ad12c..3f4c085 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -78,6 +78,8 @@ * // see https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Pdo.php#L57-L66 * ] */ + 'api_problem_error_response' => true, // if true, client errors is returned in application/problem+json content type, + // otherwise in format from oauth2 specification (default: true) ), 'zf-content-negotiation' => array( 'ZF\OAuth2\Controller\Auth' => array( diff --git a/src/Controller/AuthController.php b/src/Controller/AuthController.php index cfe2e90..e62051f 100644 --- a/src/Controller/AuthController.php +++ b/src/Controller/AuthController.php @@ -23,6 +23,11 @@ class AuthController extends AbstractActionController */ protected $server; + /** + * @var boolean + */ + protected $apiProblemErrorResponse = true; + /** * Constructor * @@ -33,6 +38,22 @@ public function __construct(OAuth2Server $server) $this->server = $server; } + /** + * @return boolean + */ + public function isApiProblemErrorResponse() + { + return $this->apiProblemErrorResponse; + } + + /** + * @param boolean $apiProblemErrorResponse + */ + public function setApiProblemErrorResponse($apiProblemErrorResponse) + { + $this->apiProblemErrorResponse = $apiProblemErrorResponse; + } + /** * Token Action (/oauth) */ @@ -52,21 +73,11 @@ public function tokenAction() $oauth2request = $this->getOAuth2Request(); $response = $this->server->handleTokenRequest($oauth2request); + if ($response->isClientError()) { - $parameters = $response->getParameters(); - $errorUri = isset($parameters['error_uri']) ? $parameters['error_uri'] : null; - $error = isset($parameters['error']) ? $parameters['error'] : null; - $errorDescription = isset($parameters['error_description']) ? $parameters['error_description'] : null; - - return new ApiProblemResponse( - new ApiProblem( - $response->getStatusCode(), - $errorDescription, - $errorUri, - $error - ) - ); + return $this->getErrorResponse($response); } + return $this->setHttpResponse($response); } @@ -78,17 +89,9 @@ public function resourceAction() // Handle a request for an OAuth2.0 Access Token and send the response to the client if (!$this->server->verifyResourceRequest($this->getOAuth2Request())) { $response = $this->server->getResponse(); - $parameters = $response->getParameters(); - $errorUri = isset($parameters['error_uri']) ? $parameters['error_uri'] : null; - return new ApiProblemResponse( - new ApiProblem( - $response->getStatusCode(), - $parameters['error_description'], - $errorUri, - $parameters['error'] - ) - ); + return $this->getApiProblemResponse($response); } + $httpResponse = $this->getResponse(); $httpResponse->setStatusCode(200); $httpResponse->getHeaders()->addHeaders(array('Content-type' => 'application/json')); @@ -107,17 +110,10 @@ public function authorizeAction() $response = new OAuth2Response(); // validate the authorize request - if (!$this->server->validateAuthorizeRequest($request, $response)) { - $parameters = $response->getParameters(); - $errorUri = isset($parameters['error_uri']) ? $parameters['error_uri'] : null; - return new ApiProblemResponse( - new ApiProblem( - $response->getStatusCode(), - $parameters['error_description'], - $errorUri, - $parameters['error'] - ) - ); + $isValid = $this->server->validateAuthorizeRequest($request, $response); + + if (!$isValid) { + return $this->getErrorResponse($response); } $authorized = $request->request('authorized', false); @@ -141,16 +137,7 @@ public function authorizeAction() return $this->redirect()->toUrl($redirect); } - $parameters = $response->getParameters(); - $errorUri = isset($parameters['error_uri']) ? $parameters['error_uri'] : null; - return new ApiProblemResponse( - new ApiProblem( - $response->getStatusCode(), - $parameters['error_description'], - $errorUri, - $parameters['error'] - ) - ); + return $this->getErrorResponse($response); } /** @@ -166,6 +153,42 @@ public function receiveCodeAction() return $view; } + /** + * @param OAuth2Response $response + * @return \ZF\ApiProblem\ApiProblemResponse|\Zend\Stdlib\ResponseInterface + */ + protected function getErrorResponse(OAuth2Response $response) + { + if ($this->isApiProblemErrorResponse()) { + return $this->getApiProblemResponse($response); + } else { + return $this->setHttpResponse($response); + } + } + + /** + * Map OAuth2Response to ApiProblemResponse + * + * @param OAuth2Response $response + * @return ApiProblemResponse + */ + protected function getApiProblemResponse(OAuth2Response $response) + { + $parameters = $response->getParameters(); + $errorUri = isset($parameters['error_uri']) ? $parameters['error_uri'] : null; + $error = isset($parameters['error']) ? $parameters['error'] : null; + $errorDescription = isset($parameters['error_description']) ? $parameters['error_description'] : null; + + return new ApiProblemResponse( + new ApiProblem( + $response->getStatusCode(), + $errorDescription, + $errorUri, + $error + ) + ); + } + /** * Create an OAuth2 request based on the ZF2 request object * @@ -210,6 +233,9 @@ protected function getOAuth2Request() if (isset($server['PHP_AUTH_PW'])) { $headers['PHP_AUTH_PW'] = $server['PHP_AUTH_PW']; } + if (isset($server['HTTP_AUTHORIZATION'])) { + $headers['AUTHORIZATION'] = $server['HTTP_AUTHORIZATION']; + } // Ensure the bodyParams are passed as an array $bodyParams = $this->bodyParams() ?: array(); diff --git a/src/Factory/AuthControllerFactory.php b/src/Factory/AuthControllerFactory.php index a8baaa6..5c03731 100644 --- a/src/Factory/AuthControllerFactory.php +++ b/src/Factory/AuthControllerFactory.php @@ -19,6 +19,14 @@ class AuthControllerFactory implements FactoryInterface public function createService(ServiceLocatorInterface $controllers) { $services = $controllers->getServiceLocator()->get('ServiceManager'); - return new AuthController($services->get('ZF\OAuth2\Service\OAuth2Server')); + $authController = new AuthController($services->get('ZF\OAuth2\Service\OAuth2Server')); + + $config = $services->get('Config'); + $apiProblemErrorResponse = isset($config['zf-oauth2']['api_problem_error_response']) + && $config['zf-oauth2']['api_problem_error_response'] === true; + + $authController->setApiProblemErrorResponse($apiProblemErrorResponse); + + return $authController; } } diff --git a/test/Controller/AuthControllerTest.php b/test/Controller/AuthControllerTest.php index dc7f1a6..ff826f6 100644 --- a/test/Controller/AuthControllerTest.php +++ b/test/Controller/AuthControllerTest.php @@ -45,6 +45,51 @@ public function testToken() $this->assertTrue(!empty($response['token_type'])); } + public function testTokenErrorIsApiProblem() + { + $request = $this->getRequest(); + $request->getPost()->set('grant_type', 'fake_grant_type'); + $request->getServer()->set('PHP_AUTH_USER', 'testclient'); + $request->getServer()->set('PHP_AUTH_PW', 'testpass'); + $request->setMethod('POST'); + + $this->dispatch('/oauth'); + $this->assertControllerName('ZF\OAuth2\Controller\Auth'); + $this->assertActionName('token'); + $this->assertResponseStatusCode(400); + + $headers = $this->getResponse()->getHeaders(); + $this->assertEquals('application/problem+json', $headers->get('content-type')->getFieldValue()); + + $response = json_decode($this->getResponse()->getContent(), true); + $this->assertEquals('unsupported_grant_type', $response['title']); + $this->assertEquals('Grant type "fake_grant_type" not supported', $response['detail']); + $this->assertEquals('400', $response['status']); + } + + public function testTokenErrorIsOAuth2Format() + { + $request = $this->getRequest(); + $request->getPost()->set('grant_type', 'fake_grant_type'); + $request->getServer()->set('PHP_AUTH_USER', 'testclient'); + $request->getServer()->set('PHP_AUTH_PW', 'testpass'); + $request->setMethod('POST'); + + $this->setIsOAuth2FormatResponse(); + + $this->dispatch('/oauth'); + $this->assertControllerName('ZF\OAuth2\Controller\Auth'); + $this->assertActionName('token'); + $this->assertResponseStatusCode(400); + + $headers = $this->getResponse()->getHeaders(); + $this->assertEquals('application/json', $headers->get('content-type')->getFieldValue()); + + $response = json_decode($this->getResponse()->getContent(), true); + $this->assertEquals('unsupported_grant_type', $response['error']); + $this->assertEquals('Grant type "fake_grant_type" not supported', $response['error_description']); + } + public function testAuthorizeForm() { $_GET['response_type'] = 'code'; @@ -59,7 +104,7 @@ public function testAuthorizeForm() $this->assertXpathQuery('//form/input[@name="authorized" and @value="no"]'); } - public function testAuthorizeErrorParam() + public function testAuthorizeParamErrorIsApiProblem() { $this->dispatch('/oauth/authorize'); @@ -76,6 +121,24 @@ public function testAuthorizeErrorParam() $this->assertEquals('400', $response['status']); } + public function testAuthorizeParamErrorIsOAuth2Format() + { + $this->setIsOAuth2FormatResponse(); + + $this->dispatch('/oauth/authorize'); + + $this->assertControllerName('ZF\OAuth2\Controller\Auth'); + $this->assertActionName('authorize'); + $this->assertResponseStatusCode(400); + + $headers = $this->getResponse()->getHeaders(); + $this->assertEquals('application/json', $headers->get('content-type')->getFieldValue()); + + $response = json_decode($this->getResponse()->getContent(), true); + $this->assertEquals('invalid_client', $response['error']); + $this->assertEquals('No client id supplied', $response['error_description']); + } + public function testAuthorizeCode() { $_GET['response_type'] = 'code'; @@ -174,6 +237,7 @@ public function testResource() $this->assertResponseStatusCode(200); $response = json_decode($this->getResponse()->getContent(), true); + $this->assertTrue($response['success']); $this->assertEquals('You accessed my APIs!', $response['message']); @@ -191,4 +255,15 @@ public function testResource() $this->assertTrue($response['success']); $this->assertEquals('You accessed my APIs!', $response['message']); } + + protected function setIsOAuth2FormatResponse() + { + $serviceManager = $this->getApplication()->getServiceManager(); + + $config = $serviceManager->get('Config'); + $config['zf-oauth2']['api_problem_error_response'] = false; + + $serviceManager->setAllowOverride(true); + $serviceManager->setService('Config', $config); + } } diff --git a/test/Factory/AuthControllerFactoryTest.php b/test/Factory/AuthControllerFactoryTest.php index 5ad5013..2f6f81d 100644 --- a/test/Factory/AuthControllerFactoryTest.php +++ b/test/Factory/AuthControllerFactoryTest.php @@ -48,6 +48,12 @@ protected function setUp() $this->services = $services = new ServiceManager(); + $this->services->setService('Config', array( + 'zf-oauth2' => array( + 'api_problem_error_response' => true, + ), + )); + $this->controllers = $controllers = new ControllerManager(); $controllers->setServiceLocator(new ServiceManager()); $controllers->getServiceLocator()->setService('ServiceManager', $services);