diff --git a/src/League/OAuth2/Server/Resource.php b/src/League/OAuth2/Server/Resource.php index eb48ac5e..4ba64b13 100644 --- a/src/League/OAuth2/Server/Resource.php +++ b/src/League/OAuth2/Server/Resource.php @@ -76,6 +76,117 @@ class Resource */ protected $clientId = null; + /** + * Exception error codes + * @var array + */ + protected static $exceptionCodes = array( + 0 => 'invalid_request', + 1 => 'invalid_token', + 2 => 'insufficient_scope', + ); + + /** + * Exception error messages + * @var array + */ + protected static $exceptionMessages = array( + 'invalid_request' => 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the "%s" parameter.', + 'invalid_token' => 'The access token provided is expired, revoked, malformed, or invalid for other reasons.', + 'insufficient_scope' => 'The request requires higher privileges than provided by the access token.', + ); + + /** + * Exception error HTTP status codes + * @var array + * + * RFC 6750, section 3.1: + * When a request fails, the resource server responds using the + * appropriate HTTP status code (typically, 400, 401, 403, or 405) and + * includes one of the following error codes in the response: + */ + protected static $exceptionHttpStatusCodes = array( + 'invalid_request' => 400, + 'invalid_token' => 401, + 'insufficient_scope' => 403, + ); + + /** + * Get an exception message + * + * @param string $error The error message key + * @return string The error message + */ + public static function getExceptionMessage($error = '') + { + return self::$exceptionMessages[$error]; + } + + /** + * Get an exception code + * + * @param integer $code The exception code + * @return string The exception code type + */ + public static function getExceptionType($code = 0) + { + return self::$exceptionCodes[$code]; + } + + /** + * Get all headers that have to be send with the error response + * + * @param string $error The error message key + * @return array Array with header values + */ + public static function getExceptionHttpHeaders($error) + { + $headers = array(); + switch (self::$exceptionHttpStatusCodes[$error]) { + case 401: + $headers[] = 'HTTP/1.1 401 Unauthorized'; + break; + case 403: + $headers[] = 'HTTP/1.1 403 Forbidden'; + break; + case 400: + default: + $headers[] = 'HTTP/1.1 400 Bad Request'; + } + + // Add "WWW-Authenticate" header + // + // RFC 6749, section 5.2.: + // "If the client attempted to authenticate via the 'Authorization' + // request header field, the authorization server MUST + // respond with an HTTP 401 (Unauthorized) status code and + // include the "WWW-Authenticate" response header field + // matching the authentication scheme used by the client. + // @codeCoverageIgnoreStart + if ($error === 'insufficient_scope') { + $authScheme = null; + $request = new Request(); + if ($request->server('PHP_AUTH_USER') !== null) { + $authScheme = 'Basic'; + } else { + $authHeader = $request->header('Authorization'); + if ($authHeader !== null) { + if (strpos($authHeader, 'Bearer') === 0) { + $authScheme = 'Bearer'; + } elseif (strpos($authHeader, 'Basic') === 0) { + $authScheme = 'Basic'; + } + } + } + if ($authScheme !== null) { + $headers[] = 'WWW-Authenticate: '.$authScheme.' realm=""'; + } + } + // @codeCoverageIgnoreEnd + + return $headers; + } + /** * Sets up the Resource * @@ -187,7 +298,7 @@ class Resource $result = $this->storages['session']->validateAccessToken($accessToken); if (! $result) { - throw new Exception\InvalidAccessTokenException('Access token is not valid'); + throw new Exception\InvalidAccessTokenException(self::$exceptionMessages['invalid_token'], 1); } $this->accessToken = $accessToken; @@ -275,7 +386,7 @@ class Resource } if (empty($accessToken)) { - throw new Exception\MissingAccessTokenException('Access token is missing'); + throw new Exception\MissingAccessTokenException(self::$exceptionMessages['invalid_request'], 0); } return $accessToken; diff --git a/tests/resource/ResourceServerTest.php b/tests/resource/ResourceServerTest.php index b25b89be..9658cba5 100644 --- a/tests/resource/ResourceServerTest.php +++ b/tests/resource/ResourceServerTest.php @@ -16,6 +16,32 @@ class Resource_Server_test extends PHPUnit_Framework_TestCase return new League\OAuth2\Server\Resource($this->session); } + public function test_getExceptionMessage() + { + $m = League\OAuth2\Server\Resource::getExceptionMessage('invalid_request'); + + $reflector = new ReflectionClass($this->returnDefault()); + $exceptionMessages = $reflector->getProperty('exceptionMessages'); + $exceptionMessages->setAccessible(true); + $v = $exceptionMessages->getValue(); + + $this->assertEquals($v['invalid_request'], $m); + } + + public function test_getExceptionCode() + { + $this->assertEquals('invalid_request', League\OAuth2\Server\Resource::getExceptionType(0)); + $this->assertEquals('invalid_token', League\OAuth2\Server\Resource::getExceptionType(1)); + $this->assertEquals('insufficient_scope', League\OAuth2\Server\Resource::getExceptionType(2)); + } + + public function test_getExceptionHttpHeaders() + { + $this->assertEquals(array('HTTP/1.1 400 Bad Request'), League\OAuth2\Server\Resource::getExceptionHttpHeaders('invalid_request')); + $this->assertEquals(array('HTTP/1.1 401 Unauthorized'), League\OAuth2\Server\Resource::getExceptionHttpHeaders('invalid_token')); + $this->assertContains('HTTP/1.1 403 Forbidden', League\OAuth2\Server\Resource::getExceptionHttpHeaders('insufficient_scope')); + } + public function test_setRequest() { $s = $this->returnDefault();