clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,516 @@
<?php
namespace Drupal\Tests\simple_oauth\Functional;
use Drupal\Core\Url;
use Drupal\simple_oauth\Entity\Oauth2Scope;
use Drupal\simple_oauth\Oauth2ScopeInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;
use GuzzleHttp\Psr7\Query;
use Psr\Http\Message\ResponseInterface;
/**
* The auth code test.
*
* @group simple_oauth
*/
class AuthCodeFunctionalTest extends TokenBearerFunctionalTestBase {
/**
* The authorize URL.
*
* @var \Drupal\Core\Url
*/
protected Url $authorizeUrl;
/**
* An extra scope for testing.
*
* @var \Drupal\simple_oauth\Oauth2ScopeInterface
*/
protected Oauth2ScopeInterface $extraScope;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->authorizeUrl = Url::fromRoute('oauth2_token.authorize');
$this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), [
'grant simple_oauth codes',
'access content',
]);
$this->extraScope = Oauth2Scope::create([
'name' => 'test:scope3',
'description' => 'Test scope 3 description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
],
],
'umbrella' => TRUE,
]);
$this->extraScope->save();
}
/**
* Test the valid AuthCode grant with a public client.
*/
public function testPublicAuthCodeGrant(): void {
$this->client->set('confidential', FALSE)->save();
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
// 1. Anonymous request invites the user to log in.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$assert_session = $this->assertSession();
$assert_session->buttonExists('Log in');
// 2. Log the user in and try again.
$this->drupalLogin($this->user);
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
// 3. Grant access by submitting the form and get the code back.
$this->submitForm([], 'Allow');
// Store the code for the second part of the flow.
$code = $this->getAndValidateCodeFromResponse();
// 4. Send the code to get the access token.
$response = $this->postGrantedCodeWithScopes($code, $this->scope, FALSE);
$parsed_response = $this->assertValidTokenResponse($response, TRUE);
// 5. Ensure codes cannot be re-used.
$response = $this->postGrantedCodeWithScopes($code, $this->scope, FALSE);
$this->assertEquals(400, $response->getStatusCode());
// 6. Test access token.
$this->assertAccessTokenOnResource($parsed_response['access_token']);
}
/**
* Test the automatic authorization when enabled on client.
*/
public function testAutomaticAuthorization(): void {
$this->client->set('automatic_authorization', TRUE);
$this->client->save();
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
// 1. Anonymous request invites the user to log in.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$assert_session = $this->assertSession();
$assert_session->buttonExists('Log in');
// 2. Log the user in and try again. This time we should get a code
// immediately without granting, because the consumer is not 3rd party.
$this->drupalLogin($this->user);
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
// Store the code for the second part of the flow.
$code = $this->getAndValidateCodeFromResponse();
// 3. Send the code to get the access token, regardless of the scopes, since
// the consumer has automatic authorization enabled.
$response = $this->postGrantedCodeWithScopes(
$code,
$this->scope . ' ' . $this->extraScope->id()
);
$parsed_response = $this->assertValidTokenResponse($response, TRUE);
// 4. Test access token.
$this->assertAccessTokenOnResource($parsed_response['access_token']);
}
/**
* Tests functionality remember approval, which is enabled by default.
*/
public function testDefaultEnabledRememberApproval(): void {
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
// 1. Anonymous request invites the user to log in.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$assert_session = $this->assertSession();
$assert_session->buttonExists('Log in');
// 2. Log the user in and try again.
$this->drupalLogin($this->user);
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
// 3. Grant access by submitting the form and get the token back.
$this->submitForm([], 'Allow');
// Store the code for the second part of the flow.
$code = $this->getAndValidateCodeFromResponse();
// 4. Send the code to get the access token.
$response = $this->postGrantedCodeWithScopes($code, $this->scope);
$parsed_response = $this->assertValidTokenResponse($response, TRUE);
// 5. Ensure codes cannot be re-used.
$response = $this->postGrantedCodeWithScopes($code, $this->scope);
$this->assertEquals(400, $response->getStatusCode());
// 6. Test access token.
$this->assertAccessTokenOnResource($parsed_response['access_token']);
}
/**
* Test confidential clients enforce a client secret.
*/
public function testConfidentialAuthCodeGrant(): void {
$this->client->set('confidential', TRUE)->save();
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
// 1. Anonymous request invites the user to log in.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$assert_session = $this->assertSession();
$assert_session->buttonExists('Log in');
// 2. Log the user in and try again.
$this->drupalLogin($this->user);
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
// 3. Grant access by submitting the form and get the code back.
$this->submitForm([], 'Allow');
// Store the code for the second part of the flow.
$code = $this->getAndValidateCodeFromResponse();
// 4. Send a request without a client secret.
$response = $this->postGrantedCodeWithScopes($code, $this->scope, FALSE);
$this->assertEquals(401, $response->getStatusCode());
// 5. Confidential clients still work when passing a secret.
$response = $this->postGrantedCodeWithScopes($code, $this->scope);
$this->assertValidTokenResponse($response, TRUE);
// Do a second authorize request, the client is now remembered and the user
// does not need to confirm again.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$code = $this->getAndValidateCodeFromResponse();
$response = $this->postGrantedCodeWithScopes($code, $this->scope);
$this->assertValidTokenResponse($response, TRUE);
// Do a third request with an additional scope.
$valid_params['scope'] .= ' ' . $this->extraScope->getName();
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
$this->assertSession()->pageTextContains($this->extraScope->getDescription());
$this->submitForm([], 'Allow');
$code = $this->getAndValidateCodeFromResponse();
$response = $this->postGrantedCodeWithScopes(
$code, $valid_params['scope']
);
$this->assertValidTokenResponse($response, TRUE);
// Do another request with the additional scope, this scope is now
// remembered too.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$code = $this->getAndValidateCodeFromResponse();
$response = $this->postGrantedCodeWithScopes(
$code, $valid_params['scope']
);
$this->assertValidTokenResponse($response, TRUE);
// Disable remember approval feature, make sure that the redirect doesn't
// happen automatically anymore.
$this->client->set('remember_approval', FALSE);
$this->client->save();
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
}
/**
* Test the AuthCode grant with PKCE.
*/
public function testClientAuthCodeGrantWithPkce(): void {
$this->client->set('pkce', TRUE);
$this->client->set('confidential', FALSE);
$this->client->save();
// For PKCE flow we need a code verifier and a code challenge.
// @see https://tools.ietf.org/html/rfc7636 for details.
$code_verifier = self::base64urlencode(random_bytes(64));
$code_challenge = self::base64urlencode(hash('sha256', $code_verifier, TRUE));
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'code_challenge' => $code_challenge,
'code_challenge_method' => 'S256',
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
// 1. Anonymous request redirect to log in.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$assert_session = $this->assertSession();
$assert_session->buttonExists('Log in');
// 2. Logged in user gets the grant form.
$this->drupalLogin($this->user);
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
// 3. Grant access by submitting the form.
$this->submitForm([], 'Allow');
// Store the code for the second part of the flow.
$code = $this->getAndValidateCodeFromResponse();
// Request the access and refresh token.
$valid_payload = [
'grant_type' => 'authorization_code',
'client_id' => $this->client->getClientId(),
'code_verifier' => $code_verifier,
'scope' => $this->scope . ' ' . $this->extraScope->getName(),
'code' => $code,
'redirect_uri' => $this->redirectUri,
];
$response = $this->post($this->url, $valid_payload);
$parsed_response = $this->assertValidTokenResponse($response, TRUE);
// Test access token.
$this->assertAccessTokenOnResource($parsed_response['access_token']);
}
/**
* Test the optional redirect uri.
*/
public function testOptionalRedirectUri(): void {
// Not providing redirect uri, this means the redirect uri set on the client
// will be used.
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
];
// 1. Anonymous request invites the user to log in.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$assert_session = $this->assertSession();
$assert_session->buttonExists('Log in');
// 2. Log the user in and try again.
$this->drupalLogin($this->user);
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->assertGrantForm();
// 3. Deny access by submitting the form.
$this->submitForm([], 'Deny');
$query = $this->getQueryAndValidateRedirect();
$this->assertArrayHasKey('error', $query);
$this->assertEquals('access_denied', $query['error']);
// Perform same request, but this time allow grant.
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->submitForm([], 'Allow');
$this->getAndValidateCodeFromResponse();
// Set additional redirect uri on the client, and perform again request
// with redirect uri.
$this->client->set('redirect', [
'mobile://test',
$this->redirectUri,
]);
$this->client->save();
$valid_params['redirect_uri'] = $this->redirectUri;
// Adding additional scope, because the 'remember approval' is enabled.
$valid_params['scope'] .= " {$this->extraScope->getName()}";
$this->drupalGet($this->authorizeUrl->toString(), [
'query' => $valid_params,
]);
$this->submitForm([], 'Allow');
$this->getAndValidateCodeFromResponse();
}
/**
* Test registration with one time login.
*/
public function testRegistrationWithOneTimeLogin(): void {
// Allow registration with administrator approval.
$this->config('user.settings')->set('register', UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
$valid_params = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
// 1. Register user.
$destination_url = $this->authorizeUrl->setOption('query', $valid_params)->toString();
$this->drupalGet('user/register', [
'query' => [
'destination' => $destination_url,
],
]);
$edit['name'] = $this->randomMachineName();
$edit['mail'] = $edit['name'] . '@example.com';
$this->submitForm($edit, 'Create new account');
// 2. Approve user.
$this->container->get('entity_type.manager')->getStorage('user')->resetCache();
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
/** @var \Drupal\user\UserInterface[] $accounts */
$accounts = $user_storage->loadByProperties($edit);
$new_user = reset($accounts);
// Unblock user.
$new_user
->set('status', TRUE)
->save();
// 3. Login via the one time login.
$reset_url = user_pass_reset_url($new_user);
$this->drupalGet($reset_url);
$this->submitForm([], 'Log in');
// 4. After saving the user, authorization form will be available.
$this->submitForm([], 'Save');
$this->assertGrantForm();
}
/**
* Helper function to assert the current page is a valid grant form.
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
* @throws \Behat\Mink\Exception\ExpectationException
*/
protected function assertGrantForm(): void {
$assert_session = $this->assertSession();
$assert_session->statusCodeEquals(200);
$assert_session->titleEquals('Grant Access to Client | Drupal');
$assert_session->buttonExists('Allow');
$assert_session->buttonExists('Deny');
}
/**
* Get the code in the response after granting access to scopes.
*
* @return string
* The code.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
protected function getAndValidateCodeFromResponse(): string {
$query = $this->getQueryAndValidateRedirect();
$this->assertArrayHasKey('code', $query);
return $query['code'];
}
/**
* Get the parsed query and validate the redirect.
*
* @return array
* The parsed URL query.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
protected function getQueryAndValidateRedirect(): array {
$assert_session = $this->assertSession();
$session = $this->getSession();
$assert_session->statusCodeEquals(200);
$parsed_url = parse_url($session->getCurrentUrl());
$redirect_url = "{$parsed_url['scheme']}://{$parsed_url['host']}";
if (isset($parsed_url['port'])) {
$redirect_url .= ':' . $parsed_url['port'];
}
$redirect_url .= $parsed_url['path'];
$this->assertEquals($this->redirectUri, $redirect_url);
return Query::parse($parsed_url['query']);
}
/**
* Posts the code and requests access to the scopes.
*
* @param string $code
* The granted code.
* @param string $scopes
* The list of scopes to request access to.
* @param bool $send_secret
* Whether to send the client secret.
*
* @return \Psr\Http\Message\ResponseInterface
* The response.
*/
protected function postGrantedCodeWithScopes(string $code, string $scopes, bool $send_secret = TRUE): ResponseInterface {
$valid_payload = [
'grant_type' => 'authorization_code',
'client_id' => $this->client->getClientId(),
'code' => $code,
'scope' => $scopes,
'redirect_uri' => $this->redirectUri,
];
if ($send_secret) {
$valid_payload['client_secret'] = $this->clientSecret;
}
return $this->post($this->url, $valid_payload);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\simple_oauth\Functional;
use Drupal\Core\Url;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\ResponseInterface;
/**
* Request helper trait.
*/
trait RequestHelperTrait {
/**
* POST a request.
*
* The base methods do not provide a non-form submission POST method.
*
* @param \Drupal\Core\Url $url
* The URL.
* @param array $data
* The data to send.
* @param array $options
* Optional options to pass to client.
*
* @see https://www.drupal.org/project/drupal/issues/2908589#comment-12258839
*
* @return \Psr\Http\Message\ResponseInterface
* The response.
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function post(Url $url, array $data, array $options = []): ResponseInterface {
$post_url = $this->getAbsoluteUrl($url->toString());
$session = $this->getSession();
$session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
return $this->getHttpClient()->request('POST', $post_url, [
'form_params' => $data,
'http_errors' => FALSE,
] + $options);
}
/**
* GET a resource, with options.
*
* @param \Drupal\Core\Url $url
* The url object to perform get request on.
* @param array $options
* The request options.
*
* @return \Psr\Http\Message\ResponseInterface
* Returns the response.
*/
protected function get(Url $url, array $options = []): ResponseInterface {
$options += [
RequestOptions::HTTP_ERRORS => FALSE,
];
$session = $this->getSession();
$get_url = $this->getAbsoluteUrl($url->toString());
$session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
return $this->getHttpClient()->get($get_url, $options);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Drupal\Tests\simple_oauth\Functional;
/**
* Trait with methods needed by tests.
*/
trait SimpleOauthTestTrait {
/**
* The private key.
*
* @var string
*/
protected $privateKey = '-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvPQBbfIu1fZ9Oq/af+KAxnhMRi3BJA9qBqsXLtNUgtkf68wn
8z484j/yj9wLRP49b0K41yoExQ8KUD1D2mSh9C45GCmeBD4dM8KNMs2flSAXFgIV
twABuu+7k+75RIndJo33heADIYf6BKT1Q4nAgDi4pyfvDYjYp5iDyeLNcWiNUo/Y
Y4aKoDH36plUPA+kP1ekjCCPw7jsnV50zvCPbutvO7TZAEve/3SUIqxs0L6eG6Zv
PV2hWAqItXpXiy/WMbtkCjlwGTb60yKmjkAUNyAppSPnclH3h6HdtOzVjXfWkO9H
x3C4OAL7QET3/arRt1GDiWKwfc+Dv04lXDT0AwIDAQABAoIBAGOHgA1C6YrI2LQG
F2kPjVd93GeHCFqPSAEVNBP1O2nlJtxU4KJPIVDn8EP423LPHNszYRvtRS/ruToE
2235Xhm6E1b37QU9FrLCAxBEoY+ypJZyKLAJb9/hEYRd960zlWsOkthQ5DVQY9D4
dzzJHb4soo9iCJivgbfeLWU1c5QNXztUoHZA0zYHbVlfNCvD7cTGJpnnPdZJLd93
lZ1abkBAz6/WMavHnNNKBxPH/8hE8wLaaOZpwqce/RpcJlKM91db6OfrWnpj/Hrd
XJIKbQErrJTXOlBm27+9xoX+btg1GR9JowlUZ+BGoSmO+j0wqVWzHb+NSkbMPf10
uLyE8QkCgYEA7IDz5AYlHvafjwzWWYvU4WHl/wJ0ZbdJfBxT2QVxu37nOzGAJJck
EYIWtXPSOUE/eTJHzyBhycjkuQtt+/Rprxj+Sn4dpFpCDxKs+gNyI6A+MzdHdEYJ
YarBC2M43j8psgUiYkMpfoIgiZac/qprmgmB39u9tD+4vjZKdauw2B8CgYEAzIeS
NjXYTKaUIJYP0y1oN0eyoNfbs4h8fXRjAQUzEj1mSs0ureosTLF4lCnOKzkkVf+C
kGpTTZ6EDn7bXxsz6/2QvnubRwzIJx+kb0UkA64623vbKnL/xM7BMt/P1Avph82r
SS11XWesjOCRpYLGf+YE8rQJXdVf1Vr1CAJ2d50CgYBq4BtXAC/mPiz8yCBVdwtM
jqERDFrtXFao72Q0vnEW+dIkvcnavzJddxwsA5sMpJ+6dS5eO5P1TAOQW8noAhuA
NRs1LqjWjLMtfJMOqF/8GX4CRwjTUpMKv89dBgm85W5CNG/FV/R4ZvWtN5Lawsi9
Y259ax/fRKyHyKD9bAkOoQKBgQDJ76i6gVs4AtgJfF/PfuuAePeyuq0ei0lujDUb
0shj399ZR1ApQiXO6wJENyppnpdzmTyN3Yy1/CYiMbniIveWrtn0WBItij8r8Z/m
hHtUbveJsLXpKXXCGOjDlBqcH87I2JWfQJS6ThwdU7Q5l+7oZHDKOFtvG7bs7ksz
R0s0OQKBgH6NaykzVBPZfHrJRwdk7gfXiaLk7PfIIiiK/OyjVBbtYwaXQkUAaHEl
Oz9H1wukAZQtqf0LEGg0qIA0UuKvtvm9Iei0KpGrz21ExbPEyFhEgp5Utmw+Oon3
Rk3L4fDUGqyKyamiZNZSRnC6gZm87EWHQNFFqU0yZ6a/QKbpOB1W
-----END RSA PRIVATE KEY-----';
/**
* The public key.
*
* @var string
*/
protected $publicKey = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPQBbfIu1fZ9Oq/af+KA
xnhMRi3BJA9qBqsXLtNUgtkf68wn8z484j/yj9wLRP49b0K41yoExQ8KUD1D2mSh
9C45GCmeBD4dM8KNMs2flSAXFgIVtwABuu+7k+75RIndJo33heADIYf6BKT1Q4nA
gDi4pyfvDYjYp5iDyeLNcWiNUo/YY4aKoDH36plUPA+kP1ekjCCPw7jsnV50zvCP
butvO7TZAEve/3SUIqxs0L6eG6ZvPV2hWAqItXpXiy/WMbtkCjlwGTb60yKmjkAU
NyAppSPnclH3h6HdtOzVjXfWkO9Hx3C4OAL7QET3/arRt1GDiWKwfc+Dv04lXDT0
AwIDAQAB
-----END PUBLIC KEY-----';
/**
* Set up public and private keys.
*/
public function setUpKeys() {
$public_key_path = 'private://public.key';
$private_key_path = 'private://private.key';
file_put_contents($public_key_path, $this->publicKey);
file_put_contents($private_key_path, $this->privateKey);
chmod($public_key_path, 0660);
chmod($private_key_path, 0660);
$settings = $this->config('simple_oauth.settings');
$settings->set('public_key', $public_key_path);
$settings->set('private_key', $private_key_path);
$settings->save();
}
/**
* Base64 url encode.
*
* @param string $string
* The string to encode.
*
* @return string
* The encoded string.
*/
public static function base64urlencode(string $string): string {
$base64 = base64_encode($string);
$base64 = rtrim($base64, "=");
return strtr($base64, '+/', '-_');
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace Drupal\Tests\simple_oauth\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\consumers\Entity\Consumer;
use Drupal\Core\Url;
use Drupal\simple_oauth\Entity\Oauth2Scope;
use Drupal\simple_oauth\Oauth2ScopeInterface;
use Drupal\Tests\BrowserTestBase;
use Psr\Http\Message\ResponseInterface;
/**
* Class TokenBearerFunctionalTestBase.
*
* Base class that handles common logic and config for the token tests.
*
* @package Drupal\Tests\simple_oauth\Functional
*/
abstract class TokenBearerFunctionalTestBase extends BrowserTestBase {
use RequestHelperTrait;
use SimpleOauthTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'image',
'options',
'serialization',
'simple_oauth_test',
'text',
'user',
];
/**
* The URL.
*
* @var \Drupal\Core\Url
*/
protected Url $url;
/**
* The client.
*
* @var \Drupal\consumers\Entity\Consumer
*/
protected $client;
/**
* The user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* The client secret.
*
* @var string
*/
protected string $clientSecret;
/**
* The HTTP client to make requests.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* The request scope.
*
* @var string
*/
protected string $scope;
/**
* The redirect URI.
*
* @var string
*/
protected string $redirectUri;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->url = Url::fromRoute('oauth2_token.token');
// Set up a HTTP client that accepts relative URLs.
$this->httpClient = $this->container->get('http_client_factory')
->fromOptions(['base_uri' => $this->baseUrl]);
$this->clientSecret = $this->randomString();
$this->redirectUri = Url::fromRoute('oauth2_token.test_token', [], [
'absolute' => TRUE,
])->toString();
$this->user = $this->drupalCreateUser();
$this->setUpKeys();
$scope_1 = Oauth2Scope::create([
'name' => 'test:scope1',
'description' => 'Test scope 1 description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test scope 1 description authorization_code',
],
'client_credentials' => [
'status' => TRUE,
'description' => 'Test scope 1 description client_credentials',
],
],
'umbrella' => FALSE,
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'access content',
]);
$scope_2 = Oauth2Scope::create([
'name' => 'test:scope2',
'description' => 'Test scope 2 description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test scope 2 description authorization_code',
],
'client_credentials' => [
'status' => TRUE,
'description' => 'Test scope 2 description client_credentials',
],
],
'umbrella' => FALSE,
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'debug simple_oauth tokens',
]);
$scope_1->save();
$scope_2->save();
$this->client = Consumer::create([
'client_id' => $this->randomString(),
'label' => $this->getRandomGenerator()->name(),
'secret' => $this->clientSecret,
'grant_types' => [
'authorization_code',
'client_credentials',
'refresh_token',
],
'redirect' => [$this->redirectUri],
'scopes' => [$scope_1->id(), $scope_2->id()],
]);
$this->client->save();
$this->scope = "{$scope_1->getName()} {$scope_2->getName()}";
}
/**
* Validates a valid token response.
*
* @param \Psr\Http\Message\ResponseInterface $response
* The response object.
* @param bool $has_refresh
* TRUE if the response should return a refresh token. FALSE otherwise.
*
* @return array
* An array representing the response of "/oauth/token".
*/
protected function assertValidTokenResponse(ResponseInterface $response, bool $has_refresh = FALSE): array {
$this->assertEquals(200, $response->getStatusCode());
$parsed_response = Json::decode((string) $response->getBody());
$this->assertSame('Bearer', $parsed_response['token_type']);
$expiration = $this->client->get('access_token_expiration')->value;
$this->assertLessThanOrEqual($expiration, $parsed_response['expires_in']);
$this->assertGreaterThanOrEqual($expiration - 10, $parsed_response['expires_in']);
$this->assertNotEmpty($parsed_response['access_token']);
if ($has_refresh) {
$this->assertNotEmpty($parsed_response['refresh_token']);
}
else {
$this->assertFalse(isset($parsed_response['refresh_token']));
}
return $parsed_response;
}
/**
* Validates access token on test resource.
*
* @param string $access_token
* The access token.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
protected function assertAccessTokenOnResource(string $access_token): void {
$resource_path = Url::fromRoute('oauth2_resource.test')->toString();
$this->drupalGet($resource_path, [], [
'Authorization' => "Bearer {$access_token}",
]);
$this->assertSession()->statusCodeEquals(200);
}
}

View File

@@ -0,0 +1,226 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\simple_oauth\Functional\SimpleOauthTestTrait;
use Drupal\consumers\Entity\Consumer;
use Drupal\simple_oauth\Entity\Oauth2Scope;
use Drupal\simple_oauth\Oauth2ScopeInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class RequestBase.
*
* Base class that handles common logic and config for the authorized requests.
*
* @package Drupal\Tests\simple_oauth\Kernel
*/
abstract class AuthorizedRequestBase extends EntityKernelTestBase {
use SimpleOauthTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'consumers',
'file',
'image',
'options',
'serialization',
'system',
'simple_oauth',
'simple_oauth_test',
'user',
];
/**
* The user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* The redirect URI.
*
* @var string
*/
protected $redirectUri;
/**
* The request scope.
*
* @var string
*/
protected $scope;
/**
* The client.
*
* @var \Drupal\consumers\Entity\Consumer
*/
protected $client;
/**
* The client secret.
*
* @var string
*/
protected $clientSecret;
/**
* The URL.
*
* @var \Drupal\Core\Url
*/
protected $url;
/**
* The kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installEntitySchema('consumer');
$this->installEntitySchema('oauth2_token');
$this->installEntitySchema('user');
$this->installConfig(['user']);
$this->installConfig(['simple_oauth']);
mkdir($this->siteDirectory . '/keys', 0775);
$public_key_path = "{$this->siteDirectory}/keys/public.key";
$private_key_path = "{$this->siteDirectory}/keys/private.key";
file_put_contents($public_key_path, $this->publicKey);
file_put_contents($private_key_path, $this->privateKey);
chmod($public_key_path, 0660);
chmod($private_key_path, 0660);
$settings = $this->config('simple_oauth.settings');
$settings->set('public_key', $public_key_path);
$settings->set('private_key', $private_key_path);
$settings->save();
$this->user = $this->drupalCreateUser();
$this->redirectUri = Url::fromRoute('oauth2_token.test_token', [], [
'absolute' => TRUE,
])->toString();
$scope_1 = Oauth2Scope::create([
'name' => 'test:scope1',
'description' => 'Test scope 1 description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test scope 1 description authorization_code',
],
'client_credentials' => [
'status' => TRUE,
'description' => 'Test scope 1 description client_credentials',
],
],
'umbrella' => FALSE,
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'access content',
]);
$scope_2 = Oauth2Scope::create([
'name' => 'test:scope2',
'description' => 'Test scope 2 description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test scope 2 description authorization_code',
],
'client_credentials' => [
'status' => TRUE,
'description' => 'Test scope 2 description client_credentials',
],
],
'umbrella' => FALSE,
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'debug simple_oauth tokens',
]);
$scope_1->save();
$scope_2->save();
$this->scope = "{$scope_1->getName()} {$scope_2->getName()}";
$this->clientSecret = $this->randomString();
$this->client = Consumer::create([
'client_id' => 'test_client',
'label' => 'test',
'grant_types' => [
'authorization_code',
'client_credentials',
'refresh_token',
],
'scopes' => [$scope_1->id(), $scope_2->id()],
'secret' => $this->clientSecret,
'redirect' => [$this->redirectUri],
]);
$this->client->save();
$this->url = Url::fromRoute('oauth2_token.token');
$this->httpKernel = $this->container->get('http_kernel');
}
/**
* Validates a valid token response.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* The response object.
* @param bool $has_refresh
* TRUE if the response should return a refresh token. FALSE otherwise.
*
* @return array
* An array representing the response of "/oauth/token".
*/
protected function assertValidTokenResponse(Response $response, bool $has_refresh = FALSE): array {
$this->assertEquals(200, $response->getStatusCode());
$parsed_response = Json::decode((string) $response->getContent());
$this->assertSame('Bearer', $parsed_response['token_type']);
$expiration = $this->client->get('access_token_expiration')->value;
$this->assertLessThanOrEqual($expiration, $parsed_response['expires_in']);
$this->assertGreaterThanOrEqual($expiration - 10, $parsed_response['expires_in']);
$this->assertNotEmpty($parsed_response['access_token']);
if ($has_refresh) {
$this->assertNotEmpty($parsed_response['refresh_token']);
}
else {
$this->assertFalse(isset($parsed_response['refresh_token']));
}
return $parsed_response;
}
/**
* Validates access token on test resource.
*
* @param string $access_token
* The access token.
*
* @throws \Exception
*/
protected function assertAccessTokenOnResource(string $access_token): void {
$resource_path = Url::fromRoute('oauth2_resource.test')->toString();
$request = Request::create($resource_path);
$request->headers->add(['Authorization' => "Bearer {$access_token}"]);
$response = $this->httpKernel->handle($request);
$this->assertEquals(200, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Component\Serialization\Json;
use Symfony\Component\HttpFoundation\Request;
/**
* The client credentials test.
*
* @group simple_oauth
*/
class ClientCredentialsTest extends AuthorizedRequestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Client credentials need a valid default user set.
$this->client->set('user_id', $this->user)->save();
}
/**
* Ensure incorrectly-configured clients without a user are unusable.
*/
public function testMisconfiguredClient(): void {
$this->client->set('user_id', NULL)->save();
$request = Request::create($this->url->toString(), 'POST', [
'grant_type' => 'client_credentials',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
]);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->assertEquals(500, $response->getStatusCode());
$this->assertStringContainsString('Invalid default user for client.', $parsed_response['message']);
}
/**
* Test the valid ClientCredentials grant.
*/
public function testClientCredentialsGrant(): void {
// 1. Test the valid response.
$parameters = [
'grant_type' => 'client_credentials',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
];
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$this->assertValidTokenResponse($response);
// 2. Test default scopes on the client.
unset($parameters['scope']);
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = $this->assertValidTokenResponse($response);
$this->assertAccessTokenOnResource($parsed_response['access_token']);
}
/**
* Data provider for ::testMissingClientCredentialsGrant.
*/
public function missingClientCredentialsProvider(): array {
return [
'grant_type' => [
'grant_type',
'unsupported_grant_type',
400,
],
'client_id' => [
'client_id',
'invalid_request',
400,
],
'client_secret' => [
'client_secret',
'invalid_client',
401,
],
];
}
/**
* Test invalid ClientCredentials grant.
*
* @dataProvider missingClientCredentialsProvider
*/
public function testMissingClientCredentialsGrant(string $key, string $error, int $code): void {
$parameters = [
'grant_type' => 'client_credentials',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
];
unset($parameters[$key]);
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->assertSame($error, $parsed_response['error'], sprintf('Correct error code %s', $error));
$this->assertSame($code, $response->getStatusCode(), sprintf('Correct status code %d', $code));
}
/**
* Data provider for ::testInvalidClientCredentialsGrant.
*/
public function invalidClientCredentialsProvider(): array {
return [
'grant_type' => [
'grant_type',
'unsupported_grant_type',
400,
],
'client_id' => [
'client_id',
'invalid_client',
401,
],
'client_secret' => [
'client_secret',
'invalid_client',
401,
],
];
}
/**
* Test invalid ClientCredentials grant.
*
* @dataProvider invalidClientCredentialsProvider
*/
public function testInvalidClientCredentialsGrant(string $key, string $error, int $code): void {
$parameters = [
'grant_type' => 'client_credentials',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
];
$parameters[$key] = $this->randomString();
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->assertSame($error, $parsed_response['error'], sprintf('Correct error code %s', $error));
$this->assertSame($code, $response->getStatusCode(), sprintf('Correct status code %d', $code));
}
/**
* Data provider for ::testPublicClientCredentialsGrant.
*/
public function publicClientCredentialsGrantProvider(): array {
return [
'client_secret' => [
$this->randomString(),
'invalid_client',
401,
],
'empty_client_secret' => [
'',
'invalid_client',
401,
],
'no_client_secret' => [
'NULL',
'invalid_client',
401,
],
];
}
/**
* Test public client with ClientCredentials grant.
*
* The client credentials grant cannot be used with a public client.
*
* @dataProvider publicClientCredentialsGrantProvider
*/
public function testPublicClientCredentialsGrant(string $secret, string $error, int $code): void {
$this->client->set('secret', NULL)->save();
$parameters = [
'grant_type' => 'client_credentials',
'client_id' => $this->client->getClientId(),
'scope' => $this->scope,
];
if ($secret) {
$parameters['client_secret'] = $secret;
}
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->assertSame($code, $response->getStatusCode(), sprintf('Correct status code %d', $code));
$this->assertSame($error, $parsed_response['error'], sprintf('Correct error code %s', $error));
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\consumers\Entity\Consumer;
use Drupal\KernelTests\KernelTestBase;
use Drupal\simple_oauth\Entity\Oauth2Scope;
use Drupal\simple_oauth\Oauth2ScopeInterface;
/**
* Tests for consumer entity.
*
* @group simple_oauth
*/
class ConsumerEntityTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'consumers',
'file',
'image',
'options',
'serialization',
'system',
'simple_oauth',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installEntitySchema('consumer');
$this->installEntitySchema('oauth2_token');
$this->installEntitySchema('user');
$this->installConfig(['user']);
$this->installConfig(['simple_oauth']);
}
/**
* Tests create operation for consumer entity.
*/
public function testCreate(): void {
$scope = Oauth2Scope::create([
'name' => 'test:test',
'description' => $this->getRandomGenerator()->sentences(5),
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
],
'client_credentials' => [
'status' => TRUE,
],
],
'umbrella' => FALSE,
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'view own simple_oauth entities',
]);
$scope->save();
$values = [
'client_id' => 'test_client',
'label' => 'test',
'grant_types' => ['authorization_code', 'client_credentials'],
'scopes' => [$scope->id()],
'confidential' => TRUE,
'pkce' => TRUE,
'redirect' => [
'mobile://test.com',
'http://localhost',
],
'access_token_expiration' => 600,
'refresh_token_expiration' => 2419200,
'automatic_authorization' => TRUE,
'remember_approval' => FALSE,
];
$consumer = Consumer::create($values);
$consumer->save();
$this->assertEquals($values['client_id'], $consumer->getClientId());
$this->assertEquals($values['label'], $consumer->label());
foreach ($values['grant_types'] as $delta => $grant_type) {
$this->assertEquals($grant_type, $consumer->get('grant_types')->get($delta)->value);
}
foreach ($values['scopes'] as $delta => $scope) {
$this->assertEquals($scope, $consumer->get('scopes')->get($delta)->scope_id);
$this->assertInstanceOf(Oauth2ScopeInterface::class, $consumer->get('scopes')->get($delta)->getScope());
}
$this->assertEquals($values['confidential'], $consumer->get('confidential')->value);
$this->assertEquals($values['pkce'], $consumer->get('pkce')->value);
foreach ($values['redirect'] as $delta => $redirect) {
$this->assertEquals($redirect, $consumer->get('redirect')->get($delta)->value);
}
$this->assertEquals($values['access_token_expiration'], $consumer->get('access_token_expiration')->value);
$this->assertEquals($values['refresh_token_expiration'], $consumer->get('refresh_token_expiration')->value);
$this->assertEquals($values['automatic_authorization'], $consumer->get('automatic_authorization')->value);
$this->assertEquals($values['remember_approval'], $consumer->get('remember_approval')->value);
}
/**
* Test default values for the enriched BaseFields on the consumer entity.
*/
public function testDefaultValues(): void {
$consumer = Consumer::create([
'client_id' => 'test_client',
'label' => 'test client',
'grant_types' => ['authorization_code'],
'redirect' => [
'http://test',
],
]);
$consumer->save();
$this->assertEquals(300, $consumer->get('access_token_expiration')->value);
$this->assertEquals(1209600, $consumer->get('refresh_token_expiration')->value);
$this->assertEquals(FALSE, (bool) $consumer->get('automatic_authorization')->value);
$this->assertEquals(TRUE, (bool) $consumer->get('remember_approval')->value);
$this->assertEquals(TRUE, (bool) $consumer->get('confidential')->value);
$this->assertEquals(FALSE, (bool) $consumer->get('pkce')->value);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Core\Session\AccountInterface;
use Drupal\simple_oauth\Entity\Oauth2Scope;
use Drupal\simple_oauth\Oauth2ScopeInterface;
/**
* Tests Dynamic OAuth2 Scope provider.
*
* @group simple_oauth
*/
class DynamicScopeProviderTest extends Oauth2ScopeProviderTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
Oauth2Scope::create([
'name' => 'dynamic_scope',
'description' => 'Dynamic scope description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test authorization_code description',
],
],
'umbrella' => TRUE,
])->save();
Oauth2Scope::create([
'name' => 'dynamic_scope:child',
'description' => 'Dynamic scope child description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test authorization_code description',
],
],
'umbrella' => FALSE,
'parent' => 'dynamic_scope',
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'debug simple_oauth tokens',
])->save();
Oauth2Scope::create([
'name' => 'dynamic_scope:child:child',
'description' => 'Dynamic scope child:child description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test authorization_code description',
],
],
'umbrella' => FALSE,
'parent' => 'dynamic_scope_child',
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'access content',
])->save();
Oauth2Scope::create([
'name' => 'dynamic_scope:role',
'description' => 'Dynamic scope dynamic_scope:role description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test authorization_code description',
],
],
'umbrella' => FALSE,
'granularity' => Oauth2ScopeInterface::GRANULARITY_ROLE,
'role' => AccountInterface::AUTHENTICATED_ROLE,
])->save();
Oauth2Scope::create([
'name' => 'dynamic_scope:role:child',
'description' => 'Dynamic scope dynamic_scope:role:child description',
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => 'Test authorization_code description',
],
],
'umbrella' => FALSE,
'parent' => 'dynamic_scope_role',
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'debug simple_oauth tokens',
])->save();
}
/**
* Tests dynamic scope provider.
*/
public function testDynamicScopeProvider(): void {
$this->assertScopeProvider(
Oauth2Scope::class,
[
'dynamic_scope' => [
'name' => 'dynamic_scope',
'permissions' => [
'access content',
'debug simple_oauth tokens',
],
],
'dynamic_scope_child' => [
'name' => 'dynamic_scope:child',
'permissions' => [
'access content',
'debug simple_oauth tokens',
],
],
'dynamic_scope_child_child' => [
'name' => 'dynamic_scope:child:child',
'permissions' => [
'access content',
],
],
'dynamic_scope_role' => [
'name' => 'dynamic_scope:role',
'permissions' => [
'access content',
'debug simple_oauth tokens',
],
],
'dynamic_scope_role_child' => [
'name' => 'dynamic_scope:role:child',
'permissions' => [
'debug simple_oauth tokens',
],
],
]
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\simple_oauth\Entity\Oauth2Scope as Oauth2ScopeEntity;
/**
* Tests the OAuth2 scope reference field type with dynamic scopes.
*
* @group simple_oauth
*/
class DynamicScopeReferenceItemTest extends Oauth2ScopeReferenceItemTestBase {
/**
* Test reference with dynamic OAuth2 scopes.
*/
public function testDynamicOauth2ScopeReferenceItem(): void {
Oauth2ScopeEntity::create([
'name' => 'dynamic_scope',
])->save();
Oauth2ScopeEntity::create([
'name' => 'dynamic_scope:1',
])->save();
$this->assertOauth2ScopeReferenceItems([
'dynamic_scope',
'dynamic_scope_1',
]);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Core\Session\AccountInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\simple_oauth\Entity\Oauth2Scope;
use Drupal\simple_oauth\Oauth2ScopeInterface;
/**
* Tests for OAuth2 scope entity.
*
* @group simple_oauth
*/
class Oauth2ScopeEntityTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'serialization',
'system',
'simple_oauth',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('system', ['sequences']);
$this->installEntitySchema('oauth2_scope');
}
/**
* Tests create operations for OAuth2 scope entity with permission.
*/
public function testCreateScopePermission(): void {
$values = [
'name' => 'test:test',
'description' => $this->getRandomGenerator()->sentences(5),
'grant_types' => [
'authorization_code' => [
'status' => TRUE,
'description' => $this->getRandomGenerator()->sentences(5),
],
],
'umbrella' => FALSE,
'parent' => 'test_parent',
'granularity' => Oauth2ScopeInterface::GRANULARITY_PERMISSION,
'permission' => 'view own simple_oauth entities',
];
/** @var \Drupal\simple_oauth\Entity\Oauth2ScopeEntityInterface $scope */
$scope = Oauth2Scope::create($values);
$scope->save();
$this->assertEquals(Oauth2Scope::scopeToMachineName($values['name']), $scope->id());
$this->assertEquals($values['name'], $scope->getName());
$this->assertEquals($values['description'], $scope->getDescription());
$this->assertEquals($values['grant_types'], $scope->getGrantTypes());
$this->assertEquals($values['grant_types']['authorization_code']['description'], $scope->getGrantTypeDescription('authorization_code'));
$this->assertEquals($values['umbrella'], $scope->isUmbrella());
$this->assertEquals($values['parent'], $scope->getParent());
$this->assertEquals($values['granularity'], $scope->getGranularity());
$this->assertEquals($values['permission'], $scope->getPermission());
}
/**
* Tests create operations for OAuth2 scope entity with role.
*/
public function testCreateScopeRole(): void {
$values = [
'name' => 'test:test',
'description' => $this->getRandomGenerator()->sentences(5),
'grant_types' => [
'client_credentials' => [
'status' => TRUE,
'description' => $this->getRandomGenerator()->sentences(5),
],
],
'umbrella' => FALSE,
'parent' => 'test_parent',
'granularity' => Oauth2ScopeInterface::GRANULARITY_ROLE,
'role' => AccountInterface::AUTHENTICATED_ROLE,
];
/** @var \Drupal\simple_oauth\Entity\Oauth2ScopeEntityInterface $scope */
$scope = Oauth2Scope::create($values);
$scope->save();
$this->assertEquals(Oauth2Scope::scopeToMachineName($values['name']), $scope->id());
$this->assertEquals($values['name'], $scope->getName());
$this->assertEquals($values['description'], $scope->getDescription());
$this->assertEquals($values['grant_types'], $scope->getGrantTypes());
$this->assertEquals($values['grant_types']['client_credentials']['description'], $scope->getGrantTypeDescription('client_credentials'));
$this->assertEquals($values['umbrella'], $scope->isUmbrella());
$this->assertEquals($values['parent'], $scope->getParent());
$this->assertEquals($values['granularity'], $scope->getGranularity());
$this->assertEquals($values['role'], $scope->getRole());
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Core\Session\AccountInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\Role;
/**
* OAuth2 Scope provider Test base.
*
* @group simple_oauth
*/
class Oauth2ScopeProviderTestBase extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'consumers',
'image',
'options',
'serialization',
'system',
'simple_oauth',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('system', ['sequences']);
$this->installEntitySchema('consumer');
$this->installConfig(['simple_oauth']);
$this->installEntitySchema('user');
$this->installConfig(['user']);
$role = Role::load(AccountInterface::AUTHENTICATED_ROLE);
$role->grantPermission('access content')->save();
}
/**
* Assert the scope provider.
*
* @param string $expected_instance
* The expected scope instance.
* @param array $expected_scopes
* The expected scopes:
* [
* 'scope_id' => [
* 'name' => '',
* 'permissions' => []
* ]
* ].
*/
protected function assertScopeProvider(string $expected_instance, array $expected_scopes): void {
/** @var \Drupal\simple_oauth\Oauth2ScopeProvider $scope_provider */
$scope_provider = \Drupal::service('simple_oauth.oauth2_scope.provider');
// Test loading a single scope by id.
$expected_first_scope_id = key($expected_scopes);
$scope = $scope_provider->load($expected_first_scope_id);
$this->assertInstanceOf($expected_instance, $scope);
$this->assertEquals($expected_first_scope_id, $scope->id());
$expected_first_scope = reset($expected_scopes);
// Test loading a single scope by name.
$scope = $scope_provider->loadByName($expected_first_scope['name']);
$this->assertInstanceOf($expected_instance, $scope);
$this->assertEquals($expected_first_scope['name'], $scope->getName());
// Test loading all scopes.
$all_scopes = $scope_provider->loadMultiple();
$this->assertEquals(array_keys($expected_scopes), array_keys($all_scopes));
foreach ($all_scopes as $scope) {
$this->assertInstanceOf($expected_instance, $scope);
}
// Test load multiple specific scopes.
$expected_first_two_scopes = array_slice($expected_scopes, 0, 2, TRUE);
$expected_first_two_scope_ids = array_keys($expected_first_two_scopes);
$scopes = $scope_provider->loadMultiple($expected_first_two_scope_ids);
$this->assertCount(2, $scopes);
foreach ($scopes as $scope) {
$this->assertInstanceOf($expected_instance, $scope);
}
$this->assertArrayHasKey($expected_first_two_scope_ids[0], $scopes);
$this->assertArrayHasKey($expected_first_two_scope_ids[1], $scopes);
// Check if scope has permission.
foreach ($all_scopes as $scope_id => $scope) {
foreach ($expected_scopes[$scope_id]['permissions'] as $permission) {
$this->assertTrue($scope_provider->scopeHasPermission($permission, $scope));
}
}
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simple_oauth\Oauth2ScopeInterface;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
/**
* Test base class for OAuth2 scope reference field type.
*
* @group simple_oauth
*/
class Oauth2ScopeReferenceItemTestBase extends FieldKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'consumers',
'serialization',
'system',
'simple_oauth',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('oauth2_scope');
$this->installConfig(['simple_oauth']);
$this->installEntitySchema('user');
$this->installConfig(['user']);
FieldStorageConfig::create([
'field_name' => 'field_oauth2_scope_reference',
'entity_type' => 'entity_test',
'type' => 'oauth2_scope_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
])->save();
FieldConfig::create([
'entity_type' => 'entity_test',
'field_name' => 'field_oauth2_scope_reference',
'bundle' => 'entity_test',
])->save();
}
/**
* Assert assigning and loading scopes.
*
* @param array $scope_ids
* Array with scope ids.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
*/
protected function assertOauth2ScopeReferenceItems(array $scope_ids): void {
$entity = EntityTest::create([
'field_oauth2_scope_reference' => $scope_ids,
]);
$entity->save();
$entity = EntityTest::load($entity->id());
$this->assertInstanceOf(FieldItemListInterface::class, $entity->field_oauth2_scope_reference);
$this->assertInstanceOf(FieldItemInterface::class, $entity->field_oauth2_scope_reference[0]);
$this->assertFalse($entity->field_oauth2_scope_reference->isEmpty());
foreach ($scope_ids as $delta => $scope_id) {
$this->assertInstanceOf(Oauth2ScopeInterface::class, $entity->field_oauth2_scope_reference[$delta]->getScope());
$this->assertInstanceOf(Oauth2ScopeInterface::class, $entity->get('field_oauth2_scope_reference')->getScopes()[$delta]);
$this->assertEquals($scope_id, $entity->field_oauth2_scope_reference[$delta]->scope_id);
$this->assertEquals($scope_id, $entity->get('field_oauth2_scope_reference')->getScopes()[$delta]->id());
}
// Test all the possible ways of assigning a scope id.
$entity->field_oauth2_scope_reference = [['scope_id' => reset($scope_ids)]];
$this->assertEquals($scope_ids[0], $entity->field_oauth2_scope_reference->first()->scope_id);
$entity->set('field_oauth2_scope_reference', $scope_ids);
$this->assertEquals($scope_ids[0], $entity->get('field_oauth2_scope_reference')->first()->scope_id);
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\KernelTestBase;
use Drupal\simple_oauth\Entity\Oauth2Scope as Oauth2ScopeEntity;
/**
* Tests validation constraints for Oauth2ScopeReferenceValidator.
*
* @group simple_oauth
*/
class Oauth2ScopeReferenceValidatorTest extends KernelTestBase {
/**
* The typed data manager to use.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedData;
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'image',
'options',
'serialization',
'system',
'simple_oauth',
'simple_oauth_test',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('system', ['sequences']);
$this->installEntitySchema('oauth2_scope');
$this->installEntitySchema('entity_test');
$this->installConfig(['simple_oauth']);
$this->installEntitySchema('user');
$this->installConfig(['user']);
$this->typedData = $this->container->get('typed_data_manager');
Oauth2ScopeEntity::create([
'name' => 'dynamic_scope',
])->save();
Oauth2ScopeEntity::create([
'name' => 'dynamic_scope:child',
])->save();
}
/**
* Test reference to non-existing OAuth2 scope.
*/
public function testOauth2ScopeReferenceNonExisting(): void {
$entity = EntityTest::create();
$entity->save();
$definition = BaseFieldDefinition::create('oauth2_scope_reference')
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$scope_id = 'non_existing_scope';
$typed_data = $this->typedData->create($definition, [$scope_id]);
$violations = $typed_data->validate();
$violation = $violations[0];
$this->assertEquals(t("The referenced OAuth2 scope '%id' does not exist.", ['%id' => $scope_id]), $violation->getMessage(), 'The message for invalid value is correct.');
$this->assertEquals($typed_data, $violation->getRoot(), 'Violation root is correct.');
}
/**
* Test validation constraint.
*/
public function testValidation(): void {
$entity = EntityTest::create();
$entity->save();
$definition = BaseFieldDefinition::create('oauth2_scope_reference')
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$typed_data = $this->typedData->create($definition, [
'dynamic_scope',
'dynamic_scope_child',
]);
$violations = $typed_data->validate();
$this->assertEquals(0, $violations->count(), 'Validation passed for correct value.');
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace Drupal\Tests\simple_oauth\Kernel;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use GuzzleHttp\Psr7\Query;
use Symfony\Component\HttpFoundation\Request;
/**
* The refresh token tests.
*
* @group simple_oauth
*/
class RefreshTokenTest extends AuthorizedRequestBase {
/**
* The refresh token.
*
* @var string
*/
protected $refreshToken;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), [
'grant simple_oauth codes',
]);
$this->client->set('automatic_authorization', TRUE);
$this->client->save();
$current_user = $this->container->get('current_user');
$current_user->setAccount($this->user);
$authorize_url = Url::fromRoute('oauth2_token.authorize')->toString();
$parameters = [
'response_type' => 'code',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
$request = Request::create($authorize_url, 'GET', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_url = parse_url($response->headers->get('location'));
$parsed_query = Query::parse($parsed_url['query']);
$code = $parsed_query['code'];
$parameters = [
'grant_type' => 'authorization_code',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'code' => $code,
'scope' => $this->scope,
'redirect_uri' => $this->redirectUri,
];
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->refreshToken = $parsed_response['refresh_token'];
}
/**
* Test the valid Refresh grant.
*/
public function testRefreshGrant(): void {
// 1. Test the valid response.
$parameters = [
'grant_type' => 'refresh_token',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'refresh_token' => $this->refreshToken,
'scope' => $this->scope,
];
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$this->assertValidTokenResponse($response, TRUE);
// 2. Test the valid without scopes.
// We need to use the new refresh token, the old one is revoked.
$parsed_response = Json::decode((string) $response->getContent());
$parameters = [
'grant_type' => 'refresh_token',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'refresh_token' => $parsed_response['refresh_token'],
'scope' => $this->scope,
];
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$this->assertValidTokenResponse($response, TRUE);
// 3. Test that the token was revoked.
$parameters = [
'grant_type' => 'refresh_token',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'refresh_token' => $this->refreshToken,
];
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$this->assertEquals(401, $response->getStatusCode());
$parsed_response = Json::decode((string) $response->getContent());
$this->assertSame('invalid_request', $parsed_response['error']);
}
/**
* Data provider for ::testMissingRefreshGrant.
*/
public function missingRefreshGrantProvider(): array {
return [
'grant_type' => [
'grant_type',
'unsupported_grant_type',
400,
],
'client_id' => [
'client_id',
'invalid_request',
400,
],
'client_secret' => [
'client_secret',
'invalid_client',
401,
],
'refresh_token' => [
'refresh_token',
'invalid_request',
400,
],
];
}
/**
* Test invalid Refresh grant.
*
* @dataProvider missingRefreshGrantProvider
*/
public function testMissingRefreshGrant(string $key, string $error, int $code): void {
$parameters = [
'grant_type' => 'refresh_token',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'refresh_token' => $this->refreshToken,
'scope' => $this->scope,
];
unset($parameters[$key]);
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->assertEquals($error, $parsed_response['error'], sprintf('Correct error code %s', $error));
$this->assertEquals($code, $response->getStatusCode(), sprintf('Correct status code %d', $code));
}
/**
* Data provider for ::invalidRefreshProvider.
*/
public function invalidRefreshProvider(): array {
return [
'grant_type' => [
'grant_type',
'unsupported_grant_type',
400,
],
'client_id' => [
'client_id',
'invalid_client',
401,
],
'client_secret' => [
'client_secret',
'invalid_client',
401,
],
'refresh_token' => [
'refresh_token',
'invalid_request',
401,
],
];
}
/**
* Test invalid Refresh grant.
*
* @dataProvider invalidRefreshProvider
*/
public function testInvalidRefreshGrant(string $key, string $error, int $code): void {
$parameters = [
'grant_type' => 'refresh_token',
'client_id' => $this->client->getClientId(),
'client_secret' => $this->clientSecret,
'refresh_token' => $this->refreshToken,
'scope' => $this->scope,
];
$parameters[$key] = $this->randomString();
$request = Request::create($this->url->toString(), 'POST', $parameters);
$response = $this->httpKernel->handle($request);
$parsed_response = Json::decode((string) $response->getContent());
$this->assertEquals($error, $parsed_response['error'], sprintf('Correct error code %s', $error));
$this->assertEquals($code, $response->getStatusCode(), sprintf('Correct status code %d', $code));
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Drupal\Tests\simple_oauth\Unit\Authentication\Provider;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\simple_oauth\Authentication\Provider\SimpleOauthAuthenticationProvider;
use Drupal\simple_oauth\PageCache\DisallowSimpleOauthRequests;
use Drupal\simple_oauth\PageCache\SimpleOauthRequestPolicyInterface;
use Drupal\simple_oauth\Server\ResourceServerFactoryInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* @coversDefaultClass \Drupal\simple_oauth\Authentication\Provider\SimpleOauthAuthenticationProvider
* @group simple_oauth
*/
class SimpleOauthAuthenticationTest extends UnitTestCase {
use ProphecyTrait;
/**
* The authentication provider.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface
*/
protected AuthenticationProviderInterface $provider;
/**
* The OAuth page cache request policy.
*
* @var \Drupal\simple_oauth\PageCache\SimpleOauthRequestPolicyInterface
*/
protected SimpleOauthRequestPolicyInterface $oauthPageCacheRequestPolicy;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$resource_server_factory = $this->prophesize(ResourceServerFactoryInterface::class);
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
$this->oauthPageCacheRequestPolicy = new DisallowSimpleOauthRequests();
$http_message_factory = $this->prophesize(HttpMessageFactoryInterface::class);
$http_foundation_factory = $this->prophesize(HttpFoundationFactoryInterface::class);
$logger = $this->prophesize(LoggerChannelInterface::class);
$this->provider = new SimpleOauthAuthenticationProvider(
$resource_server_factory->reveal(),
$entity_type_manager->reveal(),
$this->oauthPageCacheRequestPolicy,
$http_message_factory->reveal(),
$http_foundation_factory->reveal(),
$logger->reveal()
);
}
/**
* @covers ::applies
*
* @dataProvider hasTokenValueProvider
*/
public function testHasTokenValue(?string $authorization, bool $has_token): void {
$request = new Request();
if ($authorization !== NULL) {
$request->headers->set('Authorization', $authorization);
}
$this->assertSame($has_token, $this->provider->applies($request));
$this->assertSame(
$has_token ? RequestPolicyInterface::DENY : NULL,
$this->oauthPageCacheRequestPolicy->check($request)
);
}
/**
* Data provider for ::testHasTokenValue.
*/
public function hasTokenValueProvider(): array {
$token = $this->getRandomGenerator()->name();
$data = [];
// 1. Authentication header.
$data[] = ['Bearer ' . $token, TRUE];
// 2. Authentication header. Trailing white spaces.
$data[] = [' Bearer ' . $token, TRUE];
// 3. Authentication header. No white spaces.
$data[] = ['Foo' . $token, FALSE];
// 4. Authentication header. Empty value.
$data[] = ['', FALSE];
// 5. Authentication header. Fail: no token.
$data[] = [NULL, FALSE];
return $data;
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Drupal\Tests\simple_oauth\Unit;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\consumers\Entity\Consumer;
use Drupal\simple_oauth\Entity\Oauth2Token;
use Drupal\simple_oauth\ExpiredCollector;
use Prophecy\PhpUnit\ProphecyTrait;
/**
* @coversDefaultClass \Drupal\simple_oauth\ExpiredCollector
* @group simple_oauth
*/
class EntityCollectorTest extends UnitTestCase {
use ProphecyTrait;
/**
* @covers ::collect
*/
public function testCollect() {
[$expired_collector, $query] = $this->buildProphecies();
$query->condition('expire', 42, '<')->shouldBeCalledTimes(1);
$this->assertEquals([1, 52], array_map(function ($entity) {
return $entity->id();
}, $expired_collector->collect()));
}
/**
* @covers ::collectForClient
*/
public function testCollectForClient() {
[$expired_collector, $query] = $this->buildProphecies();
$client = $this->prophesize(Consumer::class);
$client->id()->willReturn(35);
$query->condition('client', 35)->shouldBeCalledTimes(1);
$tokens = $expired_collector->collectForClient($client->reveal());
$this->assertEquals([1, 52], array_map(function ($entity) {
return $entity->id();
}, $tokens));
}
/**
* @covers ::collectForAccount
*/
public function testCollectForAccount() {
[$expired_collector, $token_query,,, $client_storage] = $this->buildProphecies();
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(22);
$token_query->condition('auth_user_id', 22)->shouldBeCalledTimes(1);
$token_query->condition('bundle', 'refresh_token', '!=')->shouldBeCalledTimes(1);
$client_storage->loadByProperties([
'user_id' => 22,
])->shouldBeCalledTimes(1);
$token_query->condition('client', 6)->shouldBeCalledTimes(1);
$tokens = $expired_collector->collectForAccount($account->reveal());
$this->assertEquals([1, 52], array_map(function ($entity) {
return $entity->id();
}, $tokens));
}
/**
* @covers ::collect
*/
public function testDeleteMultipleTokens() {
[$expired_collector,, $storage] = $this->buildProphecies();
$storage->delete(['foo'])->shouldBeCalledTimes(1);
$expired_collector->deleteMultipleTokens(['foo']);
}
/**
* Builds prophecies for the tests.
*
* @return \Prophecy\Prophecy\ProphecyInterface[]
* The prophecies.
*/
protected function buildProphecies() {
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
$token_storage = $this->prophesize(EntityStorageInterface::class);
$token_query = $this->prophesize(QueryInterface::class);
$token_query->accessCheck()->willReturn(TRUE);
$token_query->execute()->willReturn([1 => '1', 52 => '52']);
$token_storage->getQuery()->willReturn($token_query->reveal());
$token1 = $this->prophesize(Oauth2Token::class);
$token1->id()->willReturn(1);
$token52 = $this->prophesize(Oauth2Token::class);
$token52->id()->willReturn(52);
$token_storage->loadMultiple(['1', '52'])->willReturn([
1 => $token1->reveal(),
52 => $token52->reveal(),
]);
$client_storage = $this->prophesize(EntityStorageInterface::class);
$client_query = $this->prophesize(QueryInterface::class);
$client_query->accessCheck()->willReturn(TRUE);
$client_query->execute()->willReturn([6 => '6']);
$client_storage->getQuery()->willReturn($client_query->reveal());
$client6 = $this->prophesize(Consumer::class);
$client6->id()->willReturn(6);
$client_storage->loadByProperties([
'user_id' => 22,
])->willReturn([6 => $client6->reveal()]);
$entity_type_manager->getStorage('oauth2_token')->willReturn($token_storage->reveal());
$entity_type_manager->getStorage('consumer')->willReturn($client_storage->reveal());
$date_time = $this->prophesize(TimeInterface::class);
$date_time->getRequestTime()->willReturn(42);
$expired_collector = new ExpiredCollector($entity_type_manager->reveal(), $date_time->reveal());
return [
$expired_collector,
$token_query,
$token_storage,
$client_query,
$client_storage,
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Drupal\Tests\simple_oauth\Unit;
use Drupal\simple_oauth\Plugin\Validation\Constraint\Oauth2RedirectUri;
use Drupal\simple_oauth\Plugin\Validation\Constraint\Oauth2RedirectUriValidator;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* @coversDefaultClass \Drupal\simple_oauth\Plugin\Validation\Constraint\Oauth2RedirectUriValidator
* @group simple_oauth
*/
class Oauth2RedirectUriValidatorTest extends UnitTestCase {
/**
* @covers ::validate
* @dataProvider providerValidate
*/
public function testValidate($value, $valid) {
$constraint = new Oauth2RedirectUri();
$validator = new Oauth2RedirectUriValidator();
$context = $this->createMock(ExecutionContextInterface::class);
if ($valid) {
$context->expects($this->never())
->method('addViolation');
}
else {
$context->expects($this->once())
->method('addViolation');
}
$items = $this->createMock('Drupal\Core\Field\FieldItemListInterface');
$items->expects($this->once())
->method('getValue')
->willReturn([['value' => $value]]);
$validator->initialize($context);
$validator->validate($items, $constraint);
}
/**
* Data provider for ::testValidate.
*/
public function providerValidate(): array {
return [
['http://localhost', TRUE],
['https://test', TRUE],
['mobile://test', TRUE],
['http://127.0.0.1', TRUE],
['test.test//test', FALSE],
['test/test//test', FALSE],
['www.test.com', FALSE],
['test.com', FALSE],
];
}
}