diff --git a/application/Espo/Core/ExternalAccount/ClientManager.php b/application/Espo/Core/ExternalAccount/ClientManager.php index a9d0c880dc..79bc08b6dd 100644 --- a/application/Espo/Core/ExternalAccount/ClientManager.php +++ b/application/Espo/Core/ExternalAccount/ClientManager.php @@ -67,6 +67,10 @@ class ClientManager */ protected $injectableFactory = null; + /** + * + * @var array> + */ protected $clientMap = []; public function __construct( @@ -81,6 +85,14 @@ class ClientManager $this->injectableFactory = $injectableFactory; } + /** + * @param array{ + * accessToken: ?string, + * tokenType: ?string, + * expiresAt?: ?string, + * refreshToken?: ?string, + * } $data + */ public function storeAccessToken(string $hash, array $data): void { if (empty($this->clientMap[$hash]) || empty($this->clientMap[$hash]['externalAccountEntity'])) { @@ -154,6 +166,7 @@ class ClientManager return null; } + /** @var class-string */ $className = $this->metadata->get("integrations.{$integration}.clientClassName"); $client = $this->injectableFactory->create($className); @@ -178,6 +191,7 @@ class ClientManager /** @var ExternalAccountEntity|null $externalAccountEntity */ $externalAccountEntity = $this->entityManager->getEntity('ExternalAccount', $integration . '__' . $userId); + /** @var class-string */ $className = $this->metadata->get("integrations.{$integration}.clientClassName"); $redirectUri = $this->config->get('siteUrl') . '?entryPoint=oauthCallback'; @@ -245,6 +259,10 @@ class ClientManager return $client; } + /** + * @param object $client + * @return void + */ protected function addToClientMap( $client, IntegrationEntity $integrationEntity, @@ -260,6 +278,9 @@ class ClientManager ]; } + /** + * @param object $client + */ protected function getClientRecord($client): Entity { $data = $this->clientMap[spl_object_hash($client)]; @@ -271,6 +292,9 @@ class ClientManager return $data['externalAccountEntity']; } + /** + * @param object $client + */ public function isClientLocked($client): bool { $externalAccountEntity = $this->getClientRecord($client); diff --git a/application/Espo/Core/ExternalAccount/Clients/IClient.php b/application/Espo/Core/ExternalAccount/Clients/IClient.php index 5bf17cc77e..31ffdf71b1 100644 --- a/application/Espo/Core/ExternalAccount/Clients/IClient.php +++ b/application/Espo/Core/ExternalAccount/Clients/IClient.php @@ -31,12 +31,27 @@ namespace Espo\Core\ExternalAccount\Clients; interface IClient { + /** + * @param string $name + * @return mixed + */ public function getParam($name); - + + /** + * @param string $name + * @param mixed $value + * @return mixed + */ public function setParam($name, $value); - + + /** + * @param array $params + * @return mixed + */ public function setParams(array $params); + /** + * @return bool + */ public function ping(); } - diff --git a/application/Espo/Core/ExternalAccount/Clients/OAuth2Abstract.php b/application/Espo/Core/ExternalAccount/Clients/OAuth2Abstract.php index 9e29518145..55142f8b2e 100644 --- a/application/Espo/Core/ExternalAccount/Clients/OAuth2Abstract.php +++ b/application/Espo/Core/ExternalAccount/Clients/OAuth2Abstract.php @@ -42,12 +42,24 @@ use DateTime; abstract class OAuth2Abstract implements IClient { + /** + * @var ?Client + */ protected $client = null; + /** + * @var ?ClientManager + */ protected $manager = null; + /** + * @var Log + */ protected $log; + /** + * @var string[] + */ protected $paramList = [ 'endpoint', 'tokenEndpoint', @@ -60,16 +72,34 @@ abstract class OAuth2Abstract implements IClient 'expiresAt', ]; + /** + * @var ?string + */ protected $clientId = null; + /** + * @var ?string + */ protected $clientSecret = null; + /** + * @var ?string + */ protected $accessToken = null; + /** + * @var ?string + */ protected $refreshToken = null; + /** + * @var ?string + */ protected $redirectUri = null; + /** + * @var ?string + */ protected $expiresAt = null; const ACCESS_TOKEN_EXPIRATION_MARGIN = '20 seconds'; @@ -78,6 +108,9 @@ abstract class OAuth2Abstract implements IClient const LOCK_CHECK_STEP = 0.5; + /** + * @param array $params + */ public function __construct( Client $client, array $params = [], @@ -91,6 +124,10 @@ abstract class OAuth2Abstract implements IClient $this->setParams($params); } + /** + * @param string $name + * @return mixed + */ public function getParam($name) { if (in_array($name, $this->paramList)) { @@ -100,6 +137,11 @@ abstract class OAuth2Abstract implements IClient return null; } + /** + * @param string $name + * @param mixed $value + * @return void + */ public function setParam($name, $value) { if (in_array($name, $this->paramList)) { @@ -113,6 +155,10 @@ abstract class OAuth2Abstract implements IClient } } + /** + * @param array $params + * @return void + */ public function setParams(array $params) { foreach ($this->paramList as $name) { @@ -122,6 +168,10 @@ abstract class OAuth2Abstract implements IClient } } + /** + * @param array $data + * @return void + */ protected function afterTokenRefreshed(array $data): void { if ($this->manager) { @@ -129,6 +179,19 @@ abstract class OAuth2Abstract implements IClient } } + /** + * @param array{ + * access_token: string, + * token_type: string, + * refresh_token?: string, + * expires_in?: int, + * } $result + * @return array{ + * accessToken: string, + * tokenType: string, + * expiresAt: ?string, + * } + */ protected function getAccessTokenDataFromResponseResult($result): array { $data = []; @@ -152,7 +215,12 @@ abstract class OAuth2Abstract implements IClient } /** - * @return array|null + * @return ?array{ + * accessToken: string, + * tokenType: string, + * expiresAt: ?string, + * refreshToken: string, + * } */ public function getAccessTokenFromAuthorizationCode(string $code) { @@ -292,7 +360,7 @@ abstract class OAuth2Abstract implements IClient /** * * @param string $url - * @param array|string|null $params + * @param array|string|null $params * @param string $httpMethod * @param ?string $contentType * @param bool $allowRenew @@ -414,6 +482,12 @@ abstract class OAuth2Abstract implements IClient return false; } + /** + * @param array $r + * @return ?array{ + * action: string, + * } + */ protected function handleErrorResponse($r) { if ($r['code'] == 401 && !empty($r['result'])) { diff --git a/application/Espo/Core/ExternalAccount/OAuth2/Client.php b/application/Espo/Core/ExternalAccount/OAuth2/Client.php index f05d3bd985..0f9dac2996 100644 --- a/application/Espo/Core/ExternalAccount/OAuth2/Client.php +++ b/application/Espo/Core/ExternalAccount/OAuth2/Client.php @@ -58,24 +58,54 @@ class Client const GRANT_TYPE_PASSWORD = 'password'; const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials'; + /** + * @var ?string + */ protected $clientId = null; + /** + * @var ?string + */ protected $clientSecret = null; + /** + * @var ?string + */ protected $accessToken = null; + /** + * @var ?string + */ protected $expiresAt = null; + /** + * @var int + */ protected $authType = self::AUTH_TYPE_URI; + /** + * @var string + */ protected $tokenType = self::TOKEN_TYPE_URI; + /** + * @var ?string + */ protected $accessTokenSecret = null; + /** + * @var string + */ protected $accessTokenParamName = 'access_token'; + /** + * @var ?string + */ protected $certificateFile = null; + /** + * @var array + */ protected $curlOptions = []; public function __construct() @@ -85,56 +115,110 @@ class Client } } + /** + * @param string $clientId + * @return void + */ public function setClientId($clientId) { $this->clientId = $clientId; } + /** + * @param ?string $clientSecret + * @return void + */ public function setClientSecret($clientSecret) { $this->clientSecret = $clientSecret; } + /** + * @param ?string $accessToken + * @return void + */ public function setAccessToken($accessToken) { $this->accessToken = $accessToken; } + /** + * @param int $authType + * @return void + */ public function setAuthType($authType) { $this->authType = $authType; } + /** + * @param string $certificateFile + * @return void + */ public function setCertificateFile($certificateFile) { $this->certificateFile = $certificateFile; } + /** + * @param string $option + * @param mixed $value + * @return void + */ public function setCurlOption($option, $value) { $this->curlOptions[$option] = $value; } + /** + * @param array $options + * @return void + */ public function setCurlOptions($options) { $this->curlOptions = array_merge($this->curlOptions, $options); } + /** + * @param string $tokenType + * @return void + */ public function setTokenType($tokenType) { $this->tokenType = $tokenType; } + /** + * @param ?string $value + * @return void + */ public function setExpiresAt($value) { $this->expiresAt = $value; } + /** + * @param ?string $accessTokenSecret + * @return void + */ public function setAccessTokenSecret($accessTokenSecret) { $this->accessTokenSecret = $accessTokenSecret; } + /** + * @param string $url + * @param array|string|null $params + * @param string $httpMethod + * @param array $httpHeaders + * @return array{ + * result: array|string, + * code: int, + * contentType: string, + * header: string, + * } + * @throws Exception + */ public function request($url, $params = null, $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = []) { if ($this->accessToken) { @@ -162,6 +246,19 @@ class Client return $this->execute($url, $params, $httpMethod, $httpHeaders); } + /** + * @param string $url + * @param array|string|null $params + * @param string $httpMethod + * @param array $httpHeaders + * @return array{ + * result: array|string, + * code: int, + * contentType: string, + * header: string, + * } + * @throws Exception + */ private function execute($url, $params, $httpMethod, array $httpHeaders = []) { $curlOptions = [ @@ -267,6 +364,21 @@ class Client ]; } + /** + * @param string $url + * @param string $grantType + * @param array{ + * client_id?: string, + * client_secret?: string, + * } $params + * @return array{ + * result: array|string, + * code: int, + * contentType: string, + * header: string, + * } + * @throws Exception + */ public function getAccessToken($url, $grantType, array $params) { $params['grant_type'] = $grantType;