diff --git a/lib/OAuth2/Model/IOAuth2Client.php b/lib/OAuth2/Model/IOAuth2Client.php index 20e3323..2af1ee8 100644 --- a/lib/OAuth2/Model/IOAuth2Client.php +++ b/lib/OAuth2/Model/IOAuth2Client.php @@ -6,5 +6,9 @@ interface IOAuth2Client { public function getPublicId(); public function getRedirectUris(); + + public function getSupportedScopes(); + public function getDefaultScopes(); + public function getScopePolicy(); } diff --git a/lib/OAuth2/Model/OAuth2Client.php b/lib/OAuth2/Model/OAuth2Client.php index c356dd8..f3152d3 100644 --- a/lib/OAuth2/Model/OAuth2Client.php +++ b/lib/OAuth2/Model/OAuth2Client.php @@ -8,10 +8,18 @@ class OAuth2Client implements IOAuth2Client { private $redirectUris; private $secret; - public function __construct($id, $secret = NULL, array $redirectUris = array()) { + private $supported_scopes; + private $default_scopes; + private $scope_policy; + + public function __construct($id, $secret = NULL, array $redirectUris = array(), $scope_policy = null, $default_scopes = null, $supported_scopes = null) { $this->setPublicId($id); $this->setSecret($secret); $this->setRedirectUris($redirectUris); + + $this->setScopePolicy($scope_policy); + $this->setDefaultScopes($default_scopes); + $this->setSupportedScopes($supported_scopes); } public function setPublicId($id) { @@ -37,5 +45,31 @@ public function setRedirectUris(array $redirectUris) { public function getRedirectUris() { return $this->redirectUris; } + + + public function setSupportedScopes($supported_scopes) { + $this->supported_scopes = $supported_scopes; + } + + public function getSupportedScopes() { + return $this->supported_scopes; + } + + + public function setDefaultScopes($default_scopes) { + $this->default_scopes = $default_scopes; + } + + public function getDefaultScopes() { + return $this->default_scopes; + } + + public function setScopePolicy($scope_policy) { + $this->scope_policy = $scope_policy; + } + + public function getScopePolicy() { + return $this->scope_policy; + } } diff --git a/lib/OAuth2/OAuth2.php b/lib/OAuth2/OAuth2.php index 9765634..d474d7c 100644 --- a/lib/OAuth2/OAuth2.php +++ b/lib/OAuth2/OAuth2.php @@ -85,6 +85,19 @@ class OAuth2 { const DEFAULT_REFRESH_TOKEN_LIFETIME = 1209600; const DEFAULT_AUTH_CODE_LIFETIME = 30; const DEFAULT_WWW_REALM = 'Service'; + const DEFAULT_SCOPE_POLICY = self::POLICY_MODE_DEFAULT; + + /** + * Available scope policies. + * + * @var string + */ + const POLICY_MODE_ERROR = 'error'; + const POLICY_MODE_DEFAULT = 'default'; + static function supportedPolicies() + { + return array(self::POLICY_MODE_DEFAULT, self::POLICY_MODE_ERROR); + } /** * Configurable options. @@ -95,6 +108,8 @@ class OAuth2 { const CONFIG_REFRESH_LIFETIME = 'refresh_token_lifetime'; // The lifetime of refresh token in seconds. const CONFIG_AUTH_LIFETIME = 'auth_code_lifetime'; // The lifetime of auth code in seconds. const CONFIG_SUPPORTED_SCOPES = 'supported_scopes'; // Array of scopes you want to support + const CONFIG_SCOPES_POLICY = 'scopes_policy'; // Policy if no scope is set. Values can be "error" or "default". + const CONFIG_DEFAULT_SCOPES = 'default_scopes'; // If scope policy is set to "default", this array of scopes will be used. const CONFIG_TOKEN_TYPE = 'token_type'; // Token type to respond with. Currently only "Bearer" supported. const CONFIG_WWW_REALM = 'realm'; const CONFIG_ENFORCE_INPUT_REDIRECT = 'enforce_redirect'; // Set to true to enforce redirect_uri on input for both authorize and token steps. @@ -371,7 +386,9 @@ protected function setDefaultOptions() { self::CONFIG_ENFORCE_INPUT_REDIRECT => TRUE, self::CONFIG_ENFORCE_STATE => FALSE, - self::CONFIG_SUPPORTED_SCOPES => null, // This is expected to be passed in on construction. Scopes can be an aribitrary string. + self::CONFIG_SUPPORTED_SCOPES => null, // This is expected to be passed in on construction. Scopes can be an arbitrary string. + self::CONFIG_SCOPES_POLICY => self::DEFAULT_SCOPE_POLICY, // This is expected to be passed in on construction. See constants for supported policies. + self::CONFIG_DEFAULT_SCOPES => null, // This is expected to be passed in on construction. Default scopes can be an arbitrary string. ); } @@ -651,6 +668,93 @@ protected function checkScope($required_scope, $available_scope) { return (count(array_diff($required_scope, $available_scope)) == 0); } + /** + * Checks whether the scope policy is respected. + * + * @param IOAuth2Client $client + * The client. + * + * @param string $scope + * The scopes to check. + * + * @throws OAuth2ServerException + * + * @return string + * The modified scopes according to the policy of the client or the server. + * + * @see https://tools.ietf.org/html/rfc6749#section-3.3 + * + * @ingroup oauth2_section_3.3 + */ + protected function checkScopePolicy(IOAuth2Client $client, $scope) { + + $policy = $this->getScopePolicy($client); + if( !in_array($policy, self::supportedPolicies()) ) + throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'The policy must be one of these values: '.json_encode(self::supportedPolicies() )); + + // If Scopes Policy is set to "error" and no scope is input, then throws an error + if (!$scope && self::POLICY_MODE_ERROR === $policy ) { + throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'No scope was requested.'); + } + + // If Scopes Policy is set to "default" and no scope is input, then application or client defaults are set + if (!$scope && self::POLICY_MODE_DEFAULT === $policy ) { + return $this->getDefaultScopes($client); + } + return $scope; + } + + /** + * Get the scope policy. + * + * @param IOAuth2Client $client + * The client. + * + * @return string + * The scope policy depending on the client and the server. + * + * @see https://tools.ietf.org/html/rfc6749#section-3.3 + * + * @ingroup oauth2_section_3.3 + */ + protected function getScopePolicy(IOAuth2Client $client) { + return $client->getScopePolicy()?:$this->getVariable(self::CONFIG_SCOPES_POLICY, self::POLICY_MODE_DEFAULT); + } + + /** + * Get the default scopes. + * + * @param IOAuth2Client $client + * The client. + * + * @return string + * The default scopes depending on the client and the server. + * + * @see https://tools.ietf.org/html/rfc6749#section-3.3 + * + * @ingroup oauth2_section_3.3 + */ + protected function getDefaultScopes(IOAuth2Client $client) { + return $client->getDefaultScopes()?:$this->getVariable(self::CONFIG_DEFAULT_SCOPES, null); + } + + /** + * Get the available scopes. + * + * @param IOAuth2Client $client + * The client. + * + * @return string + * The available scopes depending on the client and the server. + * + * @see https://tools.ietf.org/html/rfc6749#section-3.3 + * + * @ingroup oauth2_section_3.3 + */ + protected function getSupportedScopes(IOAuth2Client $client) { + return $client->getSupportedScopes()?:$this->getVariable(self::CONFIG_SUPPORTED_SCOPES, null); + } + // Access token granting (Section 4). /** @@ -701,6 +805,7 @@ public function grantAccessToken(Request $request = NULL) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); } + // Authorize the client $clientCreds = $this->getClientCredentials($inputData, $authHeaders); @@ -718,10 +823,15 @@ public function grantAccessToken(Request $request = NULL) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNAUTHORIZED_CLIENT, 'The grant type is unauthorized for this client_id'); } + $input["scope"] = $this->checkScopePolicy($client, $input["scope"]); + // Do the granting switch ($input["grant_type"]) { case self::GRANT_TYPE_AUTH_CODE: $stored = $this->grantAccessTokenAuthCode($client, $input); // returns array('data' => data, 'scope' => scope) + if( isset($stored['scope'])) { + $input["scope"] = $stored["scope"]; + } break; case self::GRANT_TYPE_USER_CREDENTIALS: $stored = $this->grantAccessTokenUserCredentials($client, $input); // returns: true || array('scope' => scope) @@ -746,14 +856,14 @@ public function grantAccessToken(Request $request = NULL) { // if no scope provided to check against $input['scope'] then application defaults are set // if no data is provided than null is set - $stored += array('scope' => $this->getVariable(self::CONFIG_SUPPORTED_SCOPES, null), 'data' => null); + $stored += array('scope' => $this->getSupportedScopes($client), 'data' => null); // Check scope, if provided if ($input["scope"] && (!isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) { throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.'); } - $token = $this->createAccessToken($client, $stored['data'], $stored['scope']); + $token = $this->createAccessToken($client, $stored['data'], $input['scope']); return new Response(json_encode($token), 200, $this->getJsonHeaders()); } @@ -980,7 +1090,7 @@ protected function getAuthorizeParams(Request $request = NULL) { } // Validate that the requested scope is supported - if ($input["scope"] && !$this->checkScope($input["scope"], $this->getVariable(self::CONFIG_SUPPORTED_SCOPES))) { + if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes($client))) { throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.', $input["state"]); } @@ -1073,6 +1183,13 @@ public function finishClientAuthorization($is_authorized, $data = NULL, Request $result["query"]["state"] = $params["state"]; } + // Validate that the requested scope is supported + if ($scope && !$this->checkScope($scope, $this->getSupportedScopes($params["client"]))) { + throw new OAuth2RedirectException($params["redirect_uri"], self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.', $params["state"]); + } + + $scope = $this->checkScopePolicy($params["client"], $scope); + if ($is_authorized === FALSE) { throw new OAuth2RedirectException($params["redirect_uri"], self::ERROR_USER_DENIED, "The user denied access to your application", $params["state"]); } diff --git a/tests/OAuth2Test.php b/tests/OAuth2Test.php index 44cc680..e97246e 100644 --- a/tests/OAuth2Test.php +++ b/tests/OAuth2Test.php @@ -501,11 +501,11 @@ public function testGrantAccessTokenWithGrantUserWithReducedScope() { array('date' => null) )); - $this->assertRegExp('{"access_token":"[^"]+","expires_in":3600,"token_type":"bearer","scope":"scope1 scope2"}', $response->getContent()); + $this->assertRegExp('{"access_token":"[^"]+","expires_in":3600,"token_type":"bearer","scope":"scope1"}', $response->getContent()); $token = $stub->getLastAccessToken(); $this->assertSame('cid', $token->getClientId()); - $this->assertSame('scope1 scope2', $token->getScope()); + $this->assertSame('scope1', $token->getScope()); } /** @@ -600,6 +600,44 @@ public function testFinishClientAuthorization() { $this->assertSame(null, $code->getScope()); $this->assertSame($data, $code->getData()); } + + /** + * Tests OAuth2->finishClientAuthorization() + */ + public function testFinishClientAuthorizationWithScopes() { + + $stub = new OAuth2GrantCodeStub; + $stub->addClient(new OAuth2Client('blah', 'foo', array('http://www.example.com/'))); + $stub->setAllowedGrantTypes(array('authorization_code')); + $oauth2 = new OAuth2($stub, array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + )); + + $data = new \stdClass; + + $response = $oauth2->finishClientAuthorization(true, $data, new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'scope1 scope3 scope5' + ); + + $this->assertSame(302, $response->getStatusCode()); + $this->assertRegexp('#^http://www\.example\.com/\?foo=bar&state=42&code=#', $response->headers->get('location')); + + $code = $stub->getLastAuthCode(); + $this->assertSame('blah', $code->getClientId()); + $this->assertSame('scope1 scope3 scope5', $code->getScope()); + $this->assertSame($data, $code->getData()); + + $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'blah', 'client_secret' => 'foo', 'redirect_uri' => 'http://www.example.com/?foo=bars', 'code'=> $code->getToken()); + $request = $this->createRequest($inputData); + + $response = $oauth2->grantAccessToken($request); + $this->assertRegexp('{"access_token":"[^"]+","expires_in":3600,"token_type":"bearer","scope":"scope1 scope3 scope5"}', $response->getContent()); + } public function testFinishClientAuthorizationThrowsErrorIfClientIdMissing() { @@ -839,6 +877,404 @@ public function testFinishClientAuthorizationThrowsErrorIfUnauthorized() { } } + /** + * Test for scope policies and default scopes + * @dataProvider getTestScopePolicyData + */ + public function testScopePolicy(OAuth2Client $client, array $server_options, Request $request = null, $exception = null, $exceptionMessage = null, $exceptionDescription = null, $requested_scopes = null, $expected_scopes = null) { + + $stub = new OAuth2GrantCodeStub; + $stub->addClient( $client ); + + try { + $oauth2 = new OAuth2($stub, $server_options); + + if( $request !== null ) { + $data = new \stdClass; + $response = $oauth2->finishClientAuthorization(true, $data, $request, $requested_scopes); + + $code = $stub->getLastAuthCode(); + $this->assertSame($expected_scopes, $code->getScope()); + } + + if( $exception !== null ) { + $this->fail('The expected exception was not thrown'); + } + } catch(\Exception $e) { + if (!$exception || !($e instanceof $exception)) { + throw $e; + } + $this->assertSame($exceptionMessage, $e->getMessage()); + $this->assertSame($exceptionDescription, $e->getDescription()); + } + } + + public function getTestScopePolicyData() { + return array( + /** Scope policy and default scopes defined by the application **/ + // Unknown scope policy + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/')), + array( + OAuth2::CONFIG_SCOPES_POLICY => 'custom_mode', + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2ServerException', + Oauth2::ERROR_INVALID_SCOPE, + 'The policy must be one of these values: '.json_encode(OAuth2::supportedPolicies()), + ), + + // Error policy without scope in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/')), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2ServerException', + Oauth2::ERROR_INVALID_SCOPE, + 'No scope was requested.', + ), + + // Error policy with scopes in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/')), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope1 scope2', + 'scope1 scope2', + ), + + // Default policy, no default scopes and no scope in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/')), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_DEFAULT, + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + ), + + // Default policy, no default scopes and scopes in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/')), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_DEFAULT, + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope1 scope2', + 'scope1 scope2', + ), + + // Default policy, default scopes and scopes in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/')), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_DEFAULT, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope1 scope2', + 'scope1 scope2', + ), + + /** Scope policy and default scopes defined by the client **/ + // Unknown policy + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), 'unknown_policy'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_DEFAULT, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2ServerException', + Oauth2::ERROR_INVALID_SCOPE, + 'The policy must be one of these values: '.json_encode(OAuth2::supportedPolicies()), + null, + null, + ), + + // Error policy and no scope in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_ERROR), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_DEFAULT, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2ServerException', + Oauth2::ERROR_INVALID_SCOPE, + 'No scope was requested.', + null, + null, + ), + + // Error policy and scopes in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_ERROR), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_DEFAULT, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope1 scope2', + 'scope1 scope2', + ), + + // Default policy, no default scopes in client and no scope in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_DEFAULT), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + null, + 'scope3 scope5 scope7', + ), + + // Default policy, default scopes in client and no scope in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_DEFAULT, 'scope4 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + null, + 'scope4 scope6', + ), + + // Default policy, default scopes in client and scopes in request + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_DEFAULT, 'scope4 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3 scope4 scope5 scope6 scope7", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope3 scope5 scope7", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope1 scope2', + 'scope1 scope2', + ), + + /* Scope policy with supported scope defined by the client */ + // No scope are requested and default scopes should be set + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_DEFAULT, 'scope6', 'scope4 scope5 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope2", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + null, + 'scope6', + ), + + // No scope are requested and error mode is set + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_ERROR, null, 'scope4 scope5 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope2", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2ServerException', + Oauth2::ERROR_INVALID_SCOPE, + 'No scope was requested.', + ), + + // No scope are requested and error mode is set + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_ERROR, null, 'scope4 scope5 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope2", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope6', + 'scope6' + ), + + // Scopes are requested and should be accepted + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_DEFAULT, 'scope6', 'scope4 scope5 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope2", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + null, + null, + null, + 'scope4 scope5', + 'scope4 scope5', + ), + + // Scopes are requested but are not supported by the client + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_DEFAULT, 'scope6', 'scope4 scope5 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope2", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2RedirectException', + Oauth2::ERROR_INVALID_SCOPE, + 'An unsupported scope was requested.', + 'scope1 scope2', + ), + + // Scopes are requested but are not supported by the client + array( + new OAuth2Client('blah', 'foo', array('http://www.example.com/'), OAuth2::POLICY_MODE_ERROR, null, 'scope4 scope5 scope6'), + array( + OAuth2::CONFIG_SUPPORTED_SCOPES => "scope1 scope2 scope3", + OAuth2::CONFIG_SCOPES_POLICY => OAuth2::POLICY_MODE_ERROR, + OAuth2::CONFIG_DEFAULT_SCOPES => "scope2", + ), + new Request(array( + 'client_id' => 'blah', + 'redirect_uri' => 'http://www.example.com/?foo=bar', + 'response_type' => 'code', + 'state' => '42', + )), + 'OAuth2\OAuth2RedirectException', + Oauth2::ERROR_INVALID_SCOPE, + 'An unsupported scope was requested.', + 'scope1 scope2', + ), + ); + } + /** * @dataProvider getTestGetBearerTokenData */