Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface HeaderCredentialsInterface extends AuthCredentialsInterface
{
public function getKeyName(): string;
public function getApiKey(): ?string;
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface ParameterCredentialsInterface extends AuthCredentialsInterface
{
public function getKeyName(): string;
public function getApiKey(): ?string;
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\ApiKey;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\HeaderCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
use Mautic\IntegrationsBundle\Exception\InvalidCredentialsException;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
/**
* Factory for building HTTP clients using basic auth.
*/
class HttpFactory implements AuthProviderInterface
{
public const NAME = 'api_key';
/**
* Cache of initialized clients.
*
* @var Client[]
*/
private array $initializedClients = [];
private HeaderCredentialsInterface|ParameterCredentialsInterface|null $credentials = null;
public function getAuthType(): string
{
return self::NAME;
}
/**
* @param HeaderCredentialsInterface|ParameterCredentialsInterface $credentials
*
* @throws PluginNotConfiguredException
* @throws InvalidCredentialsException
*/
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
{
if (!$this->credentialsAreValid($credentials)) {
throw new InvalidCredentialsException(sprintf('Credentials must implement either the %s or %s interfaces', HeaderCredentialsInterface::class, ParameterCredentialsInterface::class));
}
if (!$this->credentialsAreConfigured($credentials)) {
throw new PluginNotConfiguredException('API key is missing');
}
// Return cached initialized client if there is one.
if (!empty($this->initializedClients[$credentials->getKeyName()])) {
return $this->initializedClients[$credentials->getKeyName()];
}
$this->credentials = $credentials;
if ($credentials instanceof HeaderCredentialsInterface) {
$this->initializedClients[$credentials->getKeyName()] = $this->getHeaderClient();
return $this->initializedClients[$credentials->getKeyName()];
}
$this->initializedClients[$credentials->getKeyName()] = $this->getParameterClient();
return $this->initializedClients[$credentials->getKeyName()];
}
private function credentialsAreValid(AuthCredentialsInterface $credentials): bool
{
return $credentials instanceof HeaderCredentialsInterface || $credentials instanceof ParameterCredentialsInterface;
}
private function credentialsAreConfigured(AuthCredentialsInterface $credentials): bool
{
return !empty($credentials->getApiKey());
}
private function getHeaderClient(): ClientInterface
{
return new Client(
[
'headers' => [$this->credentials->getKeyName() => $this->credentials->getApiKey()],
]
);
}
private function getParameterClient(): ClientInterface
{
$handler = new HandlerStack();
$handler->setHandler(new CurlHandler());
$handler->unshift(
Middleware::mapRequest(
fn (Request $request) => $request->withUri(
Uri::withQueryValue($request->getUri(), $this->credentials->getKeyName(), $this->credentials->getApiKey())
)
)
);
return new Client(
[
'handler' => $handler,
]
);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider;
interface AuthConfigInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider;
interface AuthCredentialsInterface
{
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider;
use GuzzleHttp\ClientInterface;
interface AuthProviderInterface
{
public function getAuthType(): string;
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface;
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\BasicAuth;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface CredentialsInterface extends AuthCredentialsInterface
{
public function getUsername(): ?string;
public function getPassword(): ?string;
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\BasicAuth;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
/**
* Factory for building HTTP clients using basic auth.
*/
class HttpFactory implements AuthProviderInterface
{
public const NAME = 'basic_auth';
/**
* Cache of initialized clients.
*
* @var Client[]
*/
private array $initializedClients = [];
public function getAuthType(): string
{
return self::NAME;
}
/**
* @throws PluginNotConfiguredException
*/
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
{
if (!$this->credentialsAreConfigured($credentials)) {
throw new PluginNotConfiguredException('Username and/or password is missing');
}
// Return cached initialized client if there is one.
if (!empty($this->initializedClients[$credentials->getUsername()])) {
return $this->initializedClients[$credentials->getUsername()];
}
$this->initializedClients[$credentials->getUsername()] = new Client(
[
'auth' => [
$credentials->getUsername(),
$credentials->getPassword(),
],
]
);
return $this->initializedClients[$credentials->getUsername()];
}
protected function credentialsAreConfigured(CredentialsInterface $credentials): bool
{
return $credentials->getUsername() && $credentials->getPassword();
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth1aThreeLegged;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface CredentialsInterface extends AuthCredentialsInterface
{
public function getAuthorizationUrl(): string;
public function getRequestTokenUrl(): string;
public function getAccessTokenUrl(): string;
public function getAuthCallbackUrl(): ?string;
public function getConsumerId(): ?string;
public function getConsumerSecret(): ?string;
public function getAccessToken(): ?string;
public function getRequestToken(): ?string;
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface CredentialsInterface extends AuthCredentialsInterface
{
public function getAuthUrl(): string;
public function getConsumerKey(): ?string;
public function getConsumerSecret(): ?string;
public function getToken(): ?string;
public function getTokenSecret(): ?string;
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
/**
* Factory for building HTTP clients that will sign the requests with Oauth1a headers.
*/
class HttpFactory implements AuthProviderInterface
{
public const NAME = 'oauth1a_two_legged';
/**
* Cache of initialized clients.
*
* @var Client[]
*/
private array $initializedClients = [];
public function getAuthType(): string
{
return self::NAME;
}
/**
* @throws PluginNotConfiguredException
*/
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
{
// Return cached initialized client if there is one.
if (!empty($this->initializedClients[$credentials->getConsumerKey()])) {
return $this->initializedClients[$credentials->getConsumerKey()];
}
if (!$this->credentialsAreConfigured($credentials)) {
throw new PluginNotConfiguredException('Oauth1a Credentials or URL is missing');
}
$this->initializedClients[$credentials->getConsumerKey()] = $this->buildClient($credentials);
return $this->initializedClients[$credentials->getConsumerKey()];
}
private function buildClient(CredentialsInterface $credentials): Client
{
$stack = HandlerStack::create();
$stack->push($this->createOauth1($credentials));
return new Client(
[
'handler' => $stack,
'base_uri' => $credentials->getAuthUrl(),
'auth' => 'oauth',
]
);
}
private function createOauth1(CredentialsInterface $credentials): Oauth1
{
$config = [
'consumer_key' => $credentials->getConsumerKey(),
'consumer_secret' => $credentials->getConsumerSecret(),
];
if ($credentials->getToken() && $credentials->getTokenSecret()) {
$config['token'] = $credentials->getToken();
$config['token_secret'] = $credentials->getTokenSecret();
}
return new Oauth1($config);
}
private function credentialsAreConfigured(CredentialsInterface $credentials): bool
{
return !empty($credentials->getAuthUrl()) && !empty($credentials->getConsumerKey()) && !empty($credentials->getConsumerSecret());
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface AccessTokenInterface extends AuthCredentialsInterface
{
public function getAccessToken(): ?string;
public function getAccessTokenExpiry(): ?\DateTimeImmutable;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
interface CodeInterface
{
public function getCode(): ?string;
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface CredentialsInterface extends AuthCredentialsInterface
{
public function getAuthorizationUrl(): string;
public function getTokenUrl(): string;
public function getClientId(): ?string;
public function getClientSecret(): ?string;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
interface RedirectUriInterface
{
public function getRedirectUri(): string;
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface RefreshTokenInterface extends AuthCredentialsInterface
{
public function getRefreshToken(): ?string;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
interface ScopeInterface
{
public function getScope(): ?string;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
interface StateInterface
{
public function getState(): ?string;
}

View File

@@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use kamermans\OAuth2\GrantType\AuthorizationCode;
use kamermans\OAuth2\GrantType\RefreshToken;
use kamermans\OAuth2\OAuth2Middleware;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CodeInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\RedirectUriInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\ScopeInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigCredentialsSignerInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenPersistenceInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenSignerInterface;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
/**
* Factory for building HTTP clients that will sign the requests with Oauth2 headers.
* Based on Guzzle OAuth 2.0 Subscriber - kamermans/guzzle-oauth2-subscriber package.
*
* @see https://github.com/kamermans/guzzle-oauth2-subscriber
*/
class HttpFactory implements AuthProviderInterface
{
public const NAME = 'oauth2_three_legged';
private ?AuthCredentialsInterface $credentials = null;
private ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface|null $config = null;
private ?Client $reAuthClient = null;
/**
* Cache of initialized clients.
*
* @var Client[]
*/
private array $initializedClients = [];
public function getAuthType(): string
{
return self::NAME;
}
/**
* @param ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface $config
*
* @throws PluginNotConfiguredException
*/
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
{
if (!$this->credentialsAreConfigured($credentials)) {
throw new PluginNotConfiguredException('Missing credentials');
}
// Return cached initialized client if there is one.
if (isset($this->initializedClients[$credentials->getClientId()])) {
return $this->initializedClients[$credentials->getClientId()];
}
$this->credentials = $credentials;
$this->config = $config;
$options = [
'handler' => $this->getStackHandler(),
'auth' => 'oauth',
];
// Set up base URI if it's configured.
if (method_exists($credentials, 'getBaseUri') && ($baseUri = $credentials->getBaseUri()) !== null) {
$options['base_uri'] = $baseUri;
}
$this->initializedClients[$credentials->getClientId()] = new Client($options);
return $this->initializedClients[$credentials->getClientId()];
}
protected function credentialsAreConfigured(CredentialsInterface $credentials): bool
{
if (empty($credentials->getAuthorizationUrl())) {
return false;
}
if (empty($credentials->getTokenUrl())) {
return false;
}
if (empty($credentials->getClientId())) {
return false;
}
if (empty($credentials->getClientSecret())) {
return false;
}
return true;
}
private function getStackHandler(): HandlerStack
{
$reAuthConfig = $this->getReAuthConfig();
$grantType = new AuthorizationCode($this->getReAuthClient(), $reAuthConfig);
$refreshTokenGrantType = new RefreshToken($this->getReAuthClient(), $reAuthConfig);
$middleware = new OAuth2Middleware($grantType, $refreshTokenGrantType);
$this->configureMiddleware($middleware);
$stack = HandlerStack::create();
$stack->push($middleware);
return $stack;
}
private function getReAuthClient(): ClientInterface
{
if ($this->reAuthClient) {
return $this->reAuthClient;
}
$this->reAuthClient = new Client([
'base_uri' => $this->credentials->getTokenUrl(),
]);
return $this->reAuthClient;
}
private function getReAuthConfig(): array
{
$config = [
'client_id' => $this->credentials->getClientId(),
'client_secret' => $this->credentials->getClientSecret(),
'code' => '',
];
if ($this->credentials instanceof ScopeInterface) {
$config['scope'] = $this->credentials->getScope();
}
if ($this->credentials instanceof RedirectUriInterface) {
$config['redirect_uri'] = $this->credentials->getRedirectUri();
}
if ($this->credentials instanceof CodeInterface) {
$config['code'] = $this->credentials->getCode();
}
return $config;
}
private function configureMiddleware(OAuth2Middleware $oauth): void
{
if (!$this->config) {
return;
}
if ($this->config instanceof ConfigCredentialsSignerInterface) {
$oauth->setClientCredentialsSigner($this->config->getCredentialsSigner());
}
if ($this->config instanceof ConfigTokenPersistenceInterface) {
$oauth->setTokenPersistence($this->config->getTokenPersistence());
}
if ($this->config instanceof ConfigTokenSignerInterface) {
$oauth->setAccessTokenSigner($this->config->getTokenSigner());
}
if ($this->config instanceof ConfigTokenFactoryInterface) {
$oauth->setTokenFactory($this->config->getTokenFactory());
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface ClientCredentialsGrantInterface extends AuthCredentialsInterface
{
public function getAuthorizationUrl(): string;
public function getClientId(): ?string;
public function getClientSecret(): ?string;
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
interface PasswordCredentialsGrantInterface extends AuthCredentialsInterface
{
public function getAuthorizationUrl(): string;
public function getClientId(): ?string;
public function getClientSecret(): ?string;
public function getUsername(): ?string;
public function getPassword(): ?string;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
interface ScopeInterface
{
public function getScope(): ?string;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
interface StateInterface
{
public function getState(): ?string;
}

View File

@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\GrantType\GrantTypeInterface;
use kamermans\OAuth2\GrantType\PasswordCredentials;
use kamermans\OAuth2\GrantType\RefreshToken;
use kamermans\OAuth2\OAuth2Middleware;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\ClientCredentialsGrantInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\PasswordCredentialsGrantInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\ScopeInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\StateInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigCredentialsSignerInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenPersistenceInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenSignerInterface;
use Mautic\IntegrationsBundle\Exception\InvalidCredentialsException;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
/**
* Factory for building HTTP clients that will sign the requests with Oauth2 headers.
* Based on Guzzle OAuth 2.0 Subscriber - kamermans/guzzle-oauth2-subscriber package.
*
* @see https://github.com/kamermans/guzzle-oauth2-subscriber
*/
class HttpFactory implements AuthProviderInterface
{
public const NAME = 'oauth2_two_legged';
private PasswordCredentialsGrantInterface|ClientCredentialsGrantInterface|null $credentials = null;
private ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface|ConfigTokenFactoryInterface|null $config = null;
private ?Client $reAuthClient = null;
/**
* Cache of initialized clients.
*
* @var Client[]
*/
private array $initializedClients = [];
public function getAuthType(): string
{
return self::NAME;
}
/**
* @param PasswordCredentialsGrantInterface|ClientCredentialsGrantInterface $credentials
* @param ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface|ConfigTokenFactoryInterface $config
*
* @throws PluginNotConfiguredException
* @throws InvalidCredentialsException
*/
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
{
if (!$this->credentialsAreValid($credentials)) {
throw new InvalidCredentialsException(sprintf('Credentials must implement either the %s or %s interfaces', PasswordCredentialsGrantInterface::class, ClientCredentialsGrantInterface::class));
}
if (!$this->credentialsAreConfigured($credentials)) {
throw new PluginNotConfiguredException('Authorization URL, client ID or client secret is missing');
}
// Return cached initialized client if there is one.
if (!empty($this->initializedClients[$credentials->getClientId()])) {
return $this->initializedClients[$credentials->getClientId()];
}
$this->credentials = $credentials;
$this->config = $config;
$this->initializedClients[$credentials->getClientId()] = new Client(
[
'handler' => $this->getStackHandler(),
'auth' => 'oauth',
]
);
return $this->initializedClients[$credentials->getClientId()];
}
private function credentialsAreValid(AuthCredentialsInterface $credentials): bool
{
return $credentials instanceof PasswordCredentialsGrantInterface || $credentials instanceof ClientCredentialsGrantInterface;
}
private function credentialsAreConfigured(AuthCredentialsInterface $credentials): bool
{
if (empty($credentials->getAuthorizationUrl()) || empty($credentials->getClientId()) || empty($credentials->getClientSecret())) {
return false;
}
if ($credentials instanceof PasswordCredentialsGrantInterface && (empty($credentials->getUsername()) || empty($credentials->getPassword()))) {
return false;
}
return true;
}
private function getStackHandler(): HandlerStack
{
$reAuthConfig = $this->getReAuthConfig();
$accessTokenGrantType = $this->getGrantType($reAuthConfig);
$refreshTokenGrantType = new RefreshToken($this->getReAuthClient(), $reAuthConfig);
$middleware = new OAuth2Middleware($accessTokenGrantType, $refreshTokenGrantType);
$this->configureMiddleware($middleware);
$stack = HandlerStack::create();
$stack->push($middleware);
return $stack;
}
private function getReAuthClient(): ClientInterface
{
if ($this->reAuthClient) {
return $this->reAuthClient;
}
$this->reAuthClient = new Client(
[
'base_uri' => $this->credentials->getAuthorizationUrl(),
]
);
return $this->reAuthClient;
}
private function getReAuthConfig(): array
{
$config = [
'client_id' => $this->credentials->getClientId(),
'client_secret' => $this->credentials->getClientSecret(),
];
if ($this->credentials instanceof ScopeInterface) {
$config['scope'] = $this->credentials->getScope();
}
if ($this->credentials instanceof StateInterface) {
$config['state'] = $this->credentials->getState();
}
if ($this->credentials instanceof ClientCredentialsGrantInterface) {
return $config;
}
$config['username'] = $this->credentials->getUsername();
$config['password'] = $this->credentials->getPassword();
return $config;
}
private function getGrantType(array $config): GrantTypeInterface
{
if ($this->credentials instanceof ClientCredentialsGrantInterface) {
return new ClientCredentials($this->getReAuthClient(), $config);
}
return new PasswordCredentials($this->getReAuthClient(), $config);
}
private function configureMiddleware(OAuth2Middleware $oauth): void
{
if (!$this->config) {
return;
}
if ($this->config instanceof ConfigCredentialsSignerInterface) {
$oauth->setClientCredentialsSigner($this->config->getCredentialsSigner());
}
if ($this->config instanceof ConfigTokenPersistenceInterface) {
$oauth->setTokenPersistence($this->config->getTokenPersistence());
}
if ($this->config instanceof ConfigTokenSignerInterface) {
$oauth->setAccessTokenSigner($this->config->getTokenSigner());
}
if ($this->config instanceof ConfigTokenFactoryInterface) {
$oauth->setTokenFactory($this->config->getTokenFactory());
}
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
interface ConfigCredentialsSignerInterface extends AuthConfigInterface
{
public function getCredentialsSigner(): SignerInterface;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenFactoryInterface;
interface ConfigTokenFactoryInterface extends AuthConfigInterface
{
public function getTokenFactory(): TokenFactoryInterface;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
use kamermans\OAuth2\Persistence\TokenPersistenceInterface as KamermansTokenPersistenceInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
interface ConfigTokenPersistenceInterface extends AuthConfigInterface
{
public function getTokenPersistence(): KamermansTokenPersistenceInterface;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
use kamermans\OAuth2\Signer\AccessToken\SignerInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
interface ConfigTokenSignerInterface extends AuthConfigInterface
{
public function getTokenSigner(): SignerInterface;
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
use kamermans\OAuth2\Token\TokenInterface;
use kamermans\OAuth2\Token\TokenSerializer;
class IntegrationToken implements TokenInterface
{
// Pull in serialize() and unserialize() methods
use TokenSerializer;
/**
* @param mixed[] $extraData
*/
public function __construct(
?string $accessToken,
?string $refreshToken,
$expiresAt = null,
private array $extraData = [],
) {
$this->accessToken = (string) $accessToken;
$this->refreshToken = (string) $refreshToken;
$this->expiresAt = (int) $expiresAt;
}
/**
* @return string The access token
*/
public function getAccessToken(): string
{
return $this->accessToken;
}
/**
* @return string The refresh token
*/
public function getRefreshToken(): string
{
return $this->refreshToken;
}
/**
* @return int The expiration timestamp
*/
public function getExpiresAt(): int
{
return $this->expiresAt;
}
public function isExpired(): bool
{
// Consider expired if there is not an access token
if (!$this->getAccessToken()) {
return true;
}
// Otherwise, consider expired if the expiration time has passed
return $this->expiresAt && $this->expiresAt < time();
}
public function getExtraData(): array
{
return $this->extraData;
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
use kamermans\OAuth2\Token\TokenInterface;
class IntegrationTokenFactory implements TokenFactoryInterface
{
/**
* @param mixed[] $extraKeysToStore Extra keys returned by the service during the token process that needs to be captured
* @param int|null $defaultExpiresIn Default time in seconds that tokens are good for if not given in the response
*/
public function __construct(
private array $extraKeysToStore = [],
private ?int $defaultExpiresIn = null,
) {
}
public function __invoke(array $data, ?TokenInterface $previousToken = null): IntegrationToken
{
$accessToken = null;
$refreshToken = null;
// Read "access_token" attribute
if (isset($data['access_token'])) {
$accessToken = $data['access_token'];
}
// Read "refresh_token" attribute
if (isset($data['refresh_token'])) {
$refreshToken = $data['refresh_token'];
} elseif (null !== $previousToken) {
// When requesting a new access token with a refresh token, the
// server may not resend a new refresh token. In that case we
// should keep the previous refresh token as valid.
//
// See http://tools.ietf.org/html/rfc6749#section-6
$refreshToken = $previousToken->getRefreshToken();
}
$expiresAt = $this->getExpiration($data);
return new IntegrationToken($accessToken, $refreshToken, $expiresAt, $this->getExtraData($data));
}
private function getExtraData(array $data): array
{
$extraData = [];
foreach ($this->extraKeysToStore as $key) {
$extraData[$key] = $data[$key] ?? null;
}
return $extraData;
}
/**
* @param mixed[] $data
*/
private function getExpiration(array $data): ?int
{
// Read the "expires_at" attribute
if (isset($data['expires_at'])) {
return (int) $data['expires_at'];
}
// Read the "expires_in" attribute
if (isset($data['expires_in'])) {
return time() + (int) $data['expires_in'];
}
// Facebook unfortunately breaks the spec by using 'expires' instead of 'expires_in'
if (isset($data['expires'])) {
return time() + (int) $data['expires'];
}
// Fallback to the default if set
if ($this->defaultExpiresIn) {
return time() + $this->defaultExpiresIn;
}
return null;
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
use kamermans\OAuth2\Token\TokenInterface;
interface TokenFactoryInterface
{
public function __invoke(array $data, ?TokenInterface $previousToken = null): IntegrationToken;
}

View File

@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
use kamermans\OAuth2\Persistence\TokenPersistenceInterface;
use kamermans\OAuth2\Token\TokenInterface;
use Mautic\IntegrationsBundle\Exception\IntegrationNotSetException;
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
use Mautic\PluginBundle\Entity\Integration;
class TokenPersistence implements TokenPersistenceInterface
{
private ?Integration $integration = null;
public function __construct(
private IntegrationsHelper $integrationsHelper,
) {
}
/**
* Restore the token data into the give token.
*
* @return TokenInterface|IntegrationToken Restored token
*/
public function restoreToken(TokenInterface $token): TokenInterface
{
$apiKeys = $this->getIntegration()->getApiKeys();
$apiKeys['expires_at'] ??= null;
return new IntegrationToken(
empty($apiKeys['access_token']) ? null : $apiKeys['access_token'],
empty($apiKeys['refresh_token']) ? null : $apiKeys['refresh_token'],
$apiKeys['expires_at'] ?? null
);
}
/**
* Save the token data.
*/
public function saveToken(TokenInterface $token): void
{
$integration = $this->getIntegration();
$oldApiKeys = $integration->getApiKeys();
if (null === $oldApiKeys) {
$oldApiKeys = [];
}
$newApiKeys = [
'access_token' => $token->getAccessToken(),
'refresh_token' => $token->getRefreshToken(),
'expires_at' => $token->getExpiresAt(),
];
$extraData = $token instanceof IntegrationToken ? $token->getExtraData() : [];
$newApiKeys = array_merge($oldApiKeys, $extraData, $newApiKeys);
$integration->setApiKeys($newApiKeys);
$this->integrationsHelper->saveIntegrationConfiguration($integration);
}
/**
* Delete the saved token data.
*/
public function deleteToken(): void
{
$integration = $this->getIntegration();
$apiKeys = $integration->getApiKeys();
// Must delete both the access token and the expiration in order for the middleware to refresh
unset($apiKeys['access_token'], $apiKeys['expires_at']);
$integration->setApiKeys($apiKeys);
$this->integrationsHelper->saveIntegrationConfiguration($integration);
}
/**
* Returns true if a token exists (although it may not be valid).
*/
public function hasToken(): bool
{
return !empty($this->getIntegration()->getApiKeys()['access_token']);
}
public function setIntegration(Integration $integration): void
{
$this->integration = $integration;
}
/**
* @throws IntegrationNotSetException
*/
private function getIntegration(): Integration
{
if ($this->integration) {
return $this->integration;
}
throw new IntegrationNotSetException('Integration not set');
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
use kamermans\OAuth2\Token\RawToken;
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
use Mautic\PluginBundle\Entity\Integration;
class TokenPersistenceFactory
{
public function __construct(
private IntegrationsHelper $integrationsHelper,
) {
}
public function create(Integration $integration): TokenPersistence
{
$tokenPersistence = new TokenPersistence($this->integrationsHelper);
$tokenPersistence->setIntegration($integration);
$apiKeys = $integration->getApiKeys();
$token = new RawToken(
$apiKeys['access_token'] ?? null,
$apiKeys['refresh_token'] ?? null,
$apiKeys['expires_at'] ?? null
);
$tokenPersistence->restoreToken($token);
return $tokenPersistence;
}
}