> */ protected $clientMap = []; public function __construct( EntityManager $entityManager, Metadata $metadata, Config $config, ?InjectableFactory $injectableFactory = null ) { $this->entityManager = $entityManager; $this->metadata = $metadata; $this->config = $config; $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'])) { return; } /** @var ExternalAccountEntity $externalAccountEntity */ $externalAccountEntity = $this->clientMap[$hash]['externalAccountEntity']; $externalAccountEntity->set('accessToken', $data['accessToken']); $externalAccountEntity->set('tokenType', $data['tokenType']); $externalAccountEntity->set('expiresAt', $data['expiresAt'] ?? null); if ($data['refreshToken'] ?? null) { $externalAccountEntity->set('refreshToken', $data['refreshToken']); } $copy = $this->entityManager->getEntity('ExternalAccount', $externalAccountEntity->getId()); if (!$copy) { return; } if (!$copy->get('enabled')) { throw new Error("External Account Client Manager: Account got disabled."); } $copy->set('accessToken', $data['accessToken']); $copy->set('tokenType', $data['tokenType']); $copy->set('expiresAt', $data['expiresAt'] ?? null); if ($data['refreshToken'] ?? null) { $copy->set('refreshToken', $data['refreshToken'] ?? null); } $this->entityManager->saveEntity($copy, [ 'isTokenRenewal' => true, 'skipHooks' => true, ]); } public function create(string $integration, string $userId): ?object { $authMethod = $this->metadata->get("integrations.{$integration}.authMethod"); $methodName = 'create' . ucfirst($authMethod); if (method_exists($this, $methodName)) { return $this->$methodName($integration, $userId); } if (!$this->injectableFactory) { throw new Error(); } /** @var IntegrationEntity|null $integrationEntity */ $integrationEntity = $this->entityManager->getEntity('Integration', $integration); /** @var ExternalAccountEntity|null $externalAccountEntity */ $externalAccountEntity = $this->entityManager->getEntity('ExternalAccount', $integration . '__' . $userId); if (!$externalAccountEntity) { throw new Error("External Account {$integration} not found for {$userId}."); } if (!$integrationEntity) { return null; } if (!$integrationEntity->get('enabled')) { return null; } if (!$externalAccountEntity->get('enabled')) { return null; } /** @var class-string */ $className = $this->metadata->get("integrations.{$integration}.clientClassName"); $client = $this->injectableFactory->create($className); if (!method_exists($client, 'setup')) { throw new Error("{$className} does not have `setup` method."); } $client->setup( $userId, $integrationEntity, $externalAccountEntity, $this ); $this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId); return $client; } protected function createOAuth2(string $integration, string $userId): ?object { /** @var IntegrationEntity|null $integrationEntity */ $integrationEntity = $this->entityManager->getEntity('Integration', $integration); /** @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'; $redirectUriPath = $this->metadata->get(['integrations', $integration, 'params', 'redirectUriPath']); if ($redirectUriPath) { $redirectUri = rtrim($this->config->get('siteUrl'), '/') . '/' . $redirectUriPath; } if (!$externalAccountEntity) { throw new Error("External Account {$integration} not found for '{$userId}'."); } if (!$integrationEntity) { return null; } if (!$integrationEntity->get('enabled')) { return null; } if (!$externalAccountEntity->get('enabled')) { return null; } $oauth2Client = new OAuth2Client(); $params = [ 'endpoint' => $this->metadata->get("integrations.{$integration}.params.endpoint"), 'tokenEndpoint' => $this->metadata->get("integrations.{$integration}.params.tokenEndpoint"), 'clientId' => $integrationEntity->get('clientId'), 'clientSecret' => $integrationEntity->get('clientSecret'), 'redirectUri' => $redirectUri, 'accessToken' => $externalAccountEntity->get('accessToken'), 'refreshToken' => $externalAccountEntity->get('refreshToken'), 'tokenType' => $externalAccountEntity->get('tokenType'), 'expiresAt' => $externalAccountEntity->get('expiresAt'), ]; foreach (get_object_vars($integrationEntity->getValueMap()) as $k => $v) { if (array_key_exists($k, $params)) { continue; } if ($integrationEntity->hasAttribute($k)) { continue; } $params[$k] = $v; } $client = new $className($oauth2Client, $params, $this); if ($this->injectableFactory) { $this->injectableFactory->createWith($className, [ 'client' => $oauth2Client, 'params' => $params, 'manager' => $this, ]); } else { // For backward compatibility. $client = new $className($oauth2Client, $params, $this); } $this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId); return $client; } /** * @param object $client * @return void */ protected function addToClientMap( $client, IntegrationEntity $integrationEntity, ExternalAccountEntity $externalAccountEntity, string $userId ) { $this->clientMap[spl_object_hash($client)] = [ 'client' => $client, 'userId' => $userId, 'integration' => $integrationEntity->getId(), 'integrationEntity' => $integrationEntity, 'externalAccountEntity' => $externalAccountEntity, ]; } /** * @param object $client */ protected function getClientRecord($client): Entity { $data = $this->clientMap[spl_object_hash($client)]; if (!$data) { throw new Error("External Account Client Manager: Client not found in hash."); } return $data['externalAccountEntity']; } /** * @param object $client */ public function isClientLocked($client): bool { $externalAccountEntity = $this->getClientRecord($client); $id = $externalAccountEntity->getId(); $e = $this->entityManager ->getRDBRepository('ExternalAccount') ->select(['id', 'isLocked']) ->where(['id' => $id]) ->findOne(); if (!$e) { throw new Error("External Account Client Manager: Client '{$id}' not found in DB."); } return $e->get('isLocked'); } public function lockClient(object $client): void { $externalAccountEntity = $this->getClientRecord($client); $id = $externalAccountEntity->getId(); $e = $this->entityManager ->getRDBRepository('ExternalAccount') ->select(['id', 'isLocked']) ->where(['id' => $id]) ->findOne(); if (!$e) { throw new Error("External Account Client Manager: Client '{$id}' not found in DB."); } $e->set('isLocked', true); $this->entityManager->saveEntity($e, [ 'skipHooks' => true, 'silent' => true, ]); } public function unlockClient(object $client): void { $externalAccountEntity = $this->getClientRecord($client); $id = $externalAccountEntity->getId(); $e = $this->entityManager ->getRDBRepository('ExternalAccount') ->select(['id', 'isLocked']) ->where(['id' => $id]) ->findOne(); if (!$e) { throw new Error("External Account Client Manager: Client '{$id}' not found in DB."); } $e->set('isLocked', false); $this->entityManager->saveEntity($e, [ 'skipHooks' => true, 'silent' => true, ]); } /** * @param \Espo\Core\ExternalAccount\Clients\IClient $client */ public function reFetchClient(object $client): void { $externalAccountEntity = $this->getClientRecord($client); $id = $externalAccountEntity->getId(); $e = $this->entityManager->getEntity('ExternalAccount', $id); if (!$e) { throw new Error("External Account Client Manager: Client {$id} not found in DB."); } $data = $e->getValueMap(); $externalAccountEntity->set($data); $client->setParams(get_object_vars($data)); } }