diff --git a/composer.json b/composer.json
index 57d2f1f..9b785c5 100644
--- a/composer.json
+++ b/composer.json
@@ -1,34 +1,36 @@
{
- "name": "drupal/salesforce",
- "description": "Provides Drupal modules to integrate with Salesforce.",
- "type": "drupal-module",
- "homepage": "https://drupal.org/project/salesforce",
- "authors": [
- {
- "name": "Aaron Bauman (aaronbauman)",
- "homepage": "https://www.drupal.org/u/aaronbauman",
- "role": "Maintainer"
- },
- {
- "name": "Alexander Rhodes (ironsizide)",
- "homepage": "https://www.drupal.org/u/ironsizide",
- "role": "Maintainer"
- }
- ],
- "support": {
- "issues": "https://drupal.org/project/issues/salesforce",
- "source": "http://cgit.drupalcode.org/salesforce"
+ "name": "drupal/salesforce",
+ "description": "Provides Drupal modules to integrate with Salesforce.",
+ "type": "drupal-module",
+ "homepage": "https://drupal.org/project/salesforce",
+ "authors": [
+ {
+ "name": "Aaron Bauman (aaronbauman)",
+ "homepage": "https://www.drupal.org/u/aaronbauman",
+ "role": "Maintainer"
},
- "extra": {
- "drush": {
- "services": {
- "drush.services.yml": "^9"
- }
- }
- },
- "require": {
- "consolidation/output-formatters": "^3.2.0",
- "drupal/dynamic_entity_reference": "^2.0-alpha8",
- "drupal/encrypt": "^3.0-rc1"
+ {
+ "name": "Alexander Rhodes (ironsizide)",
+ "homepage": "https://www.drupal.org/u/ironsizide",
+ "role": "Maintainer"
+ }
+ ],
+ "support": {
+ "issues": "https://drupal.org/project/issues/salesforce",
+ "source": "http://cgit.drupalcode.org/salesforce"
+ },
+ "extra": {
+ "drush": {
+ "services": {
+ "drush.services.yml": "^9"
+ }
}
+ },
+ "require": {
+ "consolidation/output-formatters": "^3.2.0",
+ "drupal/dynamic_entity_reference": "^2.0-alpha8",
+ "drupal/encrypt": "^3.0-rc1",
+ "drupal/key": "^1.7",
+ "lusitanian/oauth": "^0.8.11"
+ }
}
diff --git a/config/install/salesforce.settings.yml b/config/install/salesforce.settings.yml
index 8732401..d9cddb2 100644
--- a/config/install/salesforce.settings.yml
+++ b/config/install/salesforce.settings.yml
@@ -7,4 +7,7 @@ global_push_limit: 100000
pull_max_queue_size: 100000
show_all_objects: false
standalone: false
-limit_mapped_object_revisions: 10
\ No newline at end of file
+limit_mapped_object_revisions: 10
+salesforce_auth:
+ provider: ''
+ config_id: ''
\ No newline at end of file
diff --git a/config/schema/salesforce.schema.yml b/config/schema/salesforce.schema.yml
index e00a026..b986c28 100644
--- a/config/schema/salesforce.schema.yml
+++ b/config/schema/salesforce.schema.yml
@@ -52,3 +52,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 JWT 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/modules/salesforce_encrypt/config/schema/salesforce_encrypt.schema.yml b/modules/salesforce_encrypt/config/schema/salesforce_encrypt.schema.yml
new file mode 100644
index 0000000..6e530bd
--- /dev/null
+++ b/modules/salesforce_encrypt/config/schema/salesforce_encrypt.schema.yml
@@ -0,0 +1,16 @@
+salesforce.auth_provider_settings.oauth_encrypt:
+ type: mapping
+ label: 'Salesforce JWT Provider Settings'
+ mapping:
+ consumer_key:
+ type: string
+ label: 'Consumer Key'
+ consumer_secret:
+ type: string
+ label: 'Consumer Secret'
+ login_url:
+ type: uri
+ label: 'Login URL'
+ encryption_profile:
+ type: encrypt.profile.[%key]
+ label: 'Encryption Profile ID'
diff --git a/modules/salesforce_encrypt/salesforce_encrypt.info.yml b/modules/salesforce_encrypt/salesforce_encrypt.info.yml
index 74f83be..8129abd 100644
--- a/modules/salesforce_encrypt/salesforce_encrypt.info.yml
+++ b/modules/salesforce_encrypt/salesforce_encrypt.info.yml
@@ -1,6 +1,6 @@
name: Salesforce Encrypted Keys
type: module
-description: Supplants Salesforce RestClient service to provide encrypted stateful data.
+description: Encryption provider for Salesforce credentials.
package: Salesforce
core: 8.x
dependencies:
diff --git a/modules/salesforce_encrypt/salesforce_encrypt.install b/modules/salesforce_encrypt/salesforce_encrypt.install
deleted file mode 100644
index 74ece30..0000000
--- a/modules/salesforce_encrypt/salesforce_encrypt.install
+++ /dev/null
@@ -1,50 +0,0 @@
-getEncryptionProfile();
- }
- catch (EntityNotFoundException $e) {
- // Noop.
- }
- $requirements['salesforce_encrypt'] = [
- 'title' => t('Salesforce Encrypt'),
- 'value' => t('Encryption Profile'),
- ];
- if (empty($profile)) {
- $requirements['salesforce_encrypt'] += [
- 'severity' => REQUIREMENT_ERROR,
- 'description' => t('You need to select an encryption profile in order to fully enable Salesforce Encrypt and protect sensitive information.', ['@url' => Url::fromRoute('salesforce_encrypt.settings')->toString()]),
- ];
- }
- else {
- $requirements['salesforce_encrypt'] += [
- 'severity' => REQUIREMENT_OK,
- 'description' => t('Profile id: %profile', ['%profile' => $profile->id(), ':url' => $profile->url()]),
- ];
- }
- }
- return $requirements;
-}
-
-/**
- * Implements hook_uninstall to decrypt and purge our data.
- */
-function salesforce_encrypt_uninstall() {
- \Drupal::service('salesforce.client')->disableEncryption();
- \Drupal::state()->delete('salesforce_encrypt.profile');
-}
diff --git a/modules/salesforce_encrypt/salesforce_encrypt.links.menu.yml b/modules/salesforce_encrypt/salesforce_encrypt.links.menu.yml
deleted file mode 100644
index 7825f42..0000000
--- a/modules/salesforce_encrypt/salesforce_encrypt.links.menu.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-salesforce_encrypt.settings:
- route_name: salesforce_encrypt.settings
- parent: salesforce.admin_config_salesforce
- title: Salesforce Encrypt
- description: 'Encrypt sensitive Salesforce OAuth credentials and identity.'
- weight: 10
diff --git a/modules/salesforce_encrypt/salesforce_encrypt.module b/modules/salesforce_encrypt/salesforce_encrypt.module
index ec0966d..a63e406 100644
--- a/modules/salesforce_encrypt/salesforce_encrypt.module
+++ b/modules/salesforce_encrypt/salesforce_encrypt.module
@@ -5,6 +5,9 @@
*/
use Drupal\encrypt\EncryptionProfileInterface;
+use Drupal\salesforce_encrypt\Plugin\SalesforceAuthProvider\SalesforceEncryptedOAuthPlugin;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\salesforce\Entity\SalesforceAuthConfig;
/**
* Implements hook_encryption_profile_predelete()
@@ -13,3 +16,20 @@ use Drupal\encrypt\EncryptionProfileInterface;
function salesforce_encryption_profile_predelete(EncryptionProfileInterface $entity) {
\Drupal::service('salesforce.client')->hookEncryptionProfileDelete($entity);
}
+
+/**
+ * Implements hook_entity_presave().
+ */
+function salesforce_encrypt_entity_presave(EntityInterface $entity) {
+ if (!$entity instanceof SalesforceAuthConfig) {
+ return;
+ }
+ // Encrypt credentials prior to saving the config entity.
+ if ($entity->getPlugin() instanceof SalesforceEncryptedOAuthPlugin) {
+ $plugin = $entity->getPlugin();
+ $settings = $entity->get('provider_settings');
+ $settings['consumer_key'] = $plugin->encrypt($settings['consumer_key']);
+ $settings['consumer_secret'] = $plugin->encrypt($settings['consumer_secret']);
+ $entity->set('provider_settings', $settings);
+ }
+}
diff --git a/modules/salesforce_encrypt/salesforce_encrypt.routing.yml b/modules/salesforce_encrypt/salesforce_encrypt.routing.yml
deleted file mode 100644
index ad30e49..0000000
--- a/modules/salesforce_encrypt/salesforce_encrypt.routing.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-salesforce_encrypt.settings:
- path: '/admin/config/salesforce/encrypt'
- defaults:
- _form: '\Drupal\salesforce_encrypt\Form\SettingsForm'
- _title: 'Salesforce Encryption'
- _description: 'Encrypt sensitive Salesforce OAuth credentials and identity.'
- requirements:
- _permission: 'administer salesforce encryption'
diff --git a/modules/salesforce_encrypt/src/Consumer/OAuthEncryptedCredentials.php b/modules/salesforce_encrypt/src/Consumer/OAuthEncryptedCredentials.php
new file mode 100644
index 0000000..5bb50d2
--- /dev/null
+++ b/modules/salesforce_encrypt/src/Consumer/OAuthEncryptedCredentials.php
@@ -0,0 +1,34 @@
+consumerSecret = $consumerSecret;
+ $this->encryptionProfileId = $encryptionProfileId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEncryptionProfileId() {
+ return $this->encryptionProfileId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCallbackUrl() {
+ return SalesforceEncryptedOAuthPlugin::getAuthCallbackUrl();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/salesforce_encrypt/src/Form/SettingsForm.php b/modules/salesforce_encrypt/src/Form/SettingsForm.php
index b9fbd4c..34d5e9c 100644
--- a/modules/salesforce_encrypt/src/Form/SettingsForm.php
+++ b/modules/salesforce_encrypt/src/Form/SettingsForm.php
@@ -7,7 +7,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\State\StateInterface;
use Drupal\encrypt\EncryptionProfileManagerInterface;
use Drupal\salesforce\EntityNotFoundException;
-use Drupal\salesforce_encrypt\Rest\EncryptedRestClientInterface;
+use Drupal\salesforce_encrypt\SalesforceEncryptedAuthTokenStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Url;
@@ -19,15 +19,20 @@ class SettingsForm extends FormBase {
protected $encryptionProfileManager;
/**
+ * @var \Drupal\salesforce_encrypt\SalesforceEncryptedAuthTokenStorageInterface
+ */
+ protected $storage;
+
+ /**
* Constructs a new key form base.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $storage
* The key storage.
*/
- public function __construct(StateInterface $state, EncryptionProfileManagerInterface $encryptionProfileManager, EncryptedRestClientInterface $client) {
+ public function __construct(StateInterface $state, EncryptionProfileManagerInterface $encryptionProfileManager, SalesforceEncryptedAuthTokenStorageInterface $storage) {
$this->encryptionProfileManager = $encryptionProfileManager;
$this->state = $state;
- $this->client = $client;
+ $this->storage = $storage;
}
/**
@@ -37,7 +42,7 @@ class SettingsForm extends FormBase {
return new static(
$container->get('state'),
$container->get('encrypt.encryption_profile.manager'),
- $container->get('salesforce.client')
+ $container->get('salesforce.auth_token_storage')
);
}
@@ -57,14 +62,14 @@ class SettingsForm extends FormBase {
->getEncryptionProfileNamesAsOptions();
$default = NULL;
try {
- $profile = $this->client->getEncryptionProfile();
+ $profile = $this->storage->getEncryptionProfile();
if (!empty($profile)) {
$default = $profile->id();
}
}
catch (EntityNotFoundException $e) {
- drupal_set_message($e->getFormattableMessage(), 'error');
- drupal_set_message($this->t('Error while loading encryption profile. You will need to assign a new encryption profile, then re-authenticate to Salesforce.', [':encrypt' => Url::fromRoute('salesforce_encrypt.settings')->toString(), ':oauth' => Url::fromRoute('salesforce.authorize')->toString()]), 'error');
+ $this->messenger()->addError($e->getFormattableMessage());
+ $this->messenger()->addError($this->t('Error while loading encryption profile. You will need to assign a new encryption profile, then re-authenticate to Salesforce.', [':encrypt' => Url::fromRoute('salesforce_encrypt.settings')->toString(), ':oauth' => Url::fromRoute('salesforce.admin_config_salesforce')->toString()]));
}
$form['profile'] = [
@@ -73,7 +78,7 @@ class SettingsForm extends FormBase {
'#description' => $this->t('Choose an encryption profile with which to encrypt Salesforce information.'),
'#options' => $options,
'#default_value' => $default,
- '#empty_option' => $this->t('Do not use encryption'),
+ '#empty_option' => TRUE,
];
$form['actions']['#type'] = 'actions';
@@ -113,19 +118,19 @@ class SettingsForm extends FormBase {
->getEncryptionProfile($profile_id);
if (empty($profile_id)) {
// New profile id empty: disable encryption.
- $this->client->disableEncryption();
+ $this->storage->disableEncryption();
}
elseif (empty($old_profile_id)) {
// Old profile id empty: enable encryption anew.
- $this->client->enableEncryption($profile);
+ $this->storage->enableEncryption($profile);
}
else {
// Changing encryption profiles: disable, then re-enable.
- $this->client->disableEncryption();
- $this->client->enableEncryption($profile);
+ $this->storage->disableEncryption();
+ $this->storage->enableEncryption($profile);
}
$this->state->resetCache();
- drupal_set_message($this->t('The configuration options have been saved.'));
+ $this->messenger()->addStatus($this->t('The configuration options have been saved.'));
}
}
diff --git a/modules/salesforce_encrypt/src/Plugin/SalesforceAuthProvider/SalesforceEncryptedOAuthPlugin.php b/modules/salesforce_encrypt/src/Plugin/SalesforceAuthProvider/SalesforceEncryptedOAuthPlugin.php
new file mode 100644
index 0000000..f81eeed
--- /dev/null
+++ b/modules/salesforce_encrypt/src/Plugin/SalesforceAuthProvider/SalesforceEncryptedOAuthPlugin.php
@@ -0,0 +1,281 @@
+getLoginUrl()));
+ $this->id = $id;
+ $this->encryptionProfileManager = $encryptionProfileManager;
+ $this->encryption = $encrypt;
+ $this->encryptionProfileId = $credentials->getEncryptionProfileId();
+ }
+
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ $configuration = array_merge(self::defaultConfiguration(), $configuration);
+ $storage = $container->get('salesforce.auth_token_storage');
+ /** @var EncryptServiceInterface $encrypt */
+ $encrypt = $container->get('encryption');
+ $encryptProfileMan = $container->get('encrypt.encryption_profile.manager');
+ if ($configuration['encryption_profile']) {
+ try {
+ $profile = $encryptProfileMan->getEncryptionProfile($configuration['encryption_profile']);
+ $configuration['consumer_key'] = $encrypt->decrypt($configuration['consumer_key'], $profile);
+ $configuration['consumer_secret'] = $encrypt->decrypt($configuration['consumer_secret'], $profile);
+ }
+ catch (\Exception $e) {
+ // Any exception here may cause WSOD, don't allow that to happen.
+ watchdog_exception('SFOAuthEncrypted', $e);
+ }
+ }
+ $cred = new OAuthEncryptedCredentials($configuration['consumer_key'], $configuration['login_url'], $configuration['consumer_secret'], $configuration['encryption_profile']);
+ return new static($configuration['id'], $cred, $container->get('salesforce.http_client_wrapper'), $storage, $encryptProfileMan, $encrypt);
+ }
+
+ public static function defaultConfiguration() {
+ $defaults = parent::defaultConfiguration();
+ return array_merge($defaults, [
+ 'encryption_profile' => NULL,
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hookEncryptionProfileDelete(EncryptionProfileInterface $profile) {
+ if ($this->encryptionProfile()->id() == $profile->id()) {
+ // @todo decrypt identity, access token, refresh token, consumer secret, consumer key and re-save
+ }
+ }
+
+ public function encryptionProfile() {
+ if ($this->encryptionProfile) {
+ return $this->encryptionProfile;
+ }
+ elseif (empty($this->encryptionProfileId)) {
+ return NULL;
+ }
+ else {
+ $this->encryptionProfile = $this->encryptionProfileManager
+ ->getEncryptionProfile($this->encryptionProfileId);
+ if (empty($this->encryptionProfile)) {
+ throw new EntityNotFoundException(['id' => $this->encryptionProfileId], 'encryption_profile');
+ }
+ return $this->encryptionProfile;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $options = $this
+ ->encryptionProfileManager
+ ->getEncryptionProfileNamesAsOptions();
+ $default = NULL;
+ try {
+ $profile = $this->encryptionProfile();
+ if (!empty($profile)) {
+ $default = $profile->id();
+ }
+ }
+ catch (EntityNotFoundException $e) {
+ $this->messenger()->addError($e->getFormattableMessage());
+ $this->messenger()->addError($this->t('Error while loading encryption profile. You will need to assign a new encryption profile and re-authenticate to Salesforce.'));
+ }
+
+ if (empty($options)) {
+ $this->messenger()->addError($this->t('Please create an encryption profile before adding an OAuth Encrypted provider.', ['@href' => Url::fromRoute('entity.encryption_profile.add_form')->toString()]));
+ }
+
+ $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. VALUE WILL BE ENCRYPTED ON FORM SUBMISSION.'),
+ '#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. VALUE WILL BE ENCRYPTED ON FORM SUBMISSION.'),
+ '#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,
+ ];
+ $form['encryption_profile'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Encryption Profile'),
+ '#description' => $this->t('Choose an encryption profile with which to encrypt Salesforce information.'),
+ '#options' => $options,
+ '#default_value' => $default,
+ '#required' => TRUE,
+ ];
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $this->setConfiguration($form_state->getValues());
+ $settings = $form_state->getValue('provider_settings');
+ $this->encryptionProfileId = $settings['encryption_profile'];
+ $consumer_key = $settings['consumer_key'];
+ $settings['consumer_key'] = $this->encrypt($settings['consumer_key']);
+ $settings['consumer_secret'] = $this->encrypt($settings['consumer_secret']);
+ $form_state->setValue('provider_settings', $settings);
+ parent::submitConfigurationForm($form, $form_state);
+
+ // 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' => self::getAuthCallbackUrl(),
+ 'response_type' => 'code',
+ 'client_id' => $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()]));
+ // $this->eventDispatcher->dispatch(SalesforceEvents::ERROR, new SalesforceErrorEvent($e));
+ }
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return string
+ * @throws \Drupal\encrypt\Exception\EncryptException
+ */
+ public function decrypt($value) {
+ return $this->encryption->decrypt($value, $this->encryptionProfile());
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return string
+ * @throws \Drupal\encrypt\Exception\EncryptException
+ */
+ public function encrypt($value) {
+ return $this->encryption->encrypt($value, $this->encryptionProfile());
+ }
+
+ public static function getAuthCallbackUrl() {
+ return Url::fromRoute('salesforce.oauth_callback', [], [
+ 'absolute' => TRUE,
+ 'https' => TRUE,
+ ])->toString();
+ }
+
+ public function getConsumerSecret() {
+ return $this->credentials->getConsumerSecret();
+ }
+
+ /**
+ * @return bool
+ * @throws \OAuth\Common\Http\Exception\TokenResponseException
+ * @see \Drupal\salesforce\Controller\SalesforceOAuthController
+ */
+ public function finalizeOauth() {
+ $this->requestAccessToken(\Drupal::request()->get('code'));
+ $token = $this->getAccessToken();
+
+ // Initialize identity.
+ $headers = [
+ 'Authorization' => 'OAuth ' . $token->getAccessToken(),
+ 'Content-type' => 'application/json',
+ ];
+ dpm($headers);
+ $data = $token->getExtraParams();
+ $response = $this->httpClient->retrieveResponse(new Uri($data['id']), [], $headers);
+ $identity = $this->parseIdentityResponse($response);
+ $this->storage->storeIdentity($this->service(), $identity);
+ return TRUE;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php b/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php
index 58f771f..b5e96ea 100644
--- a/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php
+++ b/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php
@@ -2,80 +2,13 @@
namespace Drupal\salesforce_encrypt\Rest;
-use Drupal\encrypt\EncryptionProfileInterface;
use Drupal\salesforce\Rest\RestClientInterface;
/**
* Objects, properties, and methods to communicate with the Salesforce REST API.
+ *
+ * @deprecated use SalesforceEncryptedAuthTokenStorage
*/
interface EncryptedRestClientInterface extends RestClientInterface {
- /**
- * Encrypts all sensitive salesforce config values.
- *
- * @param string $profile_id
- * Id of the Encrypt Profile to use for encryption.
- *
- * @return bool
- * TRUE if encryption was enabled or FALSE if it is already enabled
- *
- * @throws RuntimeException
- * if Salesforce encryption profile hasn't been selected
- */
- public function enableEncryption(EncryptionProfileInterface $profile);
-
- /**
- * Inverse of ::enableEncryption. Decrypts all sensitive salesforce config
- * values.
- *
- * @return bool
- * TRUE if encryption was disabled or FALSE if it is already disabled
- *
- * @throws RuntimeException
- * if Salesforce encryption profile hasn't been selected
- */
- public function disableEncryption();
-
- /**
- * Returns the EncryptionProfileInterface assigned to Salesforce Encrypt, or
- * NULL if no profile is assigned.
- *
- * @throws \Drupal\salesforce\EntityNotFoundException
- * if a profile is assigned, but cannot be loaded.
- *
- * @return \Drupal\encrypt\EncryptionProfileInterface | NULL
- */
- public function getEncryptionProfile();
-
- /**
- * Since we rely on a specific encryption profile, we need to respond in case
- * it gets deleted. Check to see if the profile being deleted is the one
- * assigned for encryption; if so, decrypt our config and disable encryption.
- *
- * @param \Drupal\encrypt\EncryptionProfileInterface $profile
- */
- public function hookEncryptionProfileDelete(EncryptionProfileInterface $profile);
-
- /**
- * Encrypts a value using the encryption profile given by salesforce_encrypt.profile.
- *
- * @param string $value
- * The value to encrypt.
- *
- * @return string
- * The encrypted value.
- */
- public function encrypt($value);
-
- /**
- * Decrypts a value using the encryption profile given by salesforce_encrypt.profile.
- *
- * @param string $value
- * The value to decrypt.
- *
- * @return string
- * The decrypted value.
- */
- public function decrypt($value);
-
}
diff --git a/modules/salesforce_encrypt/src/Rest/RestClient.php b/modules/salesforce_encrypt/src/Rest/RestClient.php
index a2be2b6..e74c53e 100644
--- a/modules/salesforce_encrypt/src/Rest/RestClient.php
+++ b/modules/salesforce_encrypt/src/Rest/RestClient.php
@@ -15,258 +15,15 @@ use Drupal\encrypt\EncryptionProfileInterface;
use Drupal\encrypt\EncryptionProfileManagerInterface;
use Drupal\salesforce\EntityNotFoundException;
use Drupal\salesforce\Rest\RestClient as SalesforceRestClient;
+use Drupal\salesforce\SalesforceAuthProviderPluginManager;
use GuzzleHttp\ClientInterface;
use Drupal\Component\Datetime\TimeInterface;
/**
* Objects, properties, and methods to communicate with the Salesforce REST API.
+ *
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
class RestClient extends SalesforceRestClient implements EncryptedRestClientInterface {
- use StringTranslationTrait;
-
- protected $encryption;
- protected $encryptionProfileManager;
- protected $encryptionProfileId;
-
- /**
- * The encryption profile to use when encrypting and decrypting data.
- *
- * @var \Drupal\encrypt\EncryptionProfileInterface
- */
- protected $encryptionProfile;
-
- /**
- * Construct a new Encrypted Rest Client.
- *
- * @param \GuzzleHttp\ClientInterface $http_client
- * The GuzzleHttp Client.
- * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
- * The config factory service.
- * @param \Drupal\Core\State\StateInterface $state
- * The state service.
- * @param \Drupal\Core\Cache\CacheBackendInterface $cache
- * The cache service.
- * @param \Drupal\Component\Serialization\Json $json
- * The JSON serializer service.
- * @param \Drupal\encrypt\EncryptServiceInterface $encryption
- * The encryption service.
- * @param \Drupal\encrypt\EncryptionProfileManagerInterface $encryptionProfileManager
- * The Encryption profile manager service.
- * @param \Drupal\Core\Lock\LockBackendInterface $lock
- * The lock backend service.
- */
- public function __construct(ClientInterface $http_client, ConfigFactoryInterface $config_factory, StateInterface $state, CacheBackendInterface $cache, Json $json, TimeInterface $time, EncryptServiceInterface $encryption, EncryptionProfileManagerInterface $encryptionProfileManager, LockBackendInterface $lock) {
- parent::__construct($http_client, $config_factory, $state, $cache, $json, $time);
- $this->encryption = $encryption;
- $this->encryptionProfileId = $this->state->get('salesforce_encrypt.profile');
- $this->encryptionProfileManager = $encryptionProfileManager;
- $this->encryptionProfile = NULL;
- $this->lock = $lock;
- }
-
- /**
- * Encrypts all sensitive salesforce config values.
- *
- * @throws RuntimeException if Salesforce if encryption was not enabled.
- */
- public function enableEncryption(EncryptionProfileInterface $profile) {
- if ($ret = $this->setEncryption($profile)) {
- $this->state->resetCache();
- }
- return $ret;
- }
-
- /**
- * Inverse of ::enableEncryption. Decrypts all sensitive salesforce config
- * values.
- *
- * @throws RuntimeException if Salesforce encryption can't be disabled
- */
- public function disableEncryption() {
- if ($ret = $this->setEncryption()) {
- $this->state->resetCache();
- }
- return $ret;
- }
-
- /**
- *
- */
- public function hookEncryptionProfileDelete(EncryptionProfileInterface $profile) {
- if ($this->encryptionProfileId == $profile->id()) {
- $this->disableEncryption();
- }
- }
-
- /**
- *
- */
- protected function setEncryption(EncryptionProfileInterface $profile = NULL) {
- if (!$this->lock->acquire('salesforce_encrypt')) {
- throw new \RuntimeException('Unable to acquire lock.');
- }
-
- $access_token = $this->getAccessToken();
- $refresh_token = $this->getRefreshToken();
- $identity = $this->getIdentity();
- $consumerKey = $this->getConsumerKey();
- $consumerSecret = $this->getConsumerSecret();
-
- $this->encryptionProfileId = $profile == NULL ? NULL : $profile->id();
- $this->encryptionProfile = $profile;
- $this->state->set('salesforce_encrypt.profile', $this->encryptionProfileId);
-
- $this->setAccessToken($access_token);
- $this->setRefreshToken($refresh_token);
- $this->setIdentity($identity);
- $this->setConsumerKey($consumerKey);
- $this->setConsumerSecret($consumerSecret);
-
- $this->lock->release('salesforce_encrypt');
- }
-
- /**
- * {@inheritdoc}
- */
- public function getEncryptionProfile() {
- if ($this->encryptionProfile) {
- return $this->encryptionProfile;
- }
- elseif (empty($this->encryptionProfileId)) {
- return NULL;
- }
- else {
- $this->encryptionProfile = $this->encryptionProfileManager
- ->getEncryptionProfile($this->encryptionProfileId);
- if (empty($this->encryptionProfile)) {
- throw new EntityNotFoundException(['id' => $this->encryptionProfileId], 'encryption_profile');
- }
- return $this->encryptionProfile;
- }
- }
-
- /**
- * Exception-handling wrapper around getEncryptionProfile().
- *
- * getEncryptionProfile() will throw an EntityNotFoundException exception
- * if it has an encryption profile ID but cannot load it. In this wrapper
- * we handle that exception by setting a helpful error message and allow
- * execution to proceed.
- *
- * @return \Drupal\encrypt\EncryptionProfileInterface | NULL
- * The encryption profile if it can be loaded, otherwise NULL.
- */
- protected function _getEncryptionProfile() {
- try {
- $profile = $this->getEncryptionProfile();
- }
- catch (EntityNotFoundException $e) {
- drupal_set_message($this->t('Error while loading encryption profile. You will need to assign a new encryption profile, then re-authenticate to Salesforce.', [':encrypt' => Url::fromRoute('salesforce_encrypt.settings')->toString(), ':oauth' => Url::fromRoute('salesforce.authorize')->toString()]), 'error');
- }
-
- return $profile;
- }
-
- /**
- * {@inheritdoc}
- */
- public function encrypt($value) {
- if (empty($this->_getEncryptionProfile())) {
- return $value;
- }
- else {
- return $this->encryption->encrypt($value, $this->_getEncryptionProfile());
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function decrypt($value) {
- if (empty($this->_getEncryptionProfile()) || empty($value) || mb_strlen($value) === 0) {
- return $value;
- }
- else {
- return $this->encryption->decrypt($value, $this->_getEncryptionProfile());
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function getAccessToken() {
- return $this->decrypt(parent::getAccessToken());
- }
-
- /**
- * {@inheritdoc}
- */
- public function setAccessToken($token) {
- return parent::setAccessToken($this->encrypt($token));
- }
-
- /**
- * {@inheritdoc}
- */
- public function getRefreshToken() {
- return $this->decrypt(parent::getRefreshToken());
- }
-
- /**
- * {@inheritdoc}
- */
- public function setRefreshToken($token) {
- return parent::setRefreshToken($this->encrypt($token));
- }
-
- /**
- * {@inheritdoc}
- */
- public function setIdentity($data) {
- if (is_array($data)) {
- $data = serialize($data);
- }
- return parent::setIdentity($this->encrypt($data));
- }
-
- /**
- * {@inheritdoc}
- */
- public function getIdentity() {
- $data = $this->decrypt(parent::getIdentity());
- if (!empty($data) && !is_array($data)) {
- $data = unserialize($data);
- }
- return $data;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getConsumerKey() {
- return $this->decrypt(parent::getConsumerKey());
- }
-
- /**
- * {@inheritdoc}
- */
- public function setConsumerKey($value) {
- return parent::setConsumerKey($this->encrypt($value));
- }
-
- /**
- * {@inheritdoc}
- */
- public function getConsumerSecret() {
- return $this->decrypt(parent::getConsumerSecret());
- }
-
- /**
- * {@inheritdoc}
- */
- public function setConsumerSecret($value) {
- return parent::setConsumerSecret($this->encrypt($value));
- }
-
}
diff --git a/modules/salesforce_encrypt/src/SalesforceEncryptServiceProvider.php b/modules/salesforce_encrypt/src/SalesforceEncryptServiceProvider.php
index 6146bfb..a602528 100644
--- a/modules/salesforce_encrypt/src/SalesforceEncryptServiceProvider.php
+++ b/modules/salesforce_encrypt/src/SalesforceEncryptServiceProvider.php
@@ -16,12 +16,8 @@ class SalesforceEncryptServiceProvider extends ServiceProviderBase {
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
- // Overrides salesforce.client class with our EncryptedRestClientInterface.
- $container->getDefinition('salesforce.client')
- ->setClass(RestClient::class)
- ->addArgument(new Reference('encryption'))
- ->addArgument(new Reference('encrypt.encryption_profile.manager'))
- ->addArgument(new Reference('lock'));
+ $container->getDefinition('salesforce.auth_token_storage')
+ ->setClass(SalesforceEncryptedAuthTokenStorage::CLASS);
}
}
diff --git a/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorage.php b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorage.php
new file mode 100644
index 0000000..59fd935
--- /dev/null
+++ b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorage.php
@@ -0,0 +1,91 @@
+authPluginManager) {
+ $this->authPluginManager = \Drupal::service('plugin.manager.salesforce.auth_providers');
+ }
+ $auth = SalesforceAuthConfig::load($service_id);
+ return $auth->getPlugin();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function retrieveAccessToken($service_id) {
+ $token = parent::retrieveAccessToken($service_id);
+ if ($token instanceof TokenInterface || !$this->service($service_id) instanceof SalesforceEncryptedOAuthPlugin) {
+ return $token;
+ }
+ $token = unserialize($this->service($service_id)->decrypt($token));
+ return $token;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function storeAccessToken($service_id, TokenInterface $token) {
+ if ($this->service($service_id) instanceof SalesforceEncryptedOAuthPlugin) {
+ $token = $this->service($service_id)->encrypt(serialize($token));
+ }
+ $this->state->set(self::getTokenStorageId($service_id), $token);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function storeIdentity($service_id, $identity) {
+ if ($this->service($service_id) instanceof SalesforceEncryptedOAuthPlugin) {
+ if (is_array($identity)) {
+ $identity = serialize($identity);
+ }
+ $identity = $this->service($service_id)->encrypt($identity);
+ }
+ $this->state->set(self::getIdentityStorageId($service_id), $identity);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function retrieveIdentity($service_id) {
+ $identity = parent::retrieveIdentity($service_id);
+ if (!$this->service($service_id) instanceof SalesforceEncryptedOAuthPlugin) {
+ return $identity;
+ }
+ $identity = $this->service($service_id)->decrypt($identity);
+ if (!empty($identity) && !is_array($identity)) {
+ $identity = unserialize($identity);
+ }
+ return $identity;
+ }
+
+
+}
\ No newline at end of file
diff --git a/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorageInterface.php b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorageInterface.php
new file mode 100644
index 0000000..6321e93
--- /dev/null
+++ b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorageInterface.php
@@ -0,0 +1,10 @@
+loginUser = $loginUser;
+ $this->encryptKeyId = $encryptKeyId;
+ }
+ public function getLoginUser() {
+ return $this->loginUser;
+ }
+ public function getEncryptKeyId() {
+ return $this->encryptKeyId;
+ }
+}
\ No newline at end of file
diff --git a/modules/salesforce_jwt/src/Plugin/SalesforceAuthProvider/SalesforceJWTPlugin.php b/modules/salesforce_jwt/src/Plugin/SalesforceAuthProvider/SalesforceJWTPlugin.php
new file mode 100644
index 0000000..0f7b094
--- /dev/null
+++ b/modules/salesforce_jwt/src/Plugin/SalesforceAuthProvider/SalesforceJWTPlugin.php
@@ -0,0 +1,256 @@
+getLoginUrl()));
+ $this->id = $id;
+ }
+
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ $configuration = array_merge(self::defaultConfiguration(), $configuration);
+ $cred = new JWTCredentials($configuration['consumer_key'], $configuration['login_url'], $configuration['login_user'], $configuration['encrypt_key']);
+ return new static($configuration['id'], $cred, $container->get('salesforce.http_client_wrapper'), $container->get('salesforce.auth_token_storage'));
+ }
+
+ public static function defaultConfiguration() {
+ $defaults = parent::defaultConfiguration();
+ return array_merge($defaults, [
+ 'login_user' => '',
+ 'encrypt_key' => '',
+ ]);
+ }
+
+ public function getLoginUrl() {
+ return $this->credentials->getLoginUrl();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ if (!$this->keyRepository()) {
+ $this->messenger()->addError($this->t('JWT Auth requires Key module. Please install before adding a JWT Auth config.'));
+ return $form;
+ }
+ if (!$this->keyRepository()->getKeyNamesAsOptions(['type' => 'authentication'])) {
+ $this->messenger()->addError($this->t('Please add an authentication key before creating a JWT Auth provider.', ['@href' => Url::fromRoute('entity.key.add_form')->toString()]));
+ return $form;
+ }
+ $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['login_user'] = [
+ '#title' => $this->t('Salesforce login user'),
+ '#type' => 'textfield',
+ '#description' => $this->t('User account to issue token to'),
+ '#required' => TRUE,
+ '#default_value' => $this->credentials->getLoginUser(),
+ ];
+
+ $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,
+ ];
+
+ // Can't use key-select here because its #process method is not firing on ajax, and the list is empty. DERP.
+ $form['encrypt_key'] = [
+ '#title' => 'Private Key',
+ '#type' => 'select',
+ '#options' => $this->keyRepository()->getKeyNamesAsOptions(['type' => 'authentication']),
+ '#required' => TRUE,
+ '#default_value' => $this->credentials->getEncryptKeyId(),
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ if (!$this->keyRepository()) {
+ $form_state->setError($form, $this->t('JWT Auth requires Key module. Please install before adding a JWT Auth config.'));
+ return;
+ }
+
+ parent::validateConfigurationForm($form, $form_state);
+ try {
+ $settings = $form_state->getValue('provider_settings');
+ $this->getToken($settings['login_url']);
+ \Drupal::messenger()->addStatus(t('Successfully connected to Salesforce as user %name.', ['%name' => $this->getIdentity()['display_name']]));
+ }
+ catch (\Exception $e) {
+ $form_state->setError($form, $this->t('Failed to connect to Salesforce: %message', ['%message' => $e->getMessage()]));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::submitConfigurationForm($form, $form_state);
+ $this->setConfiguration($form_state->getValues());
+ }
+
+ /**
+ * Gets a token from the given JWT OAuth endpoint.
+ */
+ protected function getToken($login_url) {
+ // Initialize access token.
+ $assertion = $this->generateAssertion();
+ $data = [
+ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+ 'assertion' => $assertion,
+ ];
+ $response = $this->httpClient->retrieveResponse(new Uri($login_url . static::AUTH_TOKEN_PATH), $data, ['Content-Type' => 'application/x-www-form-urlencoded']);
+ $token = $this->parseAccessTokenResponse($response);
+ $this->storage->storeAccessToken($this->service(), $token);
+
+ // 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 $token;
+ }
+
+ /**
+ * Refreshes an OAuth2 access token.
+ *
+ * @param TokenInterface $token
+ *
+ * @return TokenInterface $token
+ *
+ * @throws \OAuth\OAuth2\Service\Exception\MissingRefreshTokenException
+ * @throws \OAuth\Common\Http\Exception\TokenResponseException
+ */
+ public function refreshAccessToken(TokenInterface $token) {
+ $token = $this->getToken($this->getLoginUrl());
+ return $token;
+ }
+
+ /**
+ * Key repository wrapper.
+ *
+ * @return \Drupal\key\KeyRepository|FALSE
+ * The key repo.
+ */
+ protected function keyRepository() {
+ if (!\Drupal::hasService('key.repository')) {
+ return FALSE;
+ }
+ return \Drupal::service('key.repository');
+ }
+
+ /**
+ * Returns a JWT Assertion to authenticate.
+ *
+ * @return string
+ * JWT Assertion.
+ */
+ private function generateAssertion() {
+ $header = $this->generateAssertionHeader();
+ $claim = $this->generateAssertionClaim();
+ $header_encoded = $this->b64UrlEncode($header);
+ $claim_encoded = $this->b64UrlEncode($claim);
+ $encoded_string = $header_encoded . '.' . $claim_encoded;
+ $key = $this->keyRepository()->getKey($this->credentials->getEncryptKeyId())->getKeyValue();
+ openssl_sign($encoded_string, $signed, $key, 'sha256WithRSAEncryption');
+ $signed_encoded = $this->b64UrlEncode($signed);
+ $assertion = $encoded_string . '.' . $signed_encoded;
+ return $assertion;
+ }
+
+ /**
+ * Returns a JSON encoded JWT Header.
+ *
+ * @return string
+ * The encoded header.
+ */
+ private function generateAssertionHeader() {
+ $header = new \stdClass();
+ $header->alg = 'RS256';
+ return json_encode($header);
+ }
+
+ /**
+ * Returns a JSON encoded JWT Claim.
+ *
+ * @return string
+ * The encoded claim.
+ */
+ private function generateAssertionClaim() {
+ $claim = new \stdClass();
+ $claim->iss = $this->credentials->getConsumerKey();
+ $claim->sub = $this->credentials->getLoginUser();
+ $claim->aud = $this->credentials->getLoginUrl();
+ $claim->exp = \Drupal::time()->getCurrentTime() + 60;
+ return json_encode($claim);
+ }
+
+ /**
+ * Base 64 URL Safe Encoding.
+ *
+ * @param string $data
+ * String to encode.
+ *
+ * @return string
+ * Encoded string.
+ */
+ private function b64UrlEncode($data) {
+ return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php b/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php
index 066fc61..ec011e9 100644
--- a/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php
+++ b/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php
@@ -7,7 +7,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
- * Salesforce Mapping Delete Form .
+ * Salesforce Mapping Delete Form.
*/
class SalesforceMappingDeleteForm extends EntityConfirmFormBase {
diff --git a/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php b/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php
index 51bca52..f2a218c 100644
--- a/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php
+++ b/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php
@@ -35,7 +35,7 @@ abstract class SalesforceMappingFormCrudBase extends SalesforceMappingFormBase {
$object_type_options = $this->get_salesforce_object_type_options();
}
catch (\Exception $e) {
- $href = new Url('salesforce.authorize');
+ $href = new Url('salesforce.admin_config_salesforce');
drupal_set_message($this->t('Error when connecting to Salesforce. Please check your credentials and try again: %message', ['@href' => $href->toString(), '%message' => $e->getMessage()]), 'error');
return $form;
}
diff --git a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedIDs.php b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedIDs.php
index 55cd016..4e3afcb 100644
--- a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedIDs.php
+++ b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedIDs.php
@@ -64,8 +64,8 @@ class RelatedIDs extends SalesforceMappingFieldPluginBase {
}
$field = $entity->get($field_name);
- if (empty($field->getValue()) || is_null($field->entity)) {
- // This reference field is blank or the referenced entity no longer exists.
+ if (empty($field->getValue())) {
+ // This reference field is blank.
return;
}
diff --git a/salesforce.info.yml b/salesforce.info.yml
index 0e3ba3a..c6a3d3f 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 804acf8..03dc192 100644
--- a/salesforce.install
+++ b/salesforce.install
@@ -5,6 +5,9 @@
*/
use Drupal\Component\Serialization\Json;
+use Drupal\salesforce\Entity\SalesforceAuthConfig;
+use Drupal\salesforce\Token\SalesforceToken;
+use Drupal\Core\Utility\UpdateException;
/**
* Purge Salesforce module state variables.
@@ -23,7 +26,7 @@ function salesforce_uninstall() {
}
/**
- *
+ * Implements hook_requirements().
*/
function salesforce_requirements($phase) {
if ($phase != 'runtime') {
@@ -33,7 +36,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'])) {
@@ -45,6 +53,53 @@ function salesforce_requirements($phase) {
}
/**
+ * 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 create an auth provider to connect to Salesforce.', ['@href' => \Drupal\Core\Url::fromRoute('entity.salesforce_auth.add_form')]),
+ 'severity' => REQUIREMENT_WARNING
+ ];
+ }
+ elseif (!$authMan->getConfig()) {
+ $requirements += [
+ 'description' => t('Default auth provider has not been set. Please choose an auth provider to connect to Salesforce.', ['@href' => \Drupal\Core\Url::fromRoute('salesforce.auth_config')->toString()]),
+ 'severity' => REQUIREMENT_WARNING
+ ];
+ }
+ else {
+ $failMessage = t('Salesforce authentication failed. Please check your auth provider settings to connect to Salesforce.', ['@href' => \Drupal\Core\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;
+}
+
+/**
*
*/
function salesforce_fetch_new_tls() {
@@ -127,6 +182,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'),
@@ -147,7 +203,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),
];
}
@@ -204,3 +260,93 @@ 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.");
+ }
+
+ $config = \Drupal::configFactory()->getEditable('salesforce.settings');
+ $state = \Drupal::state();
+ $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 {
+ $encryption_enabled = FALSE;
+ $settings = [
+ // Get fresh values here from config, in case they were corrupted by failed decryption.
+ 'consumer_key' => $config->get('consumer_key'),
+ 'consumer_secret' => $config->get('consumer_secret'),
+ 'login_url' => $config->get('login_url'),
+ ];
+ // config to new plugin config system.
+ $values = [
+ 'id' => 'oauth_default',
+ 'label' => 'OAuth Default',
+ 'provider' => 'oauth',
+ 'provider_settings' => $settings,
+ ];
+
+ // Convert existing token to new token storage.
+ $access_token = $state->get('salesforce.access_token');
+ $refresh_token = $state->get('salesforce.refresh_token');
+
+ if (\Drupal::moduleHandler()->moduleExists('salesforce_encrypt')) {
+ // Not sure how to recover from exceptions here.
+ // Allow them to bubble up and prevent update.
+ $encryption_profile = $state->get('salesforce_encrypt.profile');
+ $profile = \Drupal::service('encrypt.encryption_profile.manager')
+ ->getEncryptionProfile($encryption_profile);
+ /** @var \Drupal\encrypt\EncryptServiceInterface $encryption */
+ $encryption = \Drupal::service('encryption');
+ $values['provider_settings']['consumer_key'] =
+ $encryption->decrypt($config->get('consumer_key'), $profile);
+ $values['provider_settings']['consumer_secret'] =
+ $encryption->decrypt($config->get('consumer_secret'), $profile);
+ $refresh_token = $encryption->decrypt($refresh_token, $profile);
+ $access_token = $encryption->decrypt($access_token, $profile);
+ if ((empty($values['provider_settings']['consumer_key']) && !empty($config->get('consumer_key')))
+ || (empty($values['provider_settings']['consumer_secret']) && !empty($config->get('consumer_secret')))
+ || (empty($access_token) && !empty($state->get('salesforce.access_token')))
+ || empty($refresh_token) && !empty($state->get('salesforce.refresh_token'))) {
+ throw new \Exception('Decryption of legacy Salesforce credentials failed. Automated update to Auth Providers not possible. Please disable Salesforce Encrypt module and re-attempt db update.');
+ }
+ $values['provider_settings']['encryption_profile'] = $encryption_profile;
+ $values['provider'] = 'oauth_encrypted';
+ $values['label'] = 'OAuth Default, Encrypted';
+ }
+
+ $oauth = SalesforceAuthConfig::create($values);
+ $oauth->save();
+
+ $token = new SalesforceToken($access_token, $refresh_token);
+ \Drupal::service('salesforce.auth_token_storage')
+ ->storeAccessToken('oauth_default', $token);
+
+ $config->set('salesforce_auth.provider', 'oauth_default');
+ $message = 'Default OAuth provider created from legacy credentials.';
+ }
+
+ $config->clear('consumer_key')
+ ->clear('consumer_secret')
+ ->clear('login_url')
+ ->save();
+
+ // Regardless of plugin conversion status, clean up the defunct values.
+
+ $state->deleteMultiple([
+ 'salesforce.access_token',
+ 'salesforce.refresh_token',
+ 'salesforce.identity',
+ 'salesforce.instance_url',
+ 'salesforce_encrypt.profile',
+ ]);
+ return $message;
+}
\ No newline at end of file
diff --git a/salesforce.links.action.yml b/salesforce.links.action.yml
new file mode 100644
index 0000000..f9d6829
--- /dev/null
+++ b/salesforce.links.action.yml
@@ -0,0 +1,14 @@
+salesforce_auth.add_action:
+ route_name: entity.salesforce_auth.add_form
+ title: 'Add Salesforce Auth Provider'
+ appears_on:
+ - entity.salesforce_auth.collection
+ - entity.salesforce_auth.edit_form
+ - salesforce.auth_config
+
+salesforce_auth.list_action:
+ route_name: entity.salesforce_auth.collection
+ title: 'Salesforce Auth Provider List'
+ appears_on:
+ - entity.salesforce_auth.add_form
+ - entity.salesforce_auth.edit_form
diff --git a/salesforce.links.menu.yml b/salesforce.links.menu.yml
index 2d7e8f1..92bae5c 100644
--- a/salesforce.links.menu.yml
+++ b/salesforce.links.menu.yml
@@ -17,16 +17,14 @@ salesforce.global_settings:
description: 'Manage global settings for Salesforce Suite.'
weight: -100
-salesforce.authorize:
- route_name: salesforce.authorize
+salesforce.auth_config:
+ route_name: salesforce.auth_config
parent: salesforce.admin_config_salesforce
title: Salesforce Authorization
- description: 'Manage OAuth consumer key and secret and authorize. View existing authorization details.'
- weight: 99
+ description: 'Salesforce Authorization.'
-salesforce.revoke:
- route_name: salesforce.revoke
- parent: salesforce.admin_config_salesforce
- title: Revoke Salesforce Authorization
- description: 'Revoke OAuth tokens.'
- weight: 100
+entity.salesforce_auth.collection:
+ route_name: entity.salesforce_auth.collection
+ parent: salesforce.auth_config
+ title: Salesforce Authorization Providers
+ description: 'Salesforce Authorization Providers.'
diff --git a/salesforce.links.task.yml b/salesforce.links.task.yml
new file mode 100644
index 0000000..f2715a9
--- /dev/null
+++ b/salesforce.links.task.yml
@@ -0,0 +1,9 @@
+salesforce.auth_config:
+ route_name: salesforce.auth_config
+ base_route: salesforce.auth_config
+ title: 'Authorization'
+
+entity.salesforce_auth.collection:
+ route_name: entity.salesforce_auth.collection
+ base_route: salesforce.auth_config
+ title: 'Providers'
diff --git a/salesforce.module b/salesforce.module
index d734e7e..cf7b92f 100644
--- a/salesforce.module
+++ b/salesforce.module
@@ -20,7 +20,7 @@ function salesforce_help($route_name, RouteMatchInterface $route_match) {
}
$client = \Drupal::service('salesforce.client');
if (!$client->isAuthorized()) {
- $output .= '
' . t('You must authorize your account with Salesforce in order to configure Salesforce Mappings.', [':authorize' => (new Url('salesforce.authorize'))->toString()]) . '
';
+ $output .= '' . t('You must authorize your account with Salesforce in order to configure Salesforce Mappings.', [':authorize' => (new Url('salesforce.admin_config_salesforce'))->toString()]) . '
';
}
return $output;
@@ -45,7 +45,7 @@ function salesforce_help($route_name, RouteMatchInterface $route_match) {
$output .= '';
$output .= '- ' . t('In Salesforce go to Your Name > Setup > Create > Apps then create a new Connected App. (Depending on your Salesforce instance, you may need to go to Your Name > Setup > Develop > Remote Access.)') . '
';
$output .= '- ' . t('Set the callback URL to: :url (SSL is required)',
- [':url' => str_replace('http:', 'https:', (new Url('salesforce.oauth_callback', [], ['absolute' => TRUE]))->toString())]) . '
';
+ [':url' => str_replace('http:', 'https:', (new Url('', [], ['absolute' => TRUE]))->toString())] . '/salesforce/oauth_callback') . '';
$output .= '- ' . t('Select at least "Perform requests on your behalf at any time" for OAuth Scope
as well as the appropriate other scopes for your application. Note that "Full access" does not include the "Perform requests on your behalf at any time" scope! Additional Information.', [':url' => 'https://help.salesforce.com/help/doc/en/remoteaccess_about.htm']) . '
';
$output .= '- ' . t('For more help see the salesforce.com documentation.', [':url' => 'https://www.salesforce.com/us/developer/docs/api_rest/Content/quickstart_oauth.htm']) . '
';
diff --git a/salesforce.permissions.yml b/salesforce.permissions.yml
index 3de400a..8277c47 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.routing.yml b/salesforce.routing.yml
index ffe3094..adfd368 100644
--- a/salesforce.routing.yml
+++ b/salesforce.routing.yml
@@ -1,28 +1,3 @@
-salesforce.oauth_callback:
- path: '/salesforce/oauth_callback'
- defaults:
- _controller: '\Drupal\salesforce\Controller\SalesforceController::oauthCallback'
- requirements:
- _permission: 'authorize salesforce'
-
-salesforce.authorize:
- path: '/admin/config/salesforce/authorize'
- defaults:
- _form: '\Drupal\salesforce\Form\AuthorizeForm'
- _title: 'Salesforce Authorization'
- _description: 'Manage Salesforce OAuth consumer key and secret and authorize. View existing Salesforce authorization details.'
- requirements:
- _permission: 'authorize salesforce'
-
-salesforce.revoke:
- path: '/admin/config/salesforce/revoke'
- defaults:
- _form: '\Drupal\salesforce\Form\RevokeAuthorizationForm'
- _title: 'Revoke Salesforce Authorization'
- _description: 'Revoke OAuth tokens.'
- requirements:
- _permission: 'authorize salesforce'
-
salesforce.global_settings:
path: '/admin/config/salesforce/settings'
defaults:
@@ -49,3 +24,64 @@ salesforce.structure_index:
_description: 'Manage Salesforce mappings.'
requirements:
_permission: 'administer salesforce'
+
+salesforce.auth_config:
+ path: '/admin/config/salesforce/authorize'
+ defaults:
+ _form: '\Drupal\salesforce\Form\SalesforceAuthSettings'
+ _title: 'Salesforce Authorization Config'
+ requirements:
+ _permission: 'authorize salesforce'
+
+entity.salesforce_auth.collection:
+ path: '/admin/config/salesforce/authorize/list'
+ defaults:
+ _entity_list: 'salesforce_auth'
+ _title: 'Salesforce Authorization Config'
+ requirements:
+ _permission: 'authorize salesforce'
+ options:
+ no_cache: TRUE
+
+entity.salesforce_auth.edit_form:
+ path: '/admin/config/salesforce/authorize/edit/{salesforce_auth}'
+ defaults:
+ _entity_form: 'salesforce_auth.default'
+ requirements:
+ _entity_access: 'salesforce_auth.update'
+ options:
+ no_cache: TRUE
+
+entity.salesforce_auth.add_form:
+ path: '/admin/config/salesforce/authorize/add'
+ defaults:
+ _entity_form: 'salesforce_auth.default'
+ requirements:
+ _entity_create_access: 'salesforce_auth'
+ options:
+ no_cache: TRUE
+
+entity.salesforce_auth.revoke:
+ path: '/admin/config/salesforce/authorize/revoke/{salesforce_auth}'
+ defaults:
+ _entity_form: 'salesforce_auth.revoke'
+ requirements:
+ _permission: 'authorize salesforce'
+ options:
+ no_cache: TRUE
+
+entity.salesforce_auth.delete_form:
+ path: '/admin/config/salesforce/authorize/delete/{salesforce_auth}'
+ defaults:
+ _entity_form: 'salesforce_auth.delete'
+ requirements:
+ _permission: 'authorize salesforce'
+ options:
+ no_cache: TRUE
+
+salesforce.oauth_callback:
+ path: '/salesforce/oauth_callback'
+ defaults:
+ _controller: '\Drupal\salesforce\Controller\SalesforceOAuthController::oauthCallback'
+ requirements:
+ _permission: 'authorize salesforce'
diff --git a/salesforce.services.yml b/salesforce.services.yml
index a91e46c..a36c875 100644
--- a/salesforce.services.yml
+++ b/salesforce.services.yml
@@ -1,4 +1,16 @@
services:
salesforce.client:
class: Drupal\salesforce\Rest\RestClient
- arguments: ['@http_client', '@config.factory', '@state', '@cache.default', '@serialization.json', '@datetime.time']
+ arguments: ['@http_client', '@config.factory', '@state', '@cache.default', '@serialization.json', '@datetime.time', '@plugin.manager.salesforce.auth_providers']
+
+ 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 0000000..40e6aa9
--- /dev/null
+++ b/src/Client/HttpClientWrapper.php
@@ -0,0 +1,29 @@
+httpClient = $httpClient;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function retrieveResponse(
+ UriInterface $endpoint,
+ $requestBody,
+ array $extraHeaders = array(),
+ $method = 'POST'
+ ) {
+ $response = $this->httpClient->request($method, $endpoint->getAbsoluteUri(), ['headers' => $extraHeaders, 'form_params' => $requestBody]);
+ return $response->getBody()->getContents();
+ }
+
+}
\ No newline at end of file
diff --git a/src/Consumer/OAuthCredentials.php b/src/Consumer/OAuthCredentials.php
new file mode 100644
index 0000000..8b32f37
--- /dev/null
+++ b/src/Consumer/OAuthCredentials.php
@@ -0,0 +1,20 @@
+consumerSecret = $consumerSecret;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCallbackUrl() {
+ return SalesforceOAuthPlugin::getAuthCallbackUrl();
+ }
+
+}
\ No newline at end of file
diff --git a/src/Consumer/SalesforceCredentials.php b/src/Consumer/SalesforceCredentials.php
new file mode 100644
index 0000000..f8ebd57
--- /dev/null
+++ b/src/Consumer/SalesforceCredentials.php
@@ -0,0 +1,26 @@
+loginUrl = $loginUrl;
+ $this->consumerKey = $consumerKey;
+ }
+
+ public function getConsumerKey() {
+ return $this->consumerKey;
+ }
+
+ public function getLoginUrl() {
+ return $this->loginUrl;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Consumer/SalesforceCredentialsInterface.php b/src/Consumer/SalesforceCredentialsInterface.php
new file mode 100644
index 0000000..60f39f8
--- /dev/null
+++ b/src/Consumer/SalesforceCredentialsInterface.php
@@ -0,0 +1,11 @@
+getPlugin();
+ $row['label'] = $entity->label();
+ $row['url'] = $plugin->getLoginUrl();
+ $row['key'] = substr($plugin->getConsumerKey(), 0, 16) . '...';
+ $row['type'] = $plugin->label();
+ return $row + parent::buildRow($entity);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildHeader() {
+ $header['label'] = [
+ 'data' => $this->t('Label'),
+ ];
+ $header['url'] = [
+ 'data' => $this->t('Login URL'),
+ ];
+ $header['key'] = [
+ 'data' => $this->t('Consumer Key'),
+ ];
+ $header['type'] = [
+ 'data' => $this->t('Auth Type'),
+ ];
+
+ return $header + parent::buildHeader();
+ }
+
+}
diff --git a/src/Controller/SalesforceController.php b/src/Controller/SalesforceController.php
deleted file mode 100644
index e94e601..0000000
--- a/src/Controller/SalesforceController.php
+++ /dev/null
@@ -1,90 +0,0 @@
-client = $rest;
- $this->http_client = $http_client;
- $this->url_generator = $url_generator;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('salesforce.client'),
- $container->get('http_client'),
- $container->get('url_generator')
- );
- }
-
- /**
- *
- */
- protected function request() {
- return \Drupal::request();
- }
-
- /**
- *
- */
- protected function successMessage() {
- drupal_set_message(t('Successfully connected to %endpoint', ['%endpoint' => $this->client->getInstanceUrl()]));
- }
-
- /**
- * OAuth step 2: Callback for the oauth redirect URI.
- *
- * Complete OAuth handshake by exchanging an authorization code for an access
- * token.
- */
- public function oauthCallback() {
- // If no code is provided, return access denied.
- if (empty($this->request()->get('code'))) {
- throw new AccessDeniedHttpException();
- }
-
- $data = urldecode(UrlHelper::buildQuery([
- 'code' => $this->request()->get('code'),
- 'grant_type' => 'authorization_code',
- 'client_id' => $this->client->getConsumerKey(),
- 'client_secret' => $this->client->getConsumerSecret(),
- 'redirect_uri' => $this->client->getAuthCallbackUrl(),
- ]));
- $url = $this->client->getAuthTokenUrl();
- $headers = [
- // This is an undocumented requirement on SF's end.
- 'Content-Type' => 'application/x-www-form-urlencoded',
- ];
-
- $response = $this->http_client->post($url, ['headers' => $headers, 'body' => $data]);
-
- $this->client->handleAuthResponse($response);
-
- $this->successMessage();
-
- return new RedirectResponse($this->url_generator->generateFromRoute('salesforce.authorize', [], ["absolute" => TRUE], FALSE));
- }
-
-}
diff --git a/src/Controller/SalesforceOAuthController.php b/src/Controller/SalesforceOAuthController.php
new file mode 100644
index 0000000..9f25fdb
--- /dev/null
+++ b/src/Controller/SalesforceOAuthController.php
@@ -0,0 +1,80 @@
+request = $stack->getCurrentRequest();
+ $this->messenger = $messenger;
+ $this->tempStore = $tempStoreFactory->get('salesforce_oauth');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('request_stack'),
+ $container->get('messenger'),
+ $container->get('user.private_tempstore')
+ );
+ }
+
+ /**
+ * Pass-through to OAuth plugin.
+ */
+ public function oauthCallback() {
+ if (empty($this->request->get('code'))) {
+ throw new AccessDeniedHttpException();
+ }
+ $configId = $this->tempStore->get('config_id');
+
+ if (empty($configId) || !($config = SalesforceAuthConfig::load($configId)) || !($config->getPlugin() instanceof SalesforceOAuthPluginInterface)) {
+ $this->messenger->addError('No OAuth config found. Please try again.');
+ return new RedirectResponse(Url::fromRoute('entity.salesforce_auth.collection')->toString());
+ }
+
+ /** @var \Drupal\salesforce\SalesforceOAuthPluginInterface $oauth */
+ $oauth = $config->getPlugin();
+ if ($oauth->finalizeOauth()) {
+ $this->messenger()->addStatus(t('Successfully connected to Salesforce.'));
+ }
+ else {
+ $this->messenger()->addError(t('Salesforce auth failed.'));
+ }
+ return new RedirectResponse(Url::fromRoute('entity.salesforce_auth.collection')->toString());
+ }
+
+}
diff --git a/src/Entity/SalesforceAuthConfig.php b/src/Entity/SalesforceAuthConfig.php
new file mode 100644
index 0000000..0ae2f34
--- /dev/null
+++ b/src/Entity/SalesforceAuthConfig.php
@@ -0,0 +1,138 @@
+id;
+ }
+
+ /**
+ * Label getter.
+ */
+ public function label() {
+ return $this->label;
+ }
+
+ /**
+ * Plugin getter.
+ *
+ * @return \Drupal\salesforce\SalesforceAuthProviderInterface
+ */
+ public function getPlugin() {
+ $settings = $this->provider_settings ?: [];
+ $settings += ['id' => $this->id()];
+ return $this->provider ? $this->authManager()->createInstance($this->provider, $settings) : 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
+ */
+ 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.
+ *
+ * @param string $type
+ * The plugin type to use.
+ *
+ * @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;
+ }
+
+ public function calculateDependencies() {
+ return parent::calculateDependencies(); // TODO: Change the autogenerated stub
+ }
+}
\ No newline at end of file
diff --git a/src/Form/AuthorizeForm.php b/src/Form/AuthorizeForm.php
deleted file mode 100644
index 52b6669..0000000
--- a/src/Form/AuthorizeForm.php
+++ /dev/null
@@ -1,212 +0,0 @@
-sf_client = $salesforce_client;
- $this->state = $state;
- $this->eventDispatcher = $event_dispatcher;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('config.factory'),
- $container->get('salesforce.client'),
- $container->get('state'),
- $container->get('event_dispatcher')
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFormId() {
- return 'salesforce_oauth';
- }
-
- /**
- * {@inheritdoc}
- */
- protected function getEditableConfigNames() {
- return [
- 'salesforce.settings',
- ];
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildForm(array $form, FormStateInterface $form_state) {
- $config = $this->config('salesforce.settings');
- $encrypted = is_subclass_of($this->sf_client, 'Drupal\salesforce_encrypt\Rest\EncryptedRestClientInterface');
- $url = new Url('salesforce.oauth_callback', [], ['absolute' => TRUE]);
- drupal_set_message($this->t('Callback URL: :url', [':url' => str_replace('http:', 'https:', $url->toString())]));
-
- $form['creds'] = [
- '#title' => $this->t('API / OAuth Connection Settings'),
- '#type' => 'details',
- '#open' => TRUE,
- '#description' => $this->t('Authorize this website to communicate with Salesforce by entering the consumer key and secret from a remote application. Submitting the form will redirect you to Salesforce where you will be asked to grant access.'),
- ];
- $form['creds']['consumer_key'] = [
- '#title' => $this->t('Salesforce consumer key'),
- '#type' => 'textfield',
- '#description' => $this->t('Consumer key of the Salesforce remote application you want to grant access to'),
- '#required' => TRUE,
- '#default_value' => $encrypted ? $this->sf_client->decrypt($config->get('consumer_key')) : $config->get('consumer_key'),
- ];
- $form['creds']['consumer_secret'] = [
- '#title' => $this->t('Salesforce consumer secret'),
- '#type' => 'textfield',
- '#description' => $this->t('Consumer secret of the Salesforce remote application you want to grant access to'),
- '#required' => TRUE,
- '#default_value' => $encrypted ? $this->sf_client->decrypt($config->get('consumer_secret')) : $config->get('consumer_secret'),
- ];
- $form['creds']['login_url'] = [
- '#title' => $this->t('Login URL'),
- '#type' => 'textfield',
- '#default_value' => empty($config->get('login_url')) ? 'https://login.salesforce.com' : $config->get('login_url'),
- '#description' => $this->t('Enter a login URL, either https://login.salesforce.com or https://test.salesforce.com.'),
- '#required' => TRUE,
- ];
-
- // If fully configured, attempt to connect to Salesforce and return a list
- // of resources.
- if ($this->sf_client->isAuthorized()) {
- $form['creds']['#open'] = FALSE;
- $form['creds']['#description'] = $this->t('Your Salesforce salesforce instance is currently authorized. Enter credentials here only to change credentials.');
- try {
- $resources = $this->sf_client->listResources();
- foreach ($resources->resources as $key => $path) {
- $items[] = $key . ': ' . $path;
- }
- if (!empty($items)) {
- $form['resources'] = [
- '#title' => $this->t('Your Salesforce instance is authorized and has access to the following resources:'),
- '#items' => $items,
- '#theme' => 'item_list',
- ];
- }
- }
- catch (\Exception $e) {
- // Do not allow any exceptions to interfere with displaying this page.
- drupal_set_message($e->getMessage(), 'warning');
- $this->eventDispatcher->dispatch(SalesforceEvents::ERROR, new SalesforceErrorEvent($e));
- }
- }
- elseif (!$form_state->getUserInput()) {
- // Don't set this message if the form was submitted.
- drupal_set_message(t('Salesforce needs to be authorized to connect to this website.'), 'error');
- }
- $form = parent::buildForm($form, $form_state);
- $form['creds']['actions'] = $form['actions'];
- unset($form['actions']);
- return $form;
- }
-
- /**
- * Return whether or not the given URL is a valid endpoint.
- *
- * @return bool
- */
- public static function validEndpoint($url) {
- return UrlHelper::isValid($url, TRUE);
- }
-
- /**
- *
- */
- public function validateForm(array &$form, FormStateInterface $form_state) {
- if (!self::validEndpoint($form_state->getValue('login_url'))) {
- $form_state->setErrorByName('login_url', t('Please enter a valid Salesforce login URL.'));
- }
-
- if (!is_numeric($form_state->getValue('consumer_secret'))) {
- $form_state->setErrorByName('consumer_secret', t('Please enter a valid consumer secret.'));
- }
-
- }
-
- /**
- * {@inheritdoc}
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
- $values = $form_state->getValues();
- $this->sf_client->setConsumerKey($values['consumer_key']);
- $this->sf_client->setConsumerSecret($values['consumer_secret']);
- $this->sf_client->setLoginUrl($values['login_url']);
-
- try {
- $path = $this->sf_client->getAuthEndpointUrl();
- $query = [
- 'redirect_uri' => $this->sf_client->getAuthCallbackUrl(),
- 'response_type' => 'code',
- 'client_id' => $this->sf_client->getConsumerKey(),
- ];
-
- // 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.
- $form_state->setResponse(new TrustedRedirectResponse($path . '?' . http_build_query($query), 302));
- }
- catch (RequestException $e) {
- drupal_set_message(t("Error during authorization: %message", ['%message' => $e->getMessage()]), 'error');
- $this->eventDispatcher->dispatch(SalesforceEvents::ERROR, new SalesforceErrorEvent($e));
- }
- }
-
-}
diff --git a/src/Form/RevokeAuthorizationForm.php b/src/Form/RevokeAuthorizationForm.php
deleted file mode 100644
index a573632..0000000
--- a/src/Form/RevokeAuthorizationForm.php
+++ /dev/null
@@ -1,114 +0,0 @@
-sf_client = $salesforce_client;
- $this->eventDispatcher = $event_dispatcher;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('config.factory'),
- $container->get('salesforce.client'),
- $container->get('event_dispatcher')
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFormId() {
- return 'salesforce_oauth';
- }
-
- /**
- * {@inheritdoc}
- */
- protected function getEditableConfigNames() {
- return [
- 'salesforce.settings',
- ];
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildForm(array $form, FormStateInterface $form_state) {
- if (!$this->sf_client->isAuthorized()) {
- drupal_set_message($this->t('Drupal is not authenticated to Salesforce.'), 'warning');
- return;
- }
- $form = parent::buildForm($form, $form_state);
- $form['actions']['#title'] = 'Are you sure you want to revoke authorization?';
- $form['actions']['#type'] = 'details';
- $form['actions']['#open'] = TRUE;
- $form['actions']['#description'] = t('Revoking authorization will destroy Salesforce OAuth and refresh tokens. Drupal will no longer be authorized to communicate with Salesforce.');
- $form['actions']['submit']['#value'] = t('Revoke authorization');
-
- // By default, render the form using system-config-form.html.twig.
- $form['#theme'] = 'system_config_form';
-
- return $form;
- }
-
- /**
- * {@inheritdoc}
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
- $this->sf_client->setAccessToken('');
- $this->sf_client->setRefreshToken('');
- $this->sf_client->setInstanceUrl('');
- $this->sf_client->setIdentity(FALSE);
- drupal_set_message($this->t('Salesforce OAuth tokens have been revoked.'));
- $this->eventDispatcher->dispatch(SalesforceEvents::NOTICE, new SalesforceNoticeEvent(NULL, "Salesforce OAuth tokens revoked."));
- }
-
-}
\ No newline at end of file
diff --git a/src/Form/SalesforceAuthDeleteForm.php b/src/Form/SalesforceAuthDeleteForm.php
new file mode 100644
index 0000000..7578fe7
--- /dev/null
+++ b/src/Form/SalesforceAuthDeleteForm.php
@@ -0,0 +1,58 @@
+t('Are you sure you want to delete the Auth Config %name?', ['%name' => $this->entity->label()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ return $this->entity->toUrl('collection');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfirmText() {
+ return $this->t('Delete');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ parent::validateForm($form, $form_state);
+ if ($form_state->getErrors()) {
+ return;
+ }
+ if (\Drupal::config('salesforce.settings')->get('salesforce_auth.provider') == $this->entity->id()) {
+ $form_state->setError($form, $this->t('You cannot delete the default auth provider. Please assign a new auth provider before deleting the active one.', ['@href' => Url::fromRoute('salesforce.auth_config')->toString()]));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->entity->delete();
+
+ // Set a message that the entity was deleted.
+ drupal_set_message($this->t('Auth Config %label was deleted.', [
+ '%label' => $this->entity->label(),
+ ]));
+
+ $form_state->setRedirectUrl($this->getCancelUrl());
+ }
+
+}
\ No newline at end of file
diff --git a/src/Form/SalesforceAuthForm.php b/src/Form/SalesforceAuthForm.php
new file mode 100644
index 0000000..49045da
--- /dev/null
+++ b/src/Form/SalesforceAuthForm.php
@@ -0,0 +1,137 @@
+entity;
+ $form['label'] = [
+ '#title' => $this->t('Label'),
+ '#type' => 'textfield',
+ '#description' => $this->t('User-facing label for this project, e.g. "OAuth Full Sandbox"'),
+ '#default_value' => $auth->label(),
+ ];
+
+ $form['id'] = [
+ '#type' => 'machine_name',
+ '#default_value' => $auth->id(),
+ '#maxlength' => 32,
+ '#machine_name' => [
+ 'exists' => [$this, 'exists'],
+ 'source' => ['label'],
+ ],
+ ];
+
+ // This is the element that contains all of the dynamic parts of the form.
+ $form['settings'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Settings'),
+ '#open' => TRUE,
+ ];
+
+ $form['settings']['provider'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Auth provider'),
+ '#options' => $auth->getPluginsAsOptions(),
+ '#required' => TRUE,
+ '#default_value' => $auth->getPluginId(),
+ '#ajax' => [
+ 'callback' => [$this, 'ajaxUpdateSettings'],
+ 'event' => 'change',
+ 'wrapper' => 'auth-settings',
+ ],
+ ];
+ $default = [
+ '#type' => 'container',
+ '#title' => $this->t('Auth provider settings'),
+ '#title_display' => FALSE,
+ '#tree' => TRUE,
+ '#prefix' => '',
+ '#suffix' => '
',
+ ];
+ $form['settings']['provider_settings'] = $default;
+ if ($auth->getPlugin()) {
+ $form['settings']['provider_settings'] += $auth->getPlugin()
+ ->buildConfigurationForm([], $form_state);
+ }
+ elseif ($form_state->getValue('provider')) {
+ $plugin = $this->entity->authManager()->createInstance($form_state->getValue('provider'));
+ $form['settings']['provider_settings'] += $plugin->buildConfigurationForm([], $form_state);
+ }
+ else {
+ $form['settings']['provider_settings'] = $default;
+ }
+ return parent::form($form, $form_state);
+ }
+
+ /**
+ * AJAX callback to update the dynamic settings on the form.
+ *
+ * @param array $form
+ * The form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return array
+ * The element to update in the form.
+ */
+ public function ajaxUpdateSettings(array &$form, FormStateInterface $form_state) {
+ return $form['settings']['provider_settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ parent::validateForm($form, $form_state);
+
+ if (!$form_state->isSubmitted()) {
+ return;
+ }
+
+ $this->entity->getPlugin()->validateConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ parent::submitForm($form, $form_state);
+ $this->entity->getPlugin()->submitConfigurationform($form, $form_state);
+ $form_state->setRedirectUrl($this->entity->toUrl('collection'));
+ }
+
+ /**
+ * Determines if the config already exists.
+ *
+ * @param string $id
+ * The config ID.
+ *
+ * @return bool
+ * TRUE if the config exists, FALSE otherwise.
+ */
+ public function exists($id) {
+ $action = \Drupal::entityTypeManager()->getStorage($this->entity->getEntityTypeId())->load($id);
+ return !empty($action);
+ }
+
+}
diff --git a/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php b/src/Form/SalesforceAuthRevokeForm.php
similarity index 63%
copy from modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php
copy to src/Form/SalesforceAuthRevokeForm.php
index 066fc61..83941b4 100644
--- a/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php
+++ b/src/Form/SalesforceAuthRevokeForm.php
@@ -1,28 +1,24 @@
t('Are you sure you want to delete the mapping %name?', ['%name' => $this->entity->label()]);
+ return $this->t('Are you sure you want to delete the Auth Config %name?', ['%name' => $this->entity->label()]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
- return new Url('entity.salesforce_mapping.list');
+ return $this->entity->toUrl('collection');
}
/**
@@ -39,11 +35,11 @@ class SalesforceMappingDeleteForm extends EntityConfirmFormBase {
$this->entity->delete();
// Set a message that the entity was deleted.
- drupal_set_message($this->t('Salesforce %label was deleted.', [
+ drupal_set_message($this->t('Auth Config %label was deleted.', [
'%label' => $this->entity->label(),
]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
-}
+}
\ No newline at end of file
diff --git a/src/Form/SalesforceAuthSettings.php b/src/Form/SalesforceAuthSettings.php
new file mode 100644
index 0000000..a1e4217
--- /dev/null
+++ b/src/Form/SalesforceAuthSettings.php
@@ -0,0 +1,94 @@
+salesforceAuth = $salesforceAuth;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('plugin.manager.salesforce.auth_providers')
+ );
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'salesforce_auth_config';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditableConfigNames() {
+ return ['salesforce.settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ if (!$this->salesforceAuth->hasProviders()) {
+ return ['#markup'=> 'No auth providers have been enabled. Please enable an auth provider and create an auth config before continuing.'];
+ }
+ $config = $this->config('salesforce.settings');
+ $form = parent::buildForm($form, $form_state);
+ $options = [];
+ /** @var \Drupal\salesforce\Entity\SalesforceAuthConfig $provider **/
+ foreach(\Drupal::service('plugin.manager.salesforce.auth_providers')->getProviders() as $provider) {
+ $options[$provider->id()] = $provider->label() . ' (' . $provider->getPlugin()->label() . ')';
+ }
+ if (empty($options)) {
+ return ['#markup'=> 'No auth providers found. Please add an auth provider before continuing.'];
+ }
+ $options = ['' => '- None -'] + $options;
+ $form['provider'] = [
+ '#type' => 'radios',
+ '#title' => $this->t('Choose a default auth provider'),
+ '#options' => $options,
+ '#default_value' => $config->get('salesforce_auth.provider') ? $config->get('salesforce_auth.provider') : '',
+ ];
+ $form['#theme'] = 'system_config_form';
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->config('salesforce.settings')
+ ->set('salesforce_auth.provider', $form_state->getValue('provider') ? $form_state->getValue('provider') : NULL)
+ ->save();
+
+ $this->messenger()->addStatus($this->t('Authorization settings have been saved.'));
+ \Drupal::service('event_dispatcher')->dispatch(SalesforceEvents::NOTICE, new SalesforceNoticeEvent(NULL, "Authorization provider changed to %provider.", ['%provider' => $form_state->getValue('provider')]));
+ }
+
+}
\ No newline at end of file
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index dad26cd..a9274e4 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -91,7 +91,7 @@ class SettingsForm extends ConfigFormBase {
$versions = $this->getVersionOptions();
}
catch (\Exception $e) {
- $href = new Url('salesforce.authorize');
+ $href = new Url('salesforce.admin_config_salesforce');
drupal_set_message($this->t('Error when connecting to Salesforce. Please check your credentials and try again: %message', ['@href' => $href->toString(), '%message' => $e->getMessage()]), 'error');
}
diff --git a/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php b/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php
new file mode 100644
index 0000000..b59c279
--- /dev/null
+++ b/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php
@@ -0,0 +1,161 @@
+getLoginUrl()));
+ $this->id = $id;
+ }
+
+ 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'));
+ }
+
+
+ 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' => self::getAuthCallbackUrl(),
+ '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()]));
+ // $this->eventDispatcher->dispatch(SalesforceEvents::ERROR, new SalesforceErrorEvent($e));
+ }
+ }
+
+ public static function getAuthCallbackUrl() {
+ return Url::fromRoute('salesforce.oauth_callback', [], [
+ 'absolute' => TRUE,
+ 'https' => TRUE,
+ ])->toString();
+ }
+
+ public function getConsumerSecret() {
+ return $this->credentials->getConsumerSecret();
+ }
+
+ /**
+ * @return bool
+ * @throws \OAuth\Common\Http\Exception\TokenResponseException
+ * @see \Drupal\salesforce\Controller\SalesforceOAuthController
+ */
+ 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Rest/RestClient.php b/src/Rest/RestClient.php
index e798bf6..da47911 100644
--- a/src/Rest/RestClient.php
+++ b/src/Rest/RestClient.php
@@ -4,21 +4,20 @@ namespace Drupal\salesforce\Rest;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
-use Drupal\Component\Utility\Unicode;
-use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\State\StateInterface;
-use Drupal\Core\Url;
use Drupal\salesforce\SelectQueryInterface;
use Drupal\salesforce\SFID;
use Drupal\salesforce\SObject;
use Drupal\salesforce\SelectQuery;
use Drupal\salesforce\SelectQueryResult;
+use Drupal\salesforce\SalesforceAuthProviderPluginManager;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
-use GuzzleHttp\Psr7\Response;
use Drupal\Component\Datetime\TimeInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+use Zend\Diactoros\Exception\DeprecatedMethodException;
/**
* Objects, properties, and methods to communicate with the Salesforce REST API.
@@ -88,6 +87,34 @@ class RestClient implements RestClientInterface {
*/
protected $json;
+ /**
+ * Auth provider manager.
+ *
+ * @var \Drupal\salesforce\SalesforceAuthProviderPluginManager
+ */
+ protected $authManager;
+
+ /**
+ * Active auth provider.
+ *
+ * @var \Drupal\salesforce\SalesforceAuthProviderInterface
+ */
+ protected $authProvider;
+
+ /**
+ * Active auth provider config.
+ *
+ * @var \Drupal\salesforce\Entity\SalesforceAuthConfig
+ */
+ protected $authConfig;
+
+ /**
+ * Active auth token.
+ *
+ * @var \OAuth\OAuth2\Token\TokenInterface
+ */
+ protected $authToken;
+
protected $httpClientOptions;
const CACHE_LIFETIME = 300;
@@ -107,7 +134,7 @@ class RestClient implements RestClientInterface {
* @param \Drupal\Component\Serialization\Json $json
* The JSON serializer service.
*/
- public function __construct(ClientInterface $http_client, ConfigFactoryInterface $config_factory, StateInterface $state, CacheBackendInterface $cache, Json $json, TimeInterface $time) {
+ public function __construct(ClientInterface $http_client, ConfigFactoryInterface $config_factory, StateInterface $state, CacheBackendInterface $cache, Json $json, TimeInterface $time, SalesforceAuthProviderPluginManager $authManager) {
$this->configFactory = $config_factory;
$this->httpClient = $http_client;
$this->mutableConfig = $this->configFactory->getEditable('salesforce.settings');
@@ -117,6 +144,10 @@ class RestClient implements RestClientInterface {
$this->json = $json;
$this->time = $time;
$this->httpClientOptions = [];
+ $this->authManager = $authManager;
+ $this->authProvider = $authManager->getProvider();
+ $this->authConfig = $authManager->getConfig();
+ $this->authToken = $authManager->getToken();
return $this;
}
@@ -124,19 +155,24 @@ class RestClient implements RestClientInterface {
* Determine if this SF instance is fully configured.
*/
public function isAuthorized() {
- return $this->getConsumerKey() && $this->getConsumerSecret() && $this->getRefreshToken();
+ try {
+ return !is_null($this->authToken) && !empty($this->authToken->getAccessToken());
+ }
+ catch (TokenNotFoundException $e) {
+ return FALSE;
+ }
}
/**
* {@inheritdoc}
*/
public function apiCall($path, array $params = [], $method = 'GET', $returnObject = FALSE) {
- if (!$this->getAccessToken()) {
- $this->refreshToken();
+ if (!$this->isAuthorized()) {
+ $this->authManager->refreshToken();
}
if (strpos($path, '/') === 0) {
- $url = $this->getInstanceUrl() . $path;
+ $url = $this->authProvider->getInstanceUrl() . $path;
}
else {
$url = $this->getApiEndPoint() . $path;
@@ -157,9 +193,9 @@ class RestClient implements RestClientInterface {
if ($this->response->getStatusCode() == 401) {
// The session ID or OAuth token used has expired or is invalid: refresh
- // token. If refreshToken() throws an exception, or if apiHttpRequest()
+ // token. If refresh_token() throws an exception, or if apiHttpRequest()
// throws anything but a RequestException, let it bubble up.
- $this->refreshToken();
+ $this->authManager->refreshToken();
try {
$this->response = new RestResponse($this->apiHttpRequest($url, $params, $method));
}
@@ -202,12 +238,12 @@ class RestClient implements RestClientInterface {
* @throws \GuzzleHttp\Exception\RequestException
*/
protected function apiHttpRequest($url, array $params, $method) {
- if (!$this->getAccessToken()) {
+ if (!$this->isAuthorized()) {
throw new \Exception('Missing OAuth Token');
}
$headers = [
- 'Authorization' => 'OAuth ' . $this->getAccessToken(),
+ 'Authorization' => 'OAuth ' . $this->authToken->getAccessToken(),
'Content-type' => 'application/json',
];
$data = NULL;
@@ -226,11 +262,11 @@ class RestClient implements RestClientInterface {
* @throws \Exception
*/
public function httpRequestRaw($url) {
- if (!$this->getAccessToken()) {
+ if (!$this->isAuthorized()) {
throw new \Exception('Missing OAuth Token');
}
$headers = [
- 'Authorization' => 'OAuth ' . $this->getAccessToken(),
+ 'Authorization' => 'OAuth ' . $this->authToken->getAccessToken(),
'Content-type' => 'application/json',
];
$response = $this->httpRequest($url, NULL, $headers);
@@ -325,7 +361,7 @@ class RestClient implements RestClientInterface {
public function getApiEndPoint($api_type = 'rest') {
$url = &drupal_static(__FUNCTION__ . $api_type);
if (!isset($url)) {
- $identity = $this->getIdentity();
+ $identity = $this->authProvider->getIdentity();
if (empty($identity)) {
return FALSE;
}
@@ -360,8 +396,11 @@ class RestClient implements RestClientInterface {
*
* @throws \Exception
* @throws \GuzzleHttp\Exception\RequestException
+ *
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function setApiVersion($use_latest = TRUE, $version = NULL) {
+ trigger_error(__CLASS__.'::'.__FUNCTION__ . ' is deprecated and will be removed before the next stable release of Salesforce module. Please update your callers.', E_DEPRECATED);
if ($use_latest) {
$this->mutableConfig->set('use_latest', $use_latest);
}
@@ -377,281 +416,108 @@ class RestClient implements RestClientInterface {
}
/**
- * Gets the Saleforce connected app consumer key.
- *
- * @return string
- * The consumer key.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function getConsumerKey() {
- return $this->immutableConfig->get('consumer_key');
+ return $this->authProvider ? $this->authProvider->getConsumerKey() : NULL;
}
/**
- * Sets the Saleforce connected app consumer key.
- *
- * @param string $value
- * The consumer key.
- *
- * @return $this
- * The RestClient object.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function setConsumerKey($value) {
- $this->mutableConfig->set('consumer_key', $value)->save();
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * Gets the Saleforce connected app consumer secret.
- *
- * @return string
- * The consumer secret.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function getConsumerSecret() {
- return $this->immutableConfig->get('consumer_secret');
+ return $this->authProvider ? $this->authProvider->getConsumerSecret() : NULL;
}
/**
- * Sets the Saleforce connected app consumer secret.
- *
- * @param string $value
- * The consumer secret.
- *
- * @return $this
- * The RestClient object.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function setConsumerSecret($value) {
- $this->mutableConfig->set('consumer_secret', $value)->save();
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * Gets the Saleforce connected app login URL.
- *
- * If the login URL is not set, a default of "https://login.salesforce.com" is
- * returned.
- *
- * @return string
- * The login URL.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function getLoginUrl() {
- $login_url = $this->immutableConfig->get('login_url');
- return empty($login_url) ? 'https://login.salesforce.com' : $login_url;
+ return $this->authProvider ? $this->authProvider->getLoginUrl() : NULL;
}
/**
- * Sets the Saleforce connected app login URL.
- *
- * @param string $value
- * The login URL.
- *
- * @return $this
- * The RestClient object.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function setLoginUrl($value) {
- $this->mutableConfig->set('login_url', $value)->save();
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * Get the SF instance URL. Useful for linking to objects.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getToken() to access the current active auth token.
*/
public function getInstanceUrl() {
- return $this->state->get('salesforce.instance_url');
+ return $this->authProvider ? $this->authProvider->getInstanceUrl() : NULL;
}
/**
- * Set the SF instance URL.
- *
- * @param string $url
- * URL to set.
- *
- * @return $this
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getToken() to access the current active auth token.
*/
public function setInstanceUrl($url) {
- $this->state->set('salesforce.instance_url', $url);
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * Get the access token.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getToken() to access the current active auth token.
*/
public function getAccessToken() {
- $access_token = $this->state->get('salesforce.access_token');
- return isset($access_token) && mb_strlen($access_token) !== 0 ? $access_token : FALSE;
+ return $this->authToken ? $this->authToken->getAccessToken() : NULL;
}
/**
- * Set the access token.
- *
- * @param string $token
- * Access token from Salesforce.
- *
- * @return $this
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getToken() to access the current active auth token.
*/
public function setAccessToken($token) {
- $this->state->set('salesforce.access_token', $token);
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * Get refresh token.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getToken() to access the current active auth token.
*/
protected function getRefreshToken() {
- return $this->state->get('salesforce.refresh_token');
+ return $this->authToken ? $this->authToken->getRefreshToken() : NULL;
}
/**
- * Set refresh token.
- *
- * @param string $token
- * Refresh token from Salesforce.
- *
- * @return $this
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getToken() to access the current active auth token.
*/
public function setRefreshToken($token) {
- $this->state->set('salesforce.refresh_token', $token);
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * {@inheritdoc}
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getProvider() to access the current active auth configuration.
*/
public function refreshToken() {
- $refresh_token = $this->getRefreshToken();
- if (empty($refresh_token)) {
- throw new \Exception(t('There is no refresh token.'));
- }
-
- $data = UrlHelper::buildQuery([
- 'grant_type' => 'refresh_token',
- 'refresh_token' => urldecode($refresh_token),
- 'client_id' => $this->getConsumerKey(),
- 'client_secret' => $this->getConsumerSecret(),
- ]);
-
- $url = $this->getAuthTokenUrl();
- $headers = [
- // This is an undocumented requirement on Salesforce's end.
- 'Content-Type' => 'application/x-www-form-urlencoded',
- ];
- $response = $this->httpRequest($url, $data, $headers, 'POST');
-
- $this->handleAuthResponse($response);
- return $this;
+ return $this->authManager->refreshToken();
}
/**
- * Helper callback for OAuth handshake, and refreshToken()
- *
- * @param \GuzzleHttp\Psr7\Response $response
- * Response object from refreshToken or authToken endpoints.
- *
- * @return $this
- *
- * @throws \Exception
- *
- * @see SalesforceController::oauthCallback()
- * @see self::refreshToken()
- */
- public function handleAuthResponse(Response $response) {
- if ($response->getStatusCode() != 200) {
- throw new \Exception($response->getReasonPhrase(), $response->getStatusCode());
- }
-
- $data = (new RestResponse($response))->data;
-
- $this
- ->setAccessToken($data['access_token'])
- ->initializeIdentity($data['id'])
- ->setInstanceUrl($data['instance_url']);
-
- // Do not overwrite an existing refresh token with an empty value.
- if (!empty($data['refresh_token'])) {
- $this->setRefreshToken($data['refresh_token']);
- }
- return $this;
- }
-
- /**
- * Retrieve and store the Salesforce identity given an ID url.
- *
- * @param string $id
- * Identity URL.
- *
- * @return $this
- *
- * @throws \Exception
- * @throws \GuzzleHttp\Exception\RequestException
- */
- public function initializeIdentity($id) {
- $headers = [
- 'Authorization' => 'OAuth ' . $this->getAccessToken(),
- 'Content-type' => 'application/json',
- ];
- $response = $this->httpRequest($id, NULL, $headers);
-
- if ($response->getStatusCode() != 200) {
- throw new \Exception(t('Unable to access identity service.'), $response->getStatusCode());
- }
- $data = (new RestResponse($response))->data;
-
- $this->setIdentity($data);
- return $this;
- }
-
- /**
- * Setter for identity state info.
- *
- * @return $this
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function setIdentity($data) {
- $this->state->set('salesforce.identity', $data);
- return $this;
+ throw new DeprecatedMethodException(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated. See release notes.');
}
/**
- * Return the Salesforce identity, which is stored in a variable.
- *
- * @return array
- * Returns FALSE is no identity has been stored.
+ * @deprecated use \Drupal\salesforce\SalesforceAuthProviderPluginManager::getConfig() to access the current active auth configuration.
*/
public function getIdentity() {
- return $this->state->get('salesforce.identity');
- }
-
- /**
- * Helper to build the redirect URL for OAUTH workflow.
- *
- * @return string
- * Redirect URL.
- *
- * @see \Drupal\salesforce\Controller\SalesforceController
- */
- public function getAuthCallbackUrl() {
- return Url::fromRoute('salesforce.oauth_callback', [], [
- 'absolute' => TRUE,
- 'https' => TRUE,
- ])->toString();
- }
-
- /**
- * Get Salesforce oauth login endpoint. (OAuth step 1)
- *
- * @return string
- * REST OAuth Login URL.
- */
- public function getAuthEndpointUrl() {
- return $this->getLoginUrl() . '/services/oauth2/authorize';
- }
-
- /**
- * Get Salesforce oauth token endpoint. (OAuth step 2)
- *
- * @return string
- * REST OAuth Token URL.
- */
- public function getAuthTokenUrl() {
- return $this->getLoginUrl() . '/services/oauth2/token';
+ return $this->authProvider ? $this->authProvider->getIdentity() : NULL;
}
/**
@@ -672,7 +538,7 @@ class RestClient implements RestClientInterface {
}
$versions = [];
- $id = $this->getIdentity();
+ $id = $this->authProvider->getIdentity();
if (!empty($id)) {
$url = str_replace('v{version}/', '', $id['urls']['rest']);
$response = new RestResponse($this->httpRequest($url));
@@ -750,9 +616,6 @@ class RestClient implements RestClientInterface {
}
}
}
- else {
- $sobjects[$object['name']] = $object;
- }
}
return $sobjects;
}
@@ -854,7 +717,7 @@ class RestClient implements RestClientInterface {
* @param array $params
* Values of the fields to set for the object.
*
- * @return \Drupal\salesforce\SFID|null
+ * @return \Drupal\salesforce\SFID|NULL
*
* @addtogroup salesforce_apicalls
*/
@@ -1078,7 +941,7 @@ class RestClient implements RestClientInterface {
* @param bool $reset
*
* @return \Drupal\salesforce\SFID
- * The Salesforce ID of the given Record Type, or null.
+ * The Salesforce ID of the given Record Type, or NULL.
*
* @throws \Exception if record type not found
*/
diff --git a/src/Rest/RestClientInterface.php b/src/Rest/RestClientInterface.php
index 1f045bb..94b2a1f 100644
--- a/src/Rest/RestClientInterface.php
+++ b/src/Rest/RestClientInterface.php
@@ -108,134 +108,6 @@ interface RestClientInterface {
public function getApiUsage();
/**
- *
- */
- public function getConsumerKey();
-
- /**
- *
- */
- public function setConsumerKey($value);
-
- /**
- *
- */
- public function getConsumerSecret();
-
- /**
- *
- */
- public function setConsumerSecret($value);
-
- /**
- *
- */
- public function getLoginUrl();
-
- /**
- *
- */
- public function setLoginUrl($value);
-
- /**
- * Get the SF instance URL. Useful for linking to objects.
- */
- public function getInstanceUrl();
-
- /**
- * Set the SF instance URL.
- */
- public function setInstanceUrl($url);
-
- /**
- * Get the access token.
- */
- public function getAccessToken();
-
- /**
- * Set the access token.
- *
- * @param string $token
- * Access token from Salesforce.
- */
- public function setAccessToken($token);
-
- /**
- * Set the refresh token.
- *
- * @param string $token
- * Refresh token from Salesforce.
- */
- public function setRefreshToken($token);
-
- /**
- * Refresh access token based on the refresh token.
- *
- * @throws \Exception
- */
- public function refreshToken();
-
- /**
- * Helper callback for OAuth handshake, and refreshToken()
- *
- * @param \GuzzleHttp\Psr7\Response $response
- * Response object from refreshToken or authToken endpoints.
- *
- * @see SalesforceController::oauthCallback()
- * @see self::refreshToken()
- */
- public function handleAuthResponse(Response $response);
-
- /**
- * Retrieve and store the Salesforce identity given an ID url.
- *
- * @param string $id
- * Identity URL.
- *
- * @throws \Exception
- */
- public function initializeIdentity($id);
-
- /**
- * Return the Salesforce identity, which is stored in a variable.
- *
- * @return array
- * Returns FALSE is no identity has been stored.
- */
- public function getIdentity();
-
- /**
- * Set the Salesforce identity, which is stored in a variable.
- */
- public function setIdentity($data);
-
- /**
- * Helper to build the redirect URL for OAUTH workflow.
- *
- * @return string
- * Redirect URL.
- *
- * @see \Drupal\salesforce\Controller\SalesforceController
- */
- public function getAuthCallbackUrl();
-
- /**
- * Get Salesforce oauth login endpoint. (OAuth step 1)
- *
- * @return string
- * REST OAuth Login URL.
- */
- public function getAuthEndpointUrl();
-
- /**
- * Get Salesforce oauth token endpoint. (OAuth step 2)
- *
- * @return string
- * REST OAuth Token URL.
- */
- public function getAuthTokenUrl();
-
- /**
* @defgroup salesforce_apicalls Wrapper calls around core apiCall()
*/
diff --git a/src/Rest/RestResponse.php b/src/Rest/RestResponse.php
index 74a1c99..3585a69 100644
--- a/src/Rest/RestResponse.php
+++ b/src/Rest/RestResponse.php
@@ -4,6 +4,7 @@ namespace Drupal\salesforce\Rest;
use Drupal\Component\Serialization\Json;
use GuzzleHttp\Psr7\Response;
+use Psr\Http\Message\ResponseInterface;
/**
* Class RestResponse.
@@ -33,7 +34,7 @@ class RestResponse extends Response {
*
* @throws \Drupal\salesforce\Rest\RestException if body cannot be json-decoded
*/
- public function __construct(Response $response) {
+ public function __construct(ResponseInterface $response) {
$this->response = $response;
parent::__construct($response->getStatusCode(), $response->getHeaders(), $response->getBody(), $response->getProtocolVersion(), $response->getReasonPhrase());
$this->handleJsonResponse();
diff --git a/src/SalesforceAuthProviderInterface.php b/src/SalesforceAuthProviderInterface.php
new file mode 100644
index 0000000..7706910
--- /dev/null
+++ b/src/SalesforceAuthProviderInterface.php
@@ -0,0 +1,69 @@
+ '',
+ '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) {
+
+ }
+
+ public function id() {
+ return $this->id;
+ }
+
+ public function type() {
+ return static::SERVICE_TYPE;
+ }
+
+ 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();
+ }
+
+ /**
+ * @param $responseBody
+ * JSON identity response from Salesforce.
+ *
+ * @return array
+ * @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
+ */
+ public function getStorage() {
+ return $this->storage;
+ }
+
+}
\ No newline at end of file
diff --git a/src/SalesforceAuthProviderPluginInterface.php b/src/SalesforceAuthProviderPluginInterface.php
new file mode 100644
index 0000000..ffa3e77
--- /dev/null
+++ b/src/SalesforceAuthProviderPluginInterface.php
@@ -0,0 +1,22 @@
+alterInfo('salesforce_auth_provider_info');
+ $this->setCacheBackend($cache_backend, 'salesforce_auth_provider');
+ $this->etm = $etm;
+ }
+
+ protected function authStorage() {
+ if (empty($this->authStorage)) {
+ $this->authStorage = $this->etm->getStorage('salesforce_auth');
+ }
+ return $this->authStorage;
+ }
+
+ public function getProviders() {
+ return $this->authStorage()->loadMultiple();
+ }
+
+ 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
+ */
+ public function getConfig() {
+ $provider_id = $this->config()->get('salesforce_auth.provider');
+ if (empty($provider_id)) {
+ return NULL;
+ }
+ return SalesforceAuthEntity::load($provider_id);
+ }
+
+ /**
+ * @return \Drupal\salesforce\SalesforceAuthProviderInterface|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
+ */
+ 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
+ */
+ public function refreshToken() {
+ if (!$config = $this->getConfig()) {
+ return NULL;
+ }
+ if (!$provider = $config->getPlugin()) {
+ return NULL;
+ }
+ $token = $this->getToken() ?: new StdOAuth2Token();
+ return $provider->refreshAccessToken($token);
+ }
+
+ protected function config() {
+ if (!$this->config) {
+ $this->config = \Drupal::config('salesforce.settings');
+ }
+ return $this->config;
+ }
+
+}
\ No newline at end of file
diff --git a/src/SalesforceOAuthPluginInterface.php b/src/SalesforceOAuthPluginInterface.php
new file mode 100644
index 0000000..66c65cc
--- /dev/null
+++ b/src/SalesforceOAuthPluginInterface.php
@@ -0,0 +1,13 @@
+state = $state;
+ }
+
+ protected static function getTokenStorageId($service) {
+ return self::TOKEN_STORAGE_PREFIX . '.' . $service;
+ }
+
+ protected static function getAuthStateStorageId($service) {
+ return self::AUTH_STATE_STORAGE_PREFIX . '.' . $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.
+ }
+
+ /**
+ *{@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().
+ }
+
+ /**
+ *{@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 0000000..96ab62e
--- /dev/null
+++ b/src/Storage/SalesforceAuthTokenStorageInterface.php
@@ -0,0 +1,34 @@
+example_url = 'https://example.com';
- $this->consumer_key = $this->randomMachineName();
-
- $this->config_factory = $this->prophesize(ConfigFactoryInterface::class);
- $this->state = $this->prophesize(StateInterface::class);
- $this->client = $this->prophesize(RestClient::class);
- $this->request_stack = $this->prophesize(RequestStack::class);
- $this->obpath = $this->prophesize(OutboundPathProcessorInterface::class);
- $this->logger = $this->prophesize(LoggerChannelFactory::class);
- $this->unrouted_url_assembler = new UnroutedUrlAssembler($this->request_stack->reveal(), $this->obpath->reveal());
- $this->event_dispatcher = $this->getMock('\Symfony\Component\EventDispatcher\EventDispatcherInterface');
-
- $this->client->getAuthCallbackUrl()->willReturn($this->example_url);
- $this->client->getAuthEndpointUrl()->willReturn($this->example_url);
- $this->client->getConsumerKey()->willReturn($this->consumer_key);
-
- $this->client->setConsumerKey(Argument::any())->willReturn(NULL);
- $this->client->setConsumerSecret(Argument::any())->willReturn(NULL);
- $this->client->setLoginUrl(Argument::any())->willReturn(NULL);
-
- $container = new ContainerBuilder();
- $container->set('config.factory', $this->config_factory->reveal());
- $container->set('salesforce.client', $this->client->reveal());
- $container->set('state', $this->state->reveal());
- $container->set('unrouted_url_assembler', $this->unrouted_url_assembler);
- $container->set('logger.factory', $this->logger->reveal());
- $container->set('event_dispatcher', $this->event_dispatcher);
- \Drupal::setContainer($container);
- }
-
- /**
- * @covers ::submitForm
- */
- public function testSubmitForm() {
- $form_state = new FormState();
- $form_state->setValues([
- 'consumer_key' => $this->consumer_key,
- 'consumer_secret' => $this->randomMachineName(),
- 'login_url' => $this->example_url,
- ]);
-
- $form = AuthorizeForm::create(\Drupal::getContainer());
- $form_array = [];
- $form->submitForm($form_array, $form_state);
- /** @var \Drupal\Core\Routing\TrustedRedirectResponse $response */
- $response = $form_state->getResponse();
- $this->assertTrue($response instanceof TrustedRedirectResponse);
- $this->assertEquals($this->example_url . '?redirect_uri=' . urlencode($this->example_url) . '&response_type=code&client_id=' . $form_state->getValue('consumer_key'), $response->getTargetUrl());
- }
-
-}
diff --git a/tests/src/Unit/RestClientTest.php b/tests/src/Unit/RestClientTest.php
index 8072a34..9678db5 100644
--- a/tests/src/Unit/RestClientTest.php
+++ b/tests/src/Unit/RestClientTest.php
@@ -36,7 +36,7 @@ class RestClientTest extends UnitTestCase {
'getConsumerSecret',
'getRefreshToken',
'getAccessToken',
- 'refreshToken',
+ 'refresh_token',
'getApiEndPoint',
'httpRequest',
];
diff --git a/tests/src/Unit/SalesforceControllerTest.php b/tests/src/Unit/SalesforceControllerTest.php
deleted file mode 100644
index 7fe990c..0000000
--- a/tests/src/Unit/SalesforceControllerTest.php
+++ /dev/null
@@ -1,115 +0,0 @@
-example_url = 'https://example.com';
-
- $this->httpClient = $this->getMock('\GuzzleHttp\Client', ['post']);
- $this->httpClient->expects($this->once())
- ->method('post')
- ->willReturn(new Response());
- $this->configFactory =
- $this->getMockBuilder('\Drupal\Core\Config\ConfigFactory')
- ->disableOriginalConstructor()
- ->getMock();
- $this->state =
- $this->getMockBuilder('\Drupal\Core\State\State')
- ->disableOriginalConstructor()
- ->getMock();
- $this->cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface');
- $this->json = $this->getMock(Json::CLASS);
- $this->time = $this->getMock(TimeInterface::CLASS);
-
- $args = [$this->httpClient, $this->configFactory, $this->state, $this->cache, $this->json, $this->time];
-
- $this->client = $this->getMock(RestClient::class, ['getConsumerKey', 'getConsumerSecret', 'getAuthCallbackUrl', 'getAuthTokenUrl', 'handleAuthResponse'], $args);
- $this->client->expects($this->once())
- ->method('getConsumerKey')
- ->willReturn($this->randomMachineName());
- $this->client->expects($this->once())
- ->method('getConsumerSecret')
- ->willReturn($this->randomMachineName());
- $this->client->expects($this->once())
- ->method('getAuthCallbackUrl')
- ->willReturn($this->example_url);
- $this->client->expects($this->once())
- ->method('getAuthTokenUrl')
- ->willReturn($this->example_url);
- $this->client->expects($this->once())
- ->method('handleAuthResponse')
- ->willReturn($this->client);
-
- $this->request = new Request(['code' => $this->randomMachineName()]);
-
- $this->request_stack = $this->getMock(RequestStack::class);
- $this->request_stack->expects($this->exactly(2))
- ->method('getCurrentRequest')
- ->willReturn($this->request);
-
- $this->url_generator = $this->prophesize(MetadataBubblingUrlGenerator::class);
- $this->url_generator->generateFromRoute(
- 'salesforce.authorize',
- [],
- ["absolute" => TRUE],
- FALSE
- )
- ->willReturn('foo/bar');
-
- $container = new ContainerBuilder();
- $container->set('salesforce.client', $this->client);
- $container->set('http_client', $this->httpClient);
- $container->set('request_stack', $this->request_stack);
- $container->set('url.generator', $this->url_generator->reveal());
- $container->set('datetime.time', $this->time);
- \Drupal::setContainer($container);
-
- }
-
- /**
- * @covers ::oauthCallback
- */
- public function testOauthCallback() {
- $this->controller = $this->getMock(
- SalesforceController::class,
- ['successMessage'],
- [
- $this->client,
- $this->httpClient,
- $this->url_generator->reveal(),
- ]
- );
- $this->controller
- ->expects($this->once())
- ->method('successMessage')
- ->willReturn(NULL);
- $expected = new RedirectResponse('foo/bar');
- $actual = $this->controller->oauthCallback();
- $this->assertEquals($expected->getTargetUrl(), $actual->getTargetUrl());
- }
-
-}