-
-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #177 from Sammyjo20/feature/client-credentials-grant
Feature | OAuth2 Client Credentials Grant
- Loading branch information
Showing
11 changed files
with
555 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Saloon\Http\OAuth2; | ||
|
||
use Saloon\Enums\Method; | ||
use Saloon\Http\Request; | ||
use Saloon\Contracts\Body\HasBody; | ||
use Saloon\Traits\Body\HasFormBody; | ||
use Saloon\Helpers\OAuth2\OAuthConfig; | ||
use Saloon\Traits\Plugins\AcceptsJson; | ||
|
||
class GetClientCredentialsTokenRequest extends Request implements HasBody | ||
{ | ||
use HasFormBody; | ||
use AcceptsJson; | ||
|
||
/** | ||
* Define the method that the request will use. | ||
* | ||
* @var \Saloon\Enums\Method | ||
*/ | ||
protected Method $method = Method::POST; | ||
|
||
/** | ||
* Define the endpoint for the request. | ||
* | ||
* @return string | ||
*/ | ||
public function resolveEndpoint(): string | ||
{ | ||
return $this->oauthConfig->getTokenEndpoint(); | ||
} | ||
|
||
/** | ||
* Requires the authorization code and OAuth 2 config. | ||
* | ||
* @param \Saloon\Helpers\OAuth2\OAuthConfig $oauthConfig | ||
* @param array<string> $scopes | ||
* @param string $scopeSeparator | ||
*/ | ||
public function __construct(protected OAuthConfig $oauthConfig, protected array $scopes = [], protected string $scopeSeparator = ' ') | ||
{ | ||
// | ||
} | ||
|
||
/** | ||
* Register the default data. | ||
* | ||
* @return array{ | ||
* grant_type: string, | ||
* client_id: string, | ||
* client_secret: string, | ||
* scope: string, | ||
* } | ||
*/ | ||
public function defaultBody(): array | ||
{ | ||
return [ | ||
'grant_type' => 'client_credentials', | ||
'client_id' => $this->oauthConfig->getClientId(), | ||
'client_secret' => $this->oauthConfig->getClientSecret(), | ||
'scope' => implode($this->scopeSeparator, array_merge($this->oauthConfig->getDefaultScopes(), $this->scopes)), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Saloon\Traits\OAuth2; | ||
|
||
use DateTimeImmutable; | ||
use Saloon\Helpers\Date; | ||
use Saloon\Contracts\Response; | ||
use Saloon\Contracts\OAuthAuthenticator; | ||
use Saloon\Http\Auth\AccessTokenAuthenticator; | ||
use Saloon\Http\OAuth2\GetClientCredentialsTokenRequest; | ||
|
||
trait ClientCredentialsGrant | ||
{ | ||
use HasOAuthConfig; | ||
|
||
/** | ||
* Get the access token | ||
* | ||
* @template TRequest of \Saloon\Contracts\Request | ||
* | ||
* @param array<string> $scopes | ||
* @param string $scopeSeparator | ||
* @param bool $returnResponse | ||
* @param callable(TRequest): (void)|null $requestModifier | ||
* @return \Saloon\Contracts\OAuthAuthenticator|\Saloon\Contracts\Response | ||
* @throws \ReflectionException | ||
* @throws \Saloon\Exceptions\InvalidResponseClassException | ||
* @throws \Saloon\Exceptions\OAuthConfigValidationException | ||
* @throws \Saloon\Exceptions\PendingRequestException | ||
*/ | ||
public function getAccessToken(array $scopes = [], string $scopeSeparator = ' ', bool $returnResponse = false, ?callable $requestModifier = null): OAuthAuthenticator|Response | ||
{ | ||
$this->oauthConfig()->validate(withRedirectUrl: false); | ||
|
||
$request = new GetClientCredentialsTokenRequest($this->oauthConfig(), $scopes, $scopeSeparator); | ||
|
||
$request = $this->oauthConfig()->invokeRequestModifier($request); | ||
|
||
if (is_callable($requestModifier)) { | ||
$requestModifier($request); | ||
} | ||
|
||
$response = $this->send($request); | ||
|
||
if ($returnResponse === true) { | ||
return $response; | ||
} | ||
|
||
$response->throw(); | ||
|
||
return $this->createOAuthAuthenticatorFromResponse($response); | ||
} | ||
|
||
/** | ||
* Create the OAuthAuthenticator from a response. | ||
* | ||
* @param \Saloon\Contracts\Response $response | ||
* @return \Saloon\Contracts\OAuthAuthenticator | ||
*/ | ||
protected function createOAuthAuthenticatorFromResponse(Response $response): OAuthAuthenticator | ||
{ | ||
$responseData = $response->object(); | ||
|
||
$accessToken = $responseData->access_token; | ||
$expiresAt = isset($responseData->expires_in) ? Date::now()->addSeconds($responseData->expires_in)->toDateTime() : null; | ||
|
||
return $this->createOAuthAuthenticator($accessToken, $expiresAt); | ||
} | ||
|
||
/** | ||
* Create the authenticator. | ||
* | ||
* @param string $accessToken | ||
* @param DateTimeImmutable|null $expiresAt | ||
* @return OAuthAuthenticator | ||
*/ | ||
protected function createOAuthAuthenticator(string $accessToken, ?DateTimeImmutable $expiresAt = null): OAuthAuthenticator | ||
{ | ||
return new AccessTokenAuthenticator($accessToken, null, $expiresAt); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Saloon\Traits\OAuth2; | ||
|
||
use Saloon\Helpers\OAuth2\OAuthConfig; | ||
|
||
trait HasOAuthConfig | ||
{ | ||
/** | ||
* The OAuth2 Config | ||
* | ||
* @var \Saloon\Helpers\OAuth2\OAuthConfig | ||
*/ | ||
protected OAuthConfig $oauthConfig; | ||
|
||
/** | ||
* Manage the OAuth2 config | ||
* | ||
* @return \Saloon\Helpers\OAuth2\OAuthConfig | ||
*/ | ||
public function oauthConfig(): OAuthConfig | ||
{ | ||
return $this->oauthConfig ??= $this->defaultOauthConfig(); | ||
} | ||
|
||
/** | ||
* Define the default Oauth 2 Config. | ||
* | ||
* @return \Saloon\Helpers\OAuth2\OAuthConfig | ||
*/ | ||
protected function defaultOauthConfig(): OAuthConfig | ||
{ | ||
return OAuthConfig::make(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Saloon\Http\Faking\MockResponse; | ||
use Psr\Http\Message\RequestInterface; | ||
use GuzzleHttp\Promise\FulfilledPromise; | ||
use Saloon\Tests\Fixtures\Requests\UserRequest; | ||
use Saloon\Tests\Fixtures\Connectors\TestConnector; | ||
|
||
test('default guzzle config options are sent', function () { | ||
$connector = new TestConnector; | ||
|
||
$connector->sender()->addMiddleware(function (callable $handler) { | ||
return function (RequestInterface $guzzleRequest, array $options) { | ||
expect($options)->toHaveKey('http_errors', true); | ||
expect($options)->toHaveKey('connect_timeout', 10); | ||
expect($options)->toHaveKey('timeout', 30); | ||
|
||
return new FulfilledPromise(MockResponse::make()->getPsrResponse()); | ||
}; | ||
}); | ||
|
||
$connector->send(new UserRequest); | ||
}); | ||
|
||
test('you can pass additional guzzle config options and they are merged from the connector and request', function () { | ||
$connector = new TestConnector(); | ||
|
||
$connector->config()->add('debug', true); | ||
|
||
$connector->sender()->addMiddleware(function (callable $handler) { | ||
return function (RequestInterface $guzzleRequest, array $options) { | ||
expect($options)->toHaveKey('http_errors', true); | ||
expect($options)->toHaveKey('connect_timeout', 10); | ||
expect($options)->toHaveKey('timeout', 30); | ||
expect($options)->toHaveKey('debug', true); | ||
expect($options)->toHaveKey('verify', false); | ||
|
||
return new FulfilledPromise(MockResponse::make()->getPsrResponse()); | ||
}; | ||
}); | ||
|
||
$request = new UserRequest; | ||
|
||
$request->config()->add('verify', false); | ||
|
||
$connector->send($request); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.