diff --git a/composer.json b/composer.json index e28780bcf560570de6a2d34e1ad3f280e06d1b12..b02b218338cbe6c585d21dac1d9eb9c057d19486 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "consolidation/output-formatters": "^3.2.0", "drupal/dynamic_entity_reference": "^2.0@alpha", "drupal/encrypt": "^3.0@rc", + "lusitanian/oauth": "^0.8.11", "messageagency/force.com-toolkit-for-php": "^1.0.0" } } diff --git a/config/install/salesforce.settings.yml b/config/install/salesforce.settings.yml index 1a2b933d9c12794bd2b4b9937cefe0ad30846958..77ae9eb2d924053605f0b0856bc243356e17a612 100644 --- a/config/install/salesforce.settings.yml +++ b/config/install/salesforce.settings.yml @@ -8,3 +8,4 @@ pull_max_queue_size: 100000 show_all_objects: false standalone: false limit_mapped_object_revisions: 10 +salesforce_auth_provider: '' diff --git a/config/schema/salesforce.schema.yml b/config/schema/salesforce.schema.yml index e00a0262ed0840736f91e28f22b9889fb05c3a7f..ac83bb9277c18346201dfbbaf48330bce4f17415 100644 --- a/config/schema/salesforce.schema.yml +++ b/config/schema/salesforce.schema.yml @@ -38,6 +38,10 @@ salesforce.settings: type: integer label: 'Limit mapped object revisions' description: 'Specify a maximum number of revisions to retain for Mapped Object content. Use 0 for no limit.' + salesforce_auth_provider: + type: string + label: 'Default authorization provider id' + description: 'A salesforce_auth config entity id which provides API authorization.' rest_api_version: type: mapping label: 'REST API Version' @@ -52,3 +56,35 @@ salesforce.settings: version: type: string label: 'Version' + +salesforce.salesforce_auth.*: + type: config_entity + label: 'Salesforce Auth Provider' + mapping: + id: + type: string + label: 'ID' + label: + type: label + label: 'Label' + translatable: true + provider: + type: string + label: 'Provider Plugin' + provider_settings: + type: salesforce.auth_provider_settings.[%parent.provider] + label: 'Provider Plugin Settings' + +salesforce.auth_provider_settings.oauth: + type: mapping + label: 'Salesforce OAuth Provider Settings' + mapping: + consumer_key: + type: string + label: 'Consumer Key' + consumer_secret: + type: string + label: 'Consumer Secret' + login_url: + type: uri + label: 'Login URL' diff --git a/salesforce.info.yml b/salesforce.info.yml index 0e3ba3afcf79194cf8a9e259b8a05c6809599a7c..c6a3d3f05d2a9799f1d1c2ada1ec48bb183082cc 100644 --- a/salesforce.info.yml +++ b/salesforce.info.yml @@ -3,4 +3,4 @@ type: module description: Modules to integrate Drupal and Salesforce package: Salesforce core: 8.x -configure: salesforce.config_index +configure: salesforce.admin_config_salesforce diff --git a/salesforce.install b/salesforce.install index 306bfa67f472fc731c3a484e55d2bf22990866e1..41b194482b260a0b930d3a31453ba39ad93fe5f4 100644 --- a/salesforce.install +++ b/salesforce.install @@ -5,7 +5,11 @@ * Salesforce install file. */ +use Drupal\Core\Url; use Drupal\Component\Serialization\Json; +use Drupal\salesforce\Entity\SalesforceAuthConfig; +use Drupal\Core\Utility\UpdateException; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; /** * Implements hook_uninstall(). @@ -36,7 +40,12 @@ function salesforce_requirements($phase) { // Check requirements once per 24 hours. $last = \Drupal::state()->get('salesforce.last_requirements_check', 0); - $requirements['salesforce_usage'] = salesforce_get_usage_requirements(); + $requirements['salesforce_auth_provider'] = salesforce_get_auth_provider_requirements(); + + // Don't bother checking usage if we aren't connected to Salesforce. + if ($requirements['salesforce_auth_provider']['severity'] == REQUIREMENT_OK) { + $requirements['salesforce_usage'] = salesforce_get_usage_requirements(); + } $requirements['salesforce_tls'] = salesforce_get_tls_requirements(); if ($last < REQUEST_TIME - (60 * 60 * 24) || empty($requirements['salesforce_tls'])) { @@ -47,6 +56,53 @@ function salesforce_requirements($phase) { return $requirements; } +/** + * Check to see if an auth provider has been set. + */ +function salesforce_get_auth_provider_requirements() { + $requirements = [ + 'title' => t('Salesforce Authentication Status'), + 'value' => t('Provider Status'), + ]; + /** @var \Drupal\salesforce\SalesforceAuthProviderPluginManager $authMan */ + $authMan = \Drupal::service('plugin.manager.salesforce.auth_providers'); + if (!$authMan->hasProviders()) { + $requirements += [ + 'description' => t('No auth providers have been created. Please <a href="@href">create an auth provider</a> to connect to Salesforce.', ['@href' => Url::fromRoute('entity.salesforce_auth.add_form')]), + 'severity' => REQUIREMENT_WARNING, + ]; + } + elseif (!$authMan->getConfig()) { + $requirements += [ + 'description' => t('Default auth provider has not been set. Please <a href="@href">choose an auth provider</a> to connect to Salesforce.', ['@href' => Url::fromRoute('salesforce.auth_config')->toString()]), + 'severity' => REQUIREMENT_WARNING, + ]; + } + else { + $failMessage = t('Salesforce authentication failed. Please <a href="@href">check your auth provider settings</a> to connect to Salesforce.', ['@href' => Url::fromRoute('entity.salesforce_auth.edit_form', ['salesforce_auth' => $authMan->getConfig()])]); + try { + if (!$authMan->getToken()) { + $requirements += [ + 'description' => $failMessage, + 'severity' => REQUIREMENT_WARNING, + ]; + } + } + catch (Exception $e) { + $requirements += [ + 'description' => $failMessage, + 'severity' => REQUIREMENT_WARNING, + ]; + } + } + if (empty($requirements['severity'])) { + $requirements += [ + 'severity' => REQUIREMENT_OK, + ]; + } + return $requirements; +} + /** * Check TLS status. */ @@ -132,6 +188,7 @@ function salesforce_get_usage_requirements() { } if (empty($usage)) { + // Missing usage information is not necessarily a problem. $requirements += [ 'severity' => REQUIREMENT_OK, 'description' => t('Usage information unavailable'), @@ -152,7 +209,7 @@ function salesforce_get_usage_requirements() { ]; $requirements += [ 'description' => t('Usage: %usage requests of %limit limit (%pct) in the past 24 hours.', $args), - 'severity' => $pct >= 100 ? REQUIREMENT_WARNING : REQUIREMENT_OK, + 'severity' => $pct >= 100 ? REQUIREMENT_ERROR : ($pct >= 80 ? REQUIREMENT_WARNING : REQUIREMENT_OK), ]; } @@ -209,3 +266,40 @@ function salesforce_update_8003() { function salesforce_update_8004() { \Drupal::cache()->delete('salesforce:objects'); } + +/** + * Convert legacy oauth credentials to new auth plugin config. + */ +function salesforce_update_8005() { + $change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); + if (!empty($change_list['salesforce_auth'])) { + throw new UpdateException("** PENDING SCHEMA UPDATES ** \n** Please install entity updates (entup) to install Salesforce Auth Config before proceeding with database update."); + } + + $message = ''; + // If auth plugin providers have not been created already, convert existing. + if (SalesforceAuthConfig::load('oauth_default')) { + // If an auth config with our name already exists, we are done here. + $message = 'Existing "oauth_default" provider config detected. Refused to set legacy credentials.'; + } + else { + SalesforceAuthProviderPluginManager::updateAuthConfig(); + $message = 'Default OAuth provider created from legacy credentials.'; + } + return $message; +} + +/** + * Convert legacy token to new auth plugin config. + */ +function salesforce_update_8006() { + $oauth = SalesforceAuthProviderPluginManager::getAuthConfig(); + if (!$oauth) { + return "Auth config missing. Refused to update legacy token."; + } + if (\Drupal::service('salesforce.auth_token_storage')->retrieveAccessToken($oauth->id())) { + return "Token exists. Refused to update."; + } + \Drupal::service('salesforce.auth_token_storage')->updateToken(); + return "Updated legacy token to new plugin config."; +} diff --git a/salesforce.permissions.yml b/salesforce.permissions.yml index 3de400a507c041f9ea29db484af4045abb2bea2d..8277c4774a862c31bd0dd2a7178322d4b6e06162 100644 --- a/salesforce.permissions.yml +++ b/salesforce.permissions.yml @@ -6,3 +6,4 @@ administer salesforce: authorize salesforce: title: 'authorize salesforce' description: 'Access Salesforce OAuth consumer key, secret, and identify information' + restrict access: TRUE diff --git a/salesforce.services.yml b/salesforce.services.yml index a91e46c9a040f0719e44810ba1e893ccf22a4ed6..a72e9f999917c4349055b6749e24429d4f28d70d 100644 --- a/salesforce.services.yml +++ b/salesforce.services.yml @@ -2,3 +2,15 @@ services: salesforce.client: class: Drupal\salesforce\Rest\RestClient arguments: ['@http_client', '@config.factory', '@state', '@cache.default', '@serialization.json', '@datetime.time'] + + plugin.manager.salesforce.auth_providers: + class: Drupal\salesforce\SalesforceAuthProviderPluginManager + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@entity_type.manager'] + + salesforce.http_client_wrapper: + class: Drupal\salesforce\Client\HttpClientWrapper + arguments: ['@http_client'] + + salesforce.auth_token_storage: + class: Drupal\salesforce\Storage\SalesforceAuthTokenStorage + arguments: ['@state'] diff --git a/src/Client/HttpClientWrapper.php b/src/Client/HttpClientWrapper.php new file mode 100644 index 0000000000000000000000000000000000000000..53c6c5fb3c83f1be247b0a1e1be81c5c989552c5 --- /dev/null +++ b/src/Client/HttpClientWrapper.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\salesforce\Client; + +use GuzzleHttp\ClientInterface as GuzzleClientInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Wraps Guzzle HTTP client for an OAuth ClientInterface. + */ +class HttpClientWrapper implements ClientInterface { + + /** + * Guzzle HTTP Client service. + * + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * HttpClientWrapper constructor. + * + * @param \GuzzleHttp\ClientInterface $httpClient + * Guzzle HTTP client service, from core http_client. + */ + public function __construct(GuzzleClientInterface $httpClient) { + $this->httpClient = $httpClient; + } + + /** + * {@inheritdoc} + */ + public function retrieveResponse( + UriInterface $endpoint, + $requestBody, + array $extraHeaders = [], + $method = 'POST' + ) { + $response = $this->httpClient->request($method, $endpoint->getAbsoluteUri(), ['headers' => $extraHeaders, 'form_params' => $requestBody]); + return $response->getBody()->getContents(); + } + +} diff --git a/src/Consumer/OAuthCredentials.php b/src/Consumer/OAuthCredentials.php new file mode 100644 index 0000000000000000000000000000000000000000..091672b61318458ee2f54e9294cb6ff22a588af2 --- /dev/null +++ b/src/Consumer/OAuthCredentials.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\salesforce\Consumer; + +/** + * OAuth user agent credentials. + */ +class OAuthCredentials extends SalesforceCredentials { + + /** + * {@inheritdoc} + */ + public function __construct($consumerKey, $loginUrl, $consumerSecret) { + parent::__construct($consumerKey, $loginUrl); + $this->consumerSecret = $consumerSecret; + } + +} diff --git a/src/Consumer/SalesforceCredentials.php b/src/Consumer/SalesforceCredentials.php new file mode 100644 index 0000000000000000000000000000000000000000..2ee0818f32e3e8ca03eefe003da0f83bc036e329 --- /dev/null +++ b/src/Consumer/SalesforceCredentials.php @@ -0,0 +1,60 @@ +<?php + +namespace Drupal\salesforce\Consumer; + +use Drupal\Core\Url; +use OAuth\Common\Consumer\Credentials; + +/** + * Salesforce credentials extension, for drupalisms. + */ +abstract class SalesforceCredentials extends Credentials implements SalesforceCredentialsInterface { + + /** + * Login URL e.g. https://test.salesforce.com or https://login.salesforce.com. + * + * @var string + */ + protected $loginUrl; + + /** + * Consumer key for the Salesforce connected OAuth app. + * + * @var string + */ + protected $consumerKey; + + /** + * {@inheritdoc} + */ + public function __construct($consumerKey, $loginUrl) { + parent::__construct($consumerKey, NULL, NULL); + $this->loginUrl = $loginUrl; + $this->consumerKey = $consumerKey; + } + + /** + * {@inheritdoc} + */ + public function getConsumerKey() { + return $this->consumerKey; + } + + /** + * {@inheritdoc} + */ + public function getLoginUrl() { + return $this->loginUrl; + } + + /** + * {@inheritdoc} + */ + public function getCallbackUrl() { + return Url::fromRoute('salesforce.oauth_callback', [], [ + 'absolute' => TRUE, + 'https' => TRUE, + ])->toString(); + } + +} diff --git a/src/Consumer/SalesforceCredentialsInterface.php b/src/Consumer/SalesforceCredentialsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..000e1d1097d1dd2b013d055d37b61f7f8d0eabd1 --- /dev/null +++ b/src/Consumer/SalesforceCredentialsInterface.php @@ -0,0 +1,26 @@ +<?php + +namespace Drupal\salesforce\Consumer; + +/** + * Salesforce credentials interface. + */ +interface SalesforceCredentialsInterface { + + /** + * Get the consumer key for these credentials. + * + * @return string + * The consumer key. + */ + public function getConsumerKey(); + + /** + * Get the login URL for these credentials. + * + * @return string + * The login url, e.g. https://login.salesforce.com. + */ + public function getLoginUrl(); + +} diff --git a/src/Entity/SalesforceAuthConfig.php b/src/Entity/SalesforceAuthConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..b6e2944a57081d7ecacfe583970e3d1df120cbe3 --- /dev/null +++ b/src/Entity/SalesforceAuthConfig.php @@ -0,0 +1,143 @@ +<?php + +namespace Drupal\salesforce\Entity; + +use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\EntityInterface; + +/** + * Defines a Salesforce Auth entity. + * + * @ConfigEntityType( + * id = "salesforce_auth", + * label = @Translation("Salesforce Auth Config"), + * module = "salesforce_auth", + * entity_keys = { + * "id" = "id", + * "label" = "label" + * }, + * handlers = { + * "list_builder" = "Drupal\salesforce\Controller\SalesforceAuthListBuilder", + * "form" = { + * "default" = "Drupal\salesforce\Form\SalesforceAuthForm", + * "delete" = "Drupal\salesforce\Form\SalesforceAuthDeleteForm", + * "revoke" = "Drupal\salesforce\Form\SalesforceAuthRevokeForm" + * } + * }, + * links = { + * "collection" = "/admin/config/salesforce/authorize/list", + * "edit-form" = "/admin/config/salesforce/authorize/edit/{salesforce_auth}", + * "delete-form" = "/admin/config/salesforce/authorize/delete/{salesforce_auth}", + * "revoke" = "/admin/config/salesforce/authorize/revoke/{salesforce_auth}" + * }, + * admin_permission = "authorize salesforce", + * ) + */ +class SalesforceAuthConfig extends ConfigEntityBase implements EntityInterface { + + /** + * Auth id. e.g. "oauth_full_sandbox". + * + * @var string + */ + protected $id; + + /** + * Auth label. e.g. "OAuth Full Sandbox". + * + * @var string + */ + protected $label; + + /** + * The auth provider for this auth config. + * + * @var string + */ + protected $provider; + + /** + * Provider plugin configuration settings. + * + * @var array + */ + protected $provider_settings = []; + + /** + * Auth manager. + * + * @var \Drupal\salesforce\SalesforceAuthProviderPluginManager + */ + protected $manager; + + /** + * Id getter. + */ + public function id() { + return $this->id; + } + + /** + * Label getter. + */ + public function label() { + return $this->label; + } + + /** + * Plugin getter. + * + * @return \Drupal\salesforce\SalesforceAuthProviderInterface|null + * The auth provider plugin, or null. + */ + public function getPlugin() { + $settings = $this->provider_settings ?: []; + $settings += ['id' => $this->id()]; + return $this->provider ? $this->authManager()->createInstance($this->provider, $settings) : NULL; + } + + /** + * Plugin id getter. + * + * @return string|null + * The auth provider plugin id, or null. + */ + public function getPluginId() { + return $this->provider ?: NULL; + } + + /** + * {@inheritdoc} + */ + public function getLoginUrl() { + return $this->getPlugin() ? $this->getPlugin()->getLoginUrl() : ''; + } + + /** + * Auth manager wrapper. + * + * @return \Drupal\salesforce\SalesforceAuthProviderPluginManager|mixed + * The auth provider plugin manager. + */ + public function authManager() { + if (!$this->manager) { + $this->manager = \Drupal::service("plugin.manager.salesforce.auth_providers"); + } + return $this->manager; + } + + /** + * Returns a list of plugins, for use in forms. + * + * @return array + * The list of plugins, indexed by ID. + */ + public function getPluginsAsOptions() { + $options = ['' => t('- Select -')]; + foreach ($this->authManager()->getDefinitions() as $id => $definition) { + $options[$id] = ($definition['label']); + } + return $options; + } + +} diff --git a/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php b/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php new file mode 100644 index 0000000000000000000000000000000000000000..d88d1abbd9371473e81d3e6c0b28ebff09afedbd --- /dev/null +++ b/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php @@ -0,0 +1,170 @@ +<?php + +namespace Drupal\salesforce\Plugin\SalesforceAuthProvider; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Routing\TrustedRedirectResponse; +use Drupal\salesforce\Consumer\OAuthCredentials; +use Drupal\salesforce\SalesforceAuthProviderPluginBase; +use Drupal\salesforce\SalesforceOAuthPluginInterface; +use Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Uri\Uri; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Salesforce OAuth user-agent flow auth provider plugin. + * + * @Plugin( + * id = "oauth", + * label = @Translation("Salesforce OAuth User-Agent") + * ) + */ +class SalesforceOAuthPlugin extends SalesforceAuthProviderPluginBase implements SalesforceOAuthPluginInterface { + + /** + * Credentials. + * + * @var \Drupal\salesforce\Consumer\OAuthCredentials + */ + protected $credentials; + + /** + * {@inheritdoc} + */ + const SERVICE_TYPE = 'oauth'; + + /** + * {@inheritdoc} + */ + const LABEL = 'OAuth'; + + /** + * SalesforceOAuthPlugin constructor. + * + * @param string $id + * The plugin id. + * @param \Drupal\salesforce\Consumer\OAuthCredentials $credentials + * The credentials. + * @param \OAuth\Common\Http\Client\ClientInterface $httpClient + * The oauth http client. + * @param \Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface $storage + * Auth token storage service. + * + * @throws \OAuth\OAuth2\Service\Exception\InvalidScopeException + * Comment. + */ + public function __construct($id, OAuthCredentials $credentials, ClientInterface $httpClient, SalesforceAuthTokenStorageInterface $storage) { + parent::__construct($credentials, $httpClient, $storage, [], new Uri($credentials->getLoginUrl())); + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $configuration = array_merge(self::defaultConfiguration(), $configuration); + $cred = new OAuthCredentials($configuration['consumer_key'], $configuration['login_url'], $configuration['consumer_secret']); + return new static($configuration['id'], $cred, $container->get('salesforce.http_client_wrapper'), $container->get('salesforce.auth_token_storage')); + } + + /** + * {@inheritdoc} + */ + public static function defaultConfiguration() { + $defaults = parent::defaultConfiguration(); + return array_merge($defaults, [ + 'consumer_secret' => '', + ]); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['consumer_key'] = [ + '#title' => t('Salesforce consumer key'), + '#type' => 'textfield', + '#description' => t('Consumer key of the Salesforce remote application you want to grant access to'), + '#required' => TRUE, + '#default_value' => $this->credentials->getConsumerKey(), + ]; + + $form['consumer_secret'] = [ + '#title' => $this->t('Salesforce consumer secret'), + '#type' => 'textfield', + '#description' => $this->t('Consumer secret of the Salesforce remote application.'), + '#required' => TRUE, + '#default_value' => $this->credentials->getConsumerSecret(), + ]; + + $form['login_url'] = [ + '#title' => t('Login URL'), + '#type' => 'textfield', + '#default_value' => $this->credentials->getLoginUrl(), + '#description' => t('Enter a login URL, either https://login.salesforce.com or https://test.salesforce.com.'), + '#required' => TRUE, + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $this->setConfiguration($form_state->getValues()); + $settings = $form_state->getValue('provider_settings'); + // Write the config id to private temp store, so that we can use the same + // callback URL for all OAuth applications in Salesforce. + /** @var \Drupal\Core\TempStore\PrivateTempStore $tempstore */ + $tempstore = \Drupal::service('user.private_tempstore')->get('salesforce_oauth'); + $tempstore->set('config_id', $form_state->getValue('id')); + + try { + $path = $this->getAuthorizationEndpoint(); + $query = [ + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + 'client_id' => $settings['consumer_key'], + ]; + + // Send the user along to the Salesforce OAuth login form. If successful, + // the user will be redirected to {redirect_uri} to complete the OAuth + // handshake, and thence to the entity listing. Upon failure, the user + // redirect URI will send the user back to the edit form. + $response = new TrustedRedirectResponse($path . '?' . http_build_query($query), 302); + $response->send(); + return; + } + catch (\Exception $e) { + $this->messenger()->addError(t("Error during authorization: %message", ['%message' => $e->getMessage()])); + } + } + + /** + * {@inheritdoc} + */ + public function getConsumerSecret() { + return $this->credentials->getConsumerSecret(); + } + + /** + * {@inheritdoc} + */ + public function finalizeOauth() { + $token = $this->requestAccessToken(\Drupal::request()->get('code')); + + // Initialize identity. + $headers = [ + 'Authorization' => 'OAuth ' . $token->getAccessToken(), + 'Content-type' => 'application/json', + ]; + $data = $token->getExtraParams(); + $response = $this->httpClient->retrieveResponse(new Uri($data['id']), [], $headers); + $identity = $this->parseIdentityResponse($response); + $this->storage->storeIdentity($this->service(), $identity); + return TRUE; + } + +} diff --git a/src/Rest/RestClient.php b/src/Rest/RestClient.php index 84aac08b6db761b6eb0c7ed13996b8723cd253c9..2b734963ff8f5ed23f47097a044df9703d963749 100644 --- a/src/Rest/RestClient.php +++ b/src/Rest/RestClient.php @@ -9,6 +9,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Url; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; use Drupal\salesforce\SelectQueryInterface; use Drupal\salesforce\SFID; use Drupal\salesforce\SObject; @@ -89,6 +90,15 @@ class RestClient implements RestClientInterface { protected $httpClientOptions; + /** + * Token storage. + * + * @var \Drupal\salesforce\Storage\SalesforceAuthTokenStorage + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + private $storage; + const CACHE_LIFETIME = 300; const LONGTERM_CACHE_LIFETIME = 86400; @@ -121,6 +131,21 @@ class RestClient implements RestClientInterface { return $this; } + /** + * Storage helper. + * + * @return \Drupal\salesforce\Storage\SalesforceAuthTokenStorage + * The auth token storage service. + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + private function storage() { + if (!$this->storage) { + $this->storage = \Drupal::service('salesforce.auth_token_storage'); + } + return $this->storage; + } + /** * {@inheritdoc} */ @@ -371,6 +396,7 @@ class RestClient implements RestClientInterface { */ public function setConsumerKey($value) { $this->mutableConfig->set('consumer_key', $value)->save(); + SalesforceAuthProviderPluginManager::updateAuthConfig(); return $this; } @@ -386,6 +412,7 @@ class RestClient implements RestClientInterface { */ public function setConsumerSecret($value) { $this->mutableConfig->set('consumer_secret', $value)->save(); + SalesforceAuthProviderPluginManager::updateAuthConfig(); return $this; } @@ -402,6 +429,7 @@ class RestClient implements RestClientInterface { */ public function setLoginUrl($value) { $this->mutableConfig->set('login_url', $value)->save(); + SalesforceAuthProviderPluginManager::updateAuthConfig(); return $this; } @@ -433,13 +461,14 @@ class RestClient implements RestClientInterface { */ public function setAccessToken($token) { $this->state->set('salesforce.access_token', $token); + $this->storage()->updateToken(); return $this; } /** * Get refresh token. */ - protected function getRefreshToken() { + public function getRefreshToken() { return $this->state->get('salesforce.refresh_token'); } @@ -448,6 +477,7 @@ class RestClient implements RestClientInterface { */ public function setRefreshToken($token) { $this->state->set('salesforce.refresh_token', $token); + $this->storage()->updateToken(); return $this; } @@ -524,6 +554,7 @@ class RestClient implements RestClientInterface { */ public function setIdentity($data) { $this->state->set('salesforce.identity', $data); + $this->storage()->updateIdentity(); return $this; } diff --git a/src/SalesforceAuthProviderInterface.php b/src/SalesforceAuthProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..130de7a7910102b22da60d29d06204d4ca378033 --- /dev/null +++ b/src/SalesforceAuthProviderInterface.php @@ -0,0 +1,153 @@ +<?php + +namespace Drupal\salesforce; + +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginFormInterface; +use OAuth\Common\Token\TokenInterface; +use OAuth\OAuth2\Service\ServiceInterface; + +/** + * Class SalesforceAuthProvider. + */ +interface SalesforceAuthProviderInterface extends ServiceInterface, PluginFormInterface, ContainerFactoryPluginInterface, PluginInspectionInterface { + + const AUTH_TOKEN_PATH = '/services/oauth2/token'; + const AUTH_ENDPOINT_PATH = '/services/oauth2/authorize'; + const SOAP_CLASS_PATH = '/services/Soap/class/'; + + /** + * Id of this service. + * + * @return string + * Id of this service. + */ + public function id(); + + /** + * Label of this service. + * + * @return string + * Id of this service. + */ + public function label(); + + /** + * Auth type id for this service, e.g. oauth, jwt, etc. + * + * @return string + * Provider type for this auth provider. + */ + public function type(); + + /** + * Perform a refresh of the given token. + * + * @param \OAuth\Common\Token\TokenInterface $token + * The token. + * + * @return \OAuth\Common\Token\TokenInterface + * The refreshed token. + * + * @throws \OAuth\OAuth2\Service\Exception\MissingRefreshTokenException + * Comment. + */ + public function refreshAccessToken(TokenInterface $token); + + /** + * Login URL, e.g. https://login.salesforce.com, for this plugin. + * + * @return string + * Login URL. + */ + public function getLoginUrl(); + + /** + * Consumer key for the connected OAuth app. + * + * @return string + * Consumer key. + */ + public function getConsumerKey(); + + /** + * Consumer secret for the connected OAuth app. + * + * @return string + * Consumer secret. + */ + public function getConsumerSecret(); + + /** + * Access token for this plugin. + * + * @return \OAuth\OAuth2\Token\TokenInterface + * The Token. + * + * @throws \OAuth\Common\Storage\Exception\TokenNotFoundException + */ + public function getAccessToken(); + + /** + * Identify for this connection. + * + * @return array + * Identity for this connection. + */ + public function getIdentity(); + + /** + * TRUE if the connection has a token, regardless of validity. + * + * @return bool + * TRUE if the connection has a token, regardless of validity. + */ + public function hasAccessToken(); + + /** + * Default configuration for this plugin type. + * + * @return array + * Default configuration. + */ + public static function defaultConfiguration(); + + /** + * Authorization URL for this plugin type. + * + * @return string + * Authorization URL for this plugin type. + */ + public function getAuthorizationEndpoint(); + + /** + * Access token URL for this plugin type. + * + * @return string + * Access token URL for this plugin type. + */ + public function getAccessTokenEndpoint(); + + /** + * Instance URL for this connection. + * + * @return string + * Instance URL for this connection. + * + * @throws \OAuth\Common\Storage\Exception\TokenNotFoundException + */ + public function getInstanceUrl(); + + /** + * Callback for configuration form after saving config entity. + * + * @param array $form + * The configuration form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + */ + public function save(array $form, FormStateInterface $form_state); + +} diff --git a/src/SalesforceAuthProviderPluginBase.php b/src/SalesforceAuthProviderPluginBase.php new file mode 100644 index 0000000000000000000000000000000000000000..f65a73e8ac61900a315cd59978679fc0d3f8b9fc --- /dev/null +++ b/src/SalesforceAuthProviderPluginBase.php @@ -0,0 +1,236 @@ +<?php + +namespace Drupal\salesforce; + +use Drupal\Core\DependencyInjection\DependencySerializationTrait; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerTrait; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\OAuth2\Service\Salesforce; + +/** + * Shared methods for auth providers. + */ +abstract class SalesforceAuthProviderPluginBase extends Salesforce implements SalesforceAuthProviderInterface { + + use StringTranslationTrait; + use DependencySerializationTrait; + use MessengerTrait; + + /** + * Credentials. + * + * @var \Drupal\salesforce\Consumer\SalesforceCredentials + */ + protected $credentials; + + /** + * Configuration. + * + * @var array + */ + protected $configuration; + + /** + * Token storage. + * + * @var \Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface + */ + protected $storage; + + /** + * Machine name identifier. + * + * @var string + */ + protected $id; + + /** + * {@inheritdoc} + */ + public static function defaultConfiguration() { + return [ + 'consumer_key' => '', + 'login_url' => 'https://test.salesforce.com', + ]; + } + + /** + * {@inheritdoc} + */ + public function getPluginId() { + return $this->getConfiguration('id'); + } + + /** + * {@inheritdoc} + */ + public function getPluginDefinition() { + return $this->getConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration($key = NULL) { + if ($key !== NULL) { + return !empty($this->configuration[$key]) ? $this->configuration[$key] : NULL; + } + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function getLoginUrl() { + return $this->credentials->getLoginUrl(); + } + + /** + * {@inheritdoc} + */ + public function getConsumerKey() { + return $this->credentials->getConsumerKey(); + } + + /** + * {@inheritdoc} + */ + public function getConsumerSecret() { + return $this->credentials->getConsumerSecret(); + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + + } + + /** + * {@inheritdoc} + */ + public function id() { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function type() { + return static::SERVICE_TYPE; + } + + /** + * {@inheritdoc} + */ + public function label() { + return static::LABEL; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() { + return new Uri($this->credentials->getLoginUrl() . static::AUTH_ENDPOINT_PATH); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() { + return new Uri($this->credentials->getLoginUrl() . static::AUTH_TOKEN_PATH); + } + + /** + * {@inheritdoc} + */ + public function hasAccessToken() { + return $this->storage->hasAccessToken($this->id()); + } + + /** + * {@inheritdoc} + */ + public function getAccessToken() { + return $this->storage->retrieveAccessToken($this->id()); + } + + /** + * {@inheritdoc} + */ + public function getInstanceUrl() { + return $this->getAccessToken()->getExtraParams()['instance_url']; + } + + /** + * {@inheritdoc} + */ + public function getIdentity() { + return $this->storage->retrieveIdentity($this->id()); + } + + /** + * {@inheritdoc} + */ + public function service() { + return $this->id(); + } + + /** + * Handle the identity response from Salesforce. + * + * @param string $responseBody + * JSON identity response from Salesforce. + * + * @return array + * The identity. + * + * @throws \OAuth\Common\Http\Exception\TokenResponseException + */ + protected function parseIdentityResponse($responseBody) { + $data = json_decode($responseBody, TRUE); + + if (NULL === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } + elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + return $data; + } + + /** + * Accessor to the storage adapter to be able to retrieve tokens. + * + * @return \Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface + * The token storage. + */ + public function getStorage() { + return $this->storage; + } + +} diff --git a/src/SalesforceAuthProviderPluginInterface.php b/src/SalesforceAuthProviderPluginInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9e96fbf4db4b1e9ebdd7545326d556efee1400c0 --- /dev/null +++ b/src/SalesforceAuthProviderPluginInterface.php @@ -0,0 +1,29 @@ +<?php + +namespace Drupal\salesforce; + +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Plugin\PluginFormInterface; + +/** + * Auth provider plugin interface. + */ +interface SalesforceAuthProviderPluginInterface extends PluginFormInterface, PluginInspectionInterface { + + /** + * The auth provider service. + * + * @return \Drupal\salesforce\SalesforceAuthProviderInterface + * The auth provider service. + */ + public function service(); + + /** + * Login URL set for this auth provider. + * + * @return string + * Login URL set for this auth provider. + */ + public function getLoginUrl(); + +} diff --git a/src/SalesforceAuthProviderPluginManager.php b/src/SalesforceAuthProviderPluginManager.php new file mode 100644 index 0000000000000000000000000000000000000000..3b24f0764e550844056e9ed49be14a9528f19904 --- /dev/null +++ b/src/SalesforceAuthProviderPluginManager.php @@ -0,0 +1,222 @@ +<?php + +namespace Drupal\salesforce; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\salesforce\Entity\SalesforceAuthConfig as SalesforceAuthEntity; +use Drupal\salesforce\Entity\SalesforceAuthConfig; +use OAuth\Common\Storage\Exception\TokenNotFoundException; +use OAuth\OAuth2\Token\StdOAuth2Token; + +/** + * Auth provider plugin manager. + */ +class SalesforceAuthProviderPluginManager extends DefaultPluginManager { + + /** + * Config from salesforce.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $config; + + /** + * Entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $etm; + + /** + * Salesforce Auth storage. + * + * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface + */ + protected $authStorage; + + /** + * Constructor. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $etm + * Entity type manager service. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $etm) { + parent::__construct('Plugin/SalesforceAuthProvider', $namespaces, $module_handler, 'Drupal\salesforce\SalesforceAuthProviderInterface'); + $this->alterInfo('salesforce_auth_provider_info'); + $this->setCacheBackend($cache_backend, 'salesforce_auth_provider'); + $this->etm = $etm; + } + + /** + * Backwards-compatibility for legacy singleton auth. + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + public static function updateAuthConfig() { + $oauth = self::getAuthConfig(); + $config = \Drupal::configFactory()->getEditable('salesforce.settings'); + $settings = [ + 'consumer_key' => $config->get('consumer_key'), + 'consumer_secret' => $config->get('consumer_secret'), + 'login_url' => $config->get('login_url'), + ]; + $oauth + ->set('provider_settings', $settings) + ->save(); + } + + /** + * Backwards-compatibility for legacy singleton auth. + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + public static function getAuthConfig() { + $config = \Drupal::configFactory()->getEditable('salesforce.settings'); + $auth_provider = $config->get('salesforce_auth_provider'); + if (!$auth_provider || !$oauth = SalesforceAuthConfig::load($auth_provider)) { + // Config to new plugin config system. + $values = [ + 'id' => 'oauth_default', + 'label' => 'OAuth Default', + 'provider' => 'oauth', + ]; + $oauth = SalesforceAuthConfig::create($values); + $config + ->set('salesforce_auth_provider', 'oauth_default') + ->save(); + } + return $oauth; + } + + /** + * Wrapper for salesforce_auth storage service. + * + * @return \Drupal\Core\Config\Entity\ConfigEntityStorageInterface + * Storage for salesforce_auth. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function authStorage() { + if (empty($this->authStorage)) { + $this->authStorage = $this->etm->getStorage('salesforce_auth'); + } + return $this->authStorage; + } + + /** + * All Salesforce auth providers. + * + * @return \Drupal\salesforce\Entity\SalesforceAuthConfig[] + * All auth provider plugins. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function getProviders() { + return $this->authStorage()->loadMultiple(); + } + + /** + * TRUE if any auth providers are defined. + * + * @return bool + * TRUE if any auth providers are defined. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function hasProviders() { + return $this->authStorage()->hasData(); + } + + /** + * Get the active auth service provider, or null if it has not been assigned. + * + * @return \Drupal\salesforce\Entity\SalesforceAuthConfig|null + * The active service provider, or null if it has not been assigned. + */ + public function getConfig() { + $provider_id = $this->config()->get('salesforce_auth_provider'); + if (empty($provider_id)) { + return NULL; + } + return SalesforceAuthEntity::load($provider_id); + } + + /** + * The auth provider plugin of the active service provider, or null. + * + * @return \Drupal\salesforce\SalesforceAuthProviderInterface|null + * The auth provider plugin of the active service provider, or null. + */ + public function getProvider() { + if (!$this->getConfig()) { + return NULL; + } + return $this->getConfig()->getPlugin(); + } + + /** + * Get the active token, or null if it has not been assigned. + * + * @return \OAuth\OAuth2\Token\TokenInterface + * The token of the plugin of the active config, or null. + */ + public function getToken() { + if (!$config = $this->getConfig()) { + return NULL; + } + if (!$provider = $config->getPlugin()) { + return NULL; + } + try { + return $provider->getAccessToken(); + } + catch (TokenNotFoundException $e) { + return NULL; + } + } + + /** + * Force a refresh of the active token and return the fresh token. + * + * @return \OAuth\OAuth2\Token\TokenInterface|null + * The token. + */ + public function refreshToken() { + if (!$config = $this->getConfig()) { + return NULL; + } + if (!$provider = $config->getPlugin()) { + return NULL; + } + $token = $this->getToken() ?: new StdOAuth2Token(); + return $provider->refreshAccessToken($token); + } + + /** + * Wrapper for salesforce.settings config. + * + * @return \Drupal\Core\Config\ImmutableConfig + * Salesforce settings config. + */ + protected function config() { + if (!$this->config) { + $this->config = \Drupal::config('salesforce.settings'); + } + return $this->config; + } + +} diff --git a/src/SalesforceOAuthPluginInterface.php b/src/SalesforceOAuthPluginInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..34d89b7d60caa08e3d46369b7cbbcab3d3d91c6f --- /dev/null +++ b/src/SalesforceOAuthPluginInterface.php @@ -0,0 +1,33 @@ +<?php + +namespace Drupal\salesforce; + +/** + * OAuth user-agent plugin interface. + * + * OAuth user-agent flow requires a 2-part handshake to complete authentication. + * This interface exposes methods to make the handshake possible. + */ +interface SalesforceOAuthPluginInterface extends SalesforceAuthProviderPluginInterface { + + /** + * Complete the OAuth user-agent handshake. + * + * @return bool + * TRUE if oauth finalization was successful. + * + * @throws \OAuth\Common\Http\Exception\TokenResponseException + * + * @see \Drupal\salesforce\Controller\SalesforceOAuthController + */ + public function finalizeOauth(); + + /** + * Getter for consumer secret. + * + * @return string + * The consumer secret. + */ + public function getConsumerSecret(); + +} diff --git a/src/Storage/SalesforceAuthTokenStorage.php b/src/Storage/SalesforceAuthTokenStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..beb74b730cfed230581882eb9626045d0865a463 --- /dev/null +++ b/src/Storage/SalesforceAuthTokenStorage.php @@ -0,0 +1,217 @@ +<?php + +namespace Drupal\salesforce\Storage; + +use Drupal\Core\State\StateInterface; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; +use Drupal\salesforce\Token\SalesforceToken; +use OAuth\Common\Storage\Exception\TokenNotFoundException; +use OAuth\Common\Token\TokenInterface; + +/** + * Salesforce auth token storage. + */ +class SalesforceAuthTokenStorage implements SalesforceAuthTokenStorageInterface { + + const TOKEN_STORAGE_PREFIX = "salesforce.auth_tokens"; + const AUTH_STATE_STORAGE_PREFIX = "salesforce.auth_state"; + const IDENTITY_STORAGE_PREFIX = "salesforce.auth_identity"; + + /** + * State kv storage. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * SalesforceAuthTokenStorage constructor. + * + * @param \Drupal\Core\State\StateInterface $state + * State service. + */ + public function __construct(StateInterface $state) { + $this->state = $state; + } + + /** + * Backwards-compatibility for legacy singleton auth. + * + * @return string + * Id of the active oauth. + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + private function service() { + $oauth = SalesforceAuthProviderPluginManager::getAuthConfig(); + return $oauth->id(); + } + + /** + * Backwards-compatibility for legacy singleton auth. + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + public function updateToken() { + $this->storeAccessToken($this->service(), + new SalesforceToken( + $this->state->get('salesforce.access_token'), + $this->state->get('salesforce.refresh_token'))); + return $this; + } + + /** + * Backwards-compatibility for legacy singleton auth. + * + * @deprecated BC legacy auth scheme only, do not use, will be removed. + */ + public function updateIdentity() { + $this->storeIdentity($this->service(), $this->state->get('salesforce.identity')); + return $this; + } + + /** + * Token storage key for given service. + * + * @return string + * Token storage key for given service. + */ + protected static function getTokenStorageId($service) { + return self::TOKEN_STORAGE_PREFIX . '.' . $service; + } + + /** + * Auth state storage key for given service. + * + * @return string + * Auth state storage key for given service. + */ + protected static function getAuthStateStorageId($service) { + return self::AUTH_STATE_STORAGE_PREFIX . '.' . $service; + } + + /** + * Identity storage key for given service. + * + * @return string + * Identity storage key for given service. + */ + protected static function getIdentityStorageId($service) { + return self::IDENTITY_STORAGE_PREFIX . '.' . $service; + } + + /** + * {@inheritdoc} + */ + public function retrieveAccessToken($service) { + if ($token = $this->state->get(self::getTokenStorageId($service))) { + return $token; + } + throw new TokenNotFoundException(); + } + + /** + * {@inheritdoc} + */ + public function storeAccessToken($service, TokenInterface $token) { + $this->state->set(self::getTokenStorageId($service), $token); + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasAccessToken($service) { + try { + return !empty($this->retrieveAccessToken($service)); + } + catch (TokenNotFoundException $e) { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function clearToken($service) { + $this->state->delete(self::getTokenStorageId($service)); + return $this; + } + + /** + * {@inheritdoc} + */ + public function clearAllTokens() { + // noop. We don't do this. Only here to satisfy interface. + return $this; + } + + /** + * {@inheritdoc} + */ + public function storeAuthorizationState($service, $state) { + $this->state->set(self::getAuthStateStorageId($service), $state); + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasAuthorizationState($service) { + return !empty($this->retrieveAuthorizationState($service)); + } + + /** + * {@inheritdoc} + */ + public function retrieveAuthorizationState($service) { + return $this->state->get(self::getAuthStateStorageId($service)); + } + + /** + * {@inheritdoc} + */ + public function clearAuthorizationState($service) { + $this->state->delete(self::getAuthStateStorageId($service)); + return $this; + } + + /** + * {@inheritdoc} + */ + public function clearAllAuthorizationStates() { + // noop. only here to satisfy interface. Use clearAuthorizationState(). + return $this; + } + + /** + * {@inheritdoc} + */ + public function storeIdentity($service, $identity) { + $this->state->set(self::getIdentityStorageId($service), $identity); + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasIdentity($service) { + return !empty($this->retrieveIdentity($service)); + } + + /** + * {@inheritdoc} + */ + public function retrieveIdentity($service) { + return $this->state->get(self::getIdentityStorageId($service)); + } + + /** + * {@inheritdoc} + */ + public function clearIdentity($service) { + $this->state->delete(self::getIdentityStorageId($service)); + return $this; + } + +} diff --git a/src/Storage/SalesforceAuthTokenStorageInterface.php b/src/Storage/SalesforceAuthTokenStorageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1a5bd54223ad0b45fa180bfd3d4a4c214cb99a08 --- /dev/null +++ b/src/Storage/SalesforceAuthTokenStorageInterface.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\salesforce\Storage; + +use OAuth\Common\Storage\TokenStorageInterface; + +/** + * Add identity handling to token storage. + * + * @package Drupal\salesforce\Storage + */ +interface SalesforceAuthTokenStorageInterface extends TokenStorageInterface { + + /** + * Setter for identity storage. + * + * @return $this + */ + public function storeIdentity($service, $identity); + + /** + * Return boolean indicating whether this service has an identity. + * + * @return bool + * TRUE if the service has an identity. + */ + public function hasIdentity($service); + + /** + * Identity for the given service. + * + * @return array + * Identity. + */ + public function retrieveIdentity($service); + + /** + * Clear identity for service. + * + * @return $this + */ + public function clearIdentity($service); + +} diff --git a/src/Token/SalesforceToken.php b/src/Token/SalesforceToken.php new file mode 100644 index 0000000000000000000000000000000000000000..c3668eb4773872a26225f72cd8dd6d571b8bc8f7 --- /dev/null +++ b/src/Token/SalesforceToken.php @@ -0,0 +1,13 @@ +<?php + +namespace Drupal\salesforce\Token; + +use OAuth\OAuth2\Token\StdOAuth2Token; + +/** + * Salesforce auth token. + */ +class SalesforceToken extends StdOAuth2Token { + + +}