diff --git a/composer.json b/composer.json index b02b218338cbe6c585d21dac1d9eb9c057d19486..0fc9be263ee0e0794138ec9784550d74f9bcafb8 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,9 @@ "require": { "consolidation/output-formatters": "^3.2.0", "drupal/dynamic_entity_reference": "^2.0@alpha", + "drupal/key": "^1.7", "drupal/encrypt": "^3.0@rc", + "firebase/php-jwt": "^5.0", "lusitanian/oauth": "^0.8.11", "messageagency/force.com-toolkit-for-php": "^1.0.0" } diff --git a/config/schema/salesforce.schema.yml b/config/schema/salesforce.schema.yml index ac83bb9277c18346201dfbbaf48330bce4f17415..a05925dce747b3b421ac72766dec0e3c634ecae7 100644 --- a/config/schema/salesforce.schema.yml +++ b/config/schema/salesforce.schema.yml @@ -74,17 +74,3 @@ salesforce.salesforce_auth.*: provider_settings: type: salesforce.auth_provider_settings.[%parent.provider] label: 'Provider Plugin Settings' - -salesforce.auth_provider_settings.oauth: - type: mapping - label: 'Salesforce OAuth Provider Settings' - mapping: - consumer_key: - type: string - label: 'Consumer Key' - consumer_secret: - type: string - label: 'Consumer Secret' - login_url: - type: uri - label: 'Login URL' diff --git a/modules/salesforce_encrypt/config/schema/salesforce_encrypt.schema.yml b/modules/salesforce_encrypt/config/schema/salesforce_encrypt.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..5fdb2e46ce00e0ecd5943dd6e1b05f2c13b92c5f --- /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 OAuth, Encrypted, 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 74f83be13dfa5e1c7636118857a99abcc330e226..521059d445a8a40a84db344f4da71a4c2a8cbcac 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: Adds encryption support for auth providers. 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 9f84836ed48954bc00196b295aac1c462e72410b..0000000000000000000000000000000000000000 --- a/modules/salesforce_encrypt/salesforce_encrypt.install +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -/** - * @file - * Requirements and uninstall hooks. - */ - -use Drupal\Core\Url; -use Drupal\salesforce\EntityNotFoundException; - -/** - * Throw a runtime error if Salesforce encryption profile is not selected. - * - * Implements hook_requirements(). - */ -function salesforce_encrypt_requirements($phase) { - $requirements = []; - if ($phase == 'runtime') { - $profile_id = NULL; - try { - $profile = \Drupal::service('salesforce.client')->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 <a href="@url">select an encryption profile</a> 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: <a href=":url">%profile</a>', ['%profile' => $profile->id(), ':url' => $profile->url()]), - ]; - } - } - return $requirements; -} - -/** - * Implements hook_uninstall(). - * - * 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 7825f4241c443285956314e6c9a5f494e33ee884..0000000000000000000000000000000000000000 --- 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.routing.yml b/modules/salesforce_encrypt/salesforce_encrypt.routing.yml deleted file mode 100644 index ad30e490962eb9b23ac5a07b6192734404a72924..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..5c0fbded9fb7bc7af2bcf5bb4c1b12bbe7e3fed5 --- /dev/null +++ b/modules/salesforce_encrypt/src/Consumer/OAuthEncryptedCredentials.php @@ -0,0 +1,37 @@ +<?php + +namespace Drupal\salesforce_encrypt\Consumer; + +use Drupal\salesforce\Consumer\SalesforceCredentials; + +/** + * OAuth encrypted creds. + */ +class OAuthEncryptedCredentials extends SalesforceCredentials { + + /** + * Encryption profile id. + * + * @var string + */ + protected $encryptionProfileId; + + /** + * {@inheritdoc} + */ + public function __construct($consumerKey, $loginUrl, $consumerSecret, $encryptionProfileId) { + parent::__construct($consumerKey, $loginUrl, $consumerSecret); + $this->encryptionProfileId = $encryptionProfileId; + } + + /** + * Getter. + * + * @return string + * The encryption profile id. + */ + public function getEncryptionProfileId() { + return $this->encryptionProfileId; + } + +} diff --git a/modules/salesforce_encrypt/src/Form/SettingsForm.php b/modules/salesforce_encrypt/src/Form/SettingsForm.php deleted file mode 100644 index 69e2aa10f39356f668a1052d2f5d74acc1de1982..0000000000000000000000000000000000000000 --- a/modules/salesforce_encrypt/src/Form/SettingsForm.php +++ /dev/null @@ -1,135 +0,0 @@ -<?php - -namespace Drupal\salesforce_encrypt\Form; - -use Drupal\Core\Form\FormBase; -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 Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Url; - -/** - * Base form for key add and edit forms. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class SettingsForm extends FormBase { - - /** - * Profile manager. - * - * @var \Drupal\encrypt\EncryptionProfileManagerInterface - */ - protected $encryptionProfileManager; - - /** - * SettingsForm constructor. - * - * @param \Drupal\Core\State\StateInterface $state - * State service. - * @param \Drupal\encrypt\EncryptionProfileManagerInterface $encryptionProfileManager - * Encryption profile manager service. - * @param \Drupal\salesforce_encrypt\Rest\EncryptedRestClientInterface $client - * Rest client service. - */ - public function __construct(StateInterface $state, EncryptionProfileManagerInterface $encryptionProfileManager, EncryptedRestClientInterface $client) { - $this->encryptionProfileManager = $encryptionProfileManager; - $this->state = $state; - $this->client = $client; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('state'), - $container->get('encrypt.encryption_profile.manager'), - $container->get('salesforce.client') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'salesforce_encrypt_config'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $options = $this - ->encryptionProfileManager - ->getEncryptionProfileNamesAsOptions(); - $default = NULL; - try { - $profile = $this->client->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 <a href=":encrypt">assign a new encryption profile</a>, then <a href=":oauth">re-authenticate to Salesforce</a>.', [':encrypt' => Url::fromRoute('salesforce_encrypt.settings')->toString(), ':oauth' => Url::fromRoute('salesforce.authorize')->toString()]), 'error'); - } - - $form['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, - '#empty_option' => $this->t('Do not use encryption'), - ]; - - $form['actions']['#type'] = 'actions'; - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save configuration'), - '#button_type' => 'primary', - ]; - - // 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) { - $old_profile_id = $this->state->get('salesforce_encrypt.profile'); - $profile_id = $form_state->getValue('profile'); - - if ($old_profile_id == $profile_id) { - // No change to encryption profile. Do nothing. - return; - } - - $profile = $this - ->encryptionProfileManager - ->getEncryptionProfile($profile_id); - if (empty($profile_id)) { - // New profile id empty: disable encryption. - $this->client->disableEncryption(); - } - elseif (empty($old_profile_id)) { - // Old profile id empty: enable encryption anew. - $this->client->enableEncryption($profile); - } - else { - // Changing encryption profiles: disable, then re-enable. - $this->client->disableEncryption(); - $this->client->enableEncryption($profile); - } - $this->state->resetCache(); - drupal_set_message($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 0000000000000000000000000000000000000000..4869e2bfe1f6ee138de1382820830fb8aa2a7f19 --- /dev/null +++ b/modules/salesforce_encrypt/src/Plugin/SalesforceAuthProvider/SalesforceEncryptedOAuthPlugin.php @@ -0,0 +1,293 @@ +<?php + +namespace Drupal\salesforce_encrypt\Plugin\SalesforceAuthProvider; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Routing\TrustedRedirectResponse; +use Drupal\Core\Url; +use Drupal\encrypt\EncryptionProfileInterface; +use Drupal\encrypt\EncryptionProfileManagerInterface; +use Drupal\encrypt\EncryptServiceInterface; +use Drupal\salesforce\EntityNotFoundException; +use Drupal\salesforce\SalesforceAuthProviderPluginBase; +use Drupal\salesforce\SalesforceAuthProviderInterface; +use Drupal\salesforce_encrypt\Consumer\OAuthEncryptedCredentials; +use Drupal\salesforce_encrypt\SalesforceEncryptedAuthTokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Uri\Uri; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * OAuth provider with encrypted credentials. + * + * @Plugin( + * id = "oauth_encrypted", + * label = @Translation("Salesforce OAuth User-Agent, Encrypted") + * ) + */ +class SalesforceEncryptedOAuthPlugin extends SalesforceAuthProviderPluginBase { + + /** + * OAuth credentials. + * + * @var \Drupal\salesforce\Consumer\SalesforceCredentials + */ + protected $credentials; + + /** + * Encryption profile manager. + * + * @var \Drupal\encrypt\EncryptionProfileManagerInterface + */ + protected $encryptionProfileManager; + + /** + * Encryption service. + * + * @var \Drupal\encrypt\EncryptServiceInterface + */ + protected $encryption; + + /** + * Encryption profile. + * + * @var \Drupal\encrypt\EncryptionProfileInterface + */ + protected $encryptionProfile; + + /** + * Encryption profile id. + * + * @var string + */ + protected $encryptionProfileId; + + /** + * {@inheritdoc} + */ + const SERVICE_TYPE = 'oauth_encrypted'; + + /** + * {@inheritdoc} + */ + const LABEL = 'OAuth Encrypted'; + + /** + * Token storage;. + * + * @var \Drupal\salesforce_encrypt\SalesforceEncryptedAuthTokenStorageInterface + */ + protected $storage; + + /** + * {@inheritdoc} + */ + public function __construct($id, OAuthEncryptedCredentials $credentials, ClientInterface $httpClient, SalesforceEncryptedAuthTokenStorageInterface $storage, EncryptionProfileManagerInterface $encryptionProfileManager, EncryptServiceInterface $encrypt) { + parent::__construct($credentials, $httpClient, $storage, [], new Uri($credentials->getLoginUrl())); + $this->id = $id; + $this->encryptionProfileManager = $encryptionProfileManager; + $this->encryption = $encrypt; + $this->encryptionProfileId = $credentials->getEncryptionProfileId(); + } + + /** + * {@inheritdoc} + */ + 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 \Drupal\encrypt\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); + } + + /** + * {@inheritdoc} + */ + 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 + } + } + + /** + * {@inheritdoc} + */ + 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 <a href="@href">create an encryption profile</a> 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' => $this->credentials->getCallbackUrl(), + '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()])); + } + } + + /** + * {@inheritdoc} + */ + public function decrypt($value) { + return $this->encryption->decrypt($value, $this->encryptionProfile()); + } + + /** + * {@inheritdoc} + */ + public function encrypt($value) { + return $this->encryption->encrypt($value, $this->encryptionProfile()); + } + + /** + * {@inheritdoc} + */ + public function getConsumerSecret() { + return $this->credentials->getConsumerSecret(); + } + + /** + * {@inheritdoc} + */ + public function finalizeOauth() { + $this->requestAccessToken(\Drupal::request()->get('code')); + $token = $this->getAccessToken(); + + // Initialize identity. + $headers = [ + 'Authorization' => 'OAuth ' . $token->getAccessToken(), + 'Content-type' => 'application/json', + ]; + $data = $token->getExtraParams(); + $response = $this->httpClient->retrieveResponse(new Uri($data['id']), [], $headers); + $identity = $this->parseIdentityResponse($response); + $this->storage->storeIdentity($this->service(), $identity); + return TRUE; + } + +} diff --git a/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php b/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php deleted file mode 100644 index 1ee3b9e95d563b43f9740cb5d6e491c994748ca8..0000000000000000000000000000000000000000 --- a/modules/salesforce_encrypt/src/Rest/EncryptedRestClientInterface.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -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 will be removed in 8.x-4.0 release. - */ -interface EncryptedRestClientInterface extends RestClientInterface { - - /** - * Encrypts all sensitive salesforce config values. - * - * @param \Drupal\encrypt\EncryptionProfileInterface $profile - * 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); - - /** - * Decrypt and re-save sensitive salesforce config values. - * - * Inverse of ::enableEncryption. - * - * @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. - * - * @return \Drupal\encrypt\EncryptionProfileInterface|null - * The assigned profile, or null if none has been assigned. - * - * @throws \Drupal\salesforce\EntityNotFoundException - * If a profile is assigned, but cannot be loaded. - */ - public function getEncryptionProfile(); - - /** - * If the given profile is our active one, disable encryption. - * - * 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 - * The encryption profile being deleted. - */ - public function hookEncryptionProfileDelete(EncryptionProfileInterface $profile); - - /** - * Encrypts a value using the active encryption profile, or return plaintext. - * - * @param string $value - * The value to encrypt. - * - * @return string - * The encrypted value, or plaintext if no active profile. - */ - public function encrypt($value); - - /** - * Decrypts a value using active encryption profile, or return the same value. - * - * @param string $value - * The value to decrypt. - * - * @return string - * The decrypted value, or the unchanged value if no active profile. - */ - public function decrypt($value); - -} diff --git a/modules/salesforce_encrypt/src/Rest/RestClient.php b/modules/salesforce_encrypt/src/Rest/RestClient.php deleted file mode 100644 index 6d8461f19f77ca2a75276fb7c9095ba61531b504..0000000000000000000000000000000000000000 --- a/modules/salesforce_encrypt/src/Rest/RestClient.php +++ /dev/null @@ -1,301 +0,0 @@ -<?php - -namespace Drupal\salesforce_encrypt\Rest; - -use Drupal\Component\Serialization\Json; -use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Lock\LockBackendInterface; -use Drupal\Core\State\StateInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\Core\Url; -use Drupal\encrypt\EncryptServiceInterface; -use Drupal\encrypt\EncryptionProfileInterface; -use Drupal\encrypt\EncryptionProfileManagerInterface; -use Drupal\salesforce\EntityNotFoundException; -use Drupal\salesforce\Rest\RestClient as SalesforceRestClient; -use GuzzleHttp\ClientInterface; -use Drupal\Component\Datetime\TimeInterface; - -/** - * Objects, properties, and methods to communicate with the Salesforce REST API. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class RestClient extends SalesforceRestClient implements EncryptedRestClientInterface { - - use StringTranslationTrait; - - /** - * Encryption service. - * - * @var \Drupal\encrypt\EncryptServiceInterface - */ - protected $encryption; - - /** - * Encryption profile manager. - * - * @var \Drupal\encrypt\EncryptionProfileManagerInterface - */ - protected $encryptionProfileManager; - - /** - * The active encryption profile id. - * - * @var string - */ - 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\Component\Datetime\TimeInterface $time - * The Time 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; - } - - /** - * {@inheritdoc} - */ - public function enableEncryption(EncryptionProfileInterface $profile) { - if ($ret = $this->setEncryption($profile)) { - $this->state->resetCache(); - } - return $ret; - } - - /** - * {@inheritdoc} - */ - public function disableEncryption() { - if ($ret = $this->setEncryption()) { - $this->state->resetCache(); - } - return $ret; - } - - /** - * {@inheritdoc} - */ - public function hookEncryptionProfileDelete(EncryptionProfileInterface $profile) { - if ($this->encryptionProfileId == $profile->id()) { - $this->disableEncryption(); - } - } - - /** - * Set the given encryption profile as active. - * - * If given profile is null, decrypt and disable encryption. - * - * @param \Drupal\encrypt\EncryptionProfileInterface|null $profile - * The encryption profile. If null, encryption will be disabled. - */ - 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; - } - } - - /** - * Deprecated, use doGetEncryptionProfile. - * - * @deprecated use ::doGetEncryptionProfile(). - */ - protected function _getEncryptionProfile() { - return $this->doGetEncryptionProfile(); - } - - /** - * 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 doGetEncryptionProfile() { - try { - $profile = $this->getEncryptionProfile(); - } - catch (EntityNotFoundException $e) { - drupal_set_message($this->t('Error while loading encryption profile. You will need to <a href=":encrypt">assign a new encryption profile</a>, then <a href=":oauth">re-authenticate to Salesforce</a>.', [':encrypt' => Url::fromRoute('salesforce_encrypt.settings')->toString(), ':oauth' => Url::fromRoute('salesforce.authorize')->toString()]), 'error'); - } - - return $profile; - } - - /** - * {@inheritdoc} - */ - public function encrypt($value) { - if (empty($this->doGetEncryptionProfile())) { - return $value; - } - else { - return $this->encryption->encrypt($value, $this->doGetEncryptionProfile()); - } - } - - /** - * {@inheritdoc} - */ - public function decrypt($value) { - if (empty($this->doGetEncryptionProfile()) || empty($value) || mb_strlen($value) === 0) { - return $value; - } - else { - return $this->encryption->decrypt($value, $this->doGetEncryptionProfile()); - } - } - - /** - * {@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 deleted file mode 100644 index 21f6ca86421cae7156f71810495b27b2eb7a0856..0000000000000000000000000000000000000000 --- a/modules/salesforce_encrypt/src/SalesforceEncryptServiceProvider.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -namespace Drupal\salesforce_encrypt; - -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\DependencyInjection\ServiceProviderBase; -use Drupal\salesforce_encrypt\Rest\RestClient; -use Symfony\Component\DependencyInjection\Reference; - -/** - * Modifies the salesforce client service. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -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')); - } - -} diff --git a/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorage.php b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..53252719adc918ff7dfd6e44b19e9cf9565b78c8 --- /dev/null +++ b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorage.php @@ -0,0 +1,91 @@ +<?php + +namespace Drupal\salesforce_encrypt; + +use Drupal\salesforce\Entity\SalesforceAuthConfig; +use Drupal\salesforce\Storage\SalesforceAuthTokenStorage; +use Drupal\salesforce_encrypt\Plugin\SalesforceAuthProvider\SalesforceEncryptedOAuthPlugin; +use OAuth\Common\Token\TokenInterface; + +/** + * Auth token storage, using encryption. + */ +class SalesforceEncryptedAuthTokenStorage extends SalesforceAuthTokenStorage implements SalesforceEncryptedAuthTokenStorageInterface { + + /** + * Auth plugin manager. + * + * @var \Drupal\salesforce\SalesforceAuthProviderPluginManager + */ + protected $authPluginManager; + + /** + * Given a service id, return the instantiated auth provider plugin. + * + * @param string $service_id + * The service id. + * + * @return \Drupal\salesforce\SalesforceAuthProviderInterface + * The plugin. + */ + protected function service($service_id) { + if (!$this->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; + } + +} diff --git a/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorageInterface.php b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6ff4be197a6c375408d9161a9976cbe9d19d51f2 --- /dev/null +++ b/modules/salesforce_encrypt/src/SalesforceEncryptedAuthTokenStorageInterface.php @@ -0,0 +1,12 @@ +<?php + +namespace Drupal\salesforce_encrypt; + +use Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface; + +/** + * Encrypted token storage interface. + */ +interface SalesforceEncryptedAuthTokenStorageInterface extends SalesforceAuthTokenStorageInterface { + +} diff --git a/modules/salesforce_encrypt/src/SalesforceEncryptedOAuthPluginInterface.php b/modules/salesforce_encrypt/src/SalesforceEncryptedOAuthPluginInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..27a94f22ca80fdde2fd123674b14f3a7c4595b4b --- /dev/null +++ b/modules/salesforce_encrypt/src/SalesforceEncryptedOAuthPluginInterface.php @@ -0,0 +1,57 @@ +<?php + +namespace Drupal\salesforce_encrypt; + +use Drupal\encrypt\EncryptionProfileInterface; +use Drupal\salesforce\SalesforceOAuthPluginInterface; + +/** + * Encrypted oauth provider interface. + */ +interface SalesforceEncryptedOAuthPluginInterface extends SalesforceOAuthPluginInterface { + + /** + * Callback for hook_encryption_profile_predelete(). + * + * @param \Drupal\encrypt\EncryptionProfileInterface $profile + * The encryption profile being deleted. + */ + public function hookEncryptionProfileDelete(EncryptionProfileInterface $profile); + + /** + * Get the encryption profile assigned to this auth plugin. + * + * @return \Drupal\encrypt\EncryptionProfileInterface|null + * Profile. + */ + public function encryptionProfile(); + + /** + * Decrypt a given value, using the assigned encryption profile. + * + * @param string $value + * The encrypted value. + * + * @return string + * The plain text value. + * + * @throws \Drupal\encrypt\Exception\EncryptException + * On decryption error. + */ + public function decrypt($value); + + /** + * Encrypt a value, using the assigned encryption profile. + * + * @param string $value + * The plain text value. + * + * @return string + * The encrypted value. + * + * @throws \Drupal\encrypt\Exception\EncryptException + * On error. + */ + public function encrypt($value); + +} diff --git a/modules/salesforce_encrypt/tests/src/Unit/RestClientTest.php b/modules/salesforce_encrypt/tests/src/Unit/RestClientTest.php deleted file mode 100644 index 84ccc59744e2c8c9436957019e9c331964d3dee3..0000000000000000000000000000000000000000 --- a/modules/salesforce_encrypt/tests/src/Unit/RestClientTest.php +++ /dev/null @@ -1,121 +0,0 @@ -<?php - -namespace Drupal\Tests\salesforce_encrypt\Unit; - -use Drupal\Component\Serialization\Json; -use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\Config\ConfigFactory; -use Drupal\Core\Lock\LockBackendInterface; -use Drupal\Core\State\State; -use Drupal\Tests\UnitTestCase; -use Drupal\encrypt\EncryptServiceInterface; -use Drupal\encrypt\EncryptionProfileInterface; -use Drupal\encrypt\EncryptionProfileManagerInterface; -use Drupal\salesforce_encrypt\Rest\RestClient; -use GuzzleHttp\Client; -use Drupal\Component\Datetime\TimeInterface; - -/** - * @coversDefaultClass \Drupal\salesforce_encrypt\Rest\RestClient - * @group salesforce - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class RestClientTest extends UnitTestCase { - - static public $modules = [ - 'key', - 'encrypt', - 'salesforce', - 'salesforce_encrypt', - ]; - - protected $httpClient; - protected $configFactory; - protected $state; - protected $cache; - protected $json; - protected $time; - protected $encryption; - protected $profileManager; - protected $lock; - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - $this->accessToken = 'foo'; - $this->refreshToken = 'bar'; - $this->identity = ['zee' => 'bang']; - - $this->httpClient = $this->getMock(Client::CLASS); - $this->configFactory = - $this->getMockBuilder(ConfigFactory::CLASS) - ->disableOriginalConstructor() - ->getMock(); - $this->state = - $this->getMockBuilder(State::CLASS) - ->disableOriginalConstructor() - ->getMock(); - $this->cache = $this->createMock(CacheBackendInterface::CLASS); - $this->json = $this->createMock('Drupal\Component\Serialization\Json'); - $this->encryption = $this->createMock(EncryptServiceInterface::CLASS); - $this->profileManager = $this->createMock(EncryptionProfileManagerInterface::CLASS); - $this->lock = $this->createMock(LockBackendInterface::CLASS); - $this->encryptionProfile = $this->createMock(EncryptionProfileInterface::CLASS); - $this->json = $this->createMock(Json::CLASS); - $this->time = $this->createMock(TimeInterface::CLASS); - $this->client = $this->getMockBuilder(RestClient::CLASS) - ->setMethods(['doGetEncryptionProfile']) - ->setConstructorArgs([ - $this->httpClient, - $this->configFactory, - $this->state, - $this->cache, - $this->json, - $this->time, - $this->encryption, - $this->profileManager, - $this->lock, - ]) - ->getMock(); - } - - /** - * @covers ::encrypt - * - * encrypt is protected, so we get at it through ::getAccessToken - * This test covers the case where access token is NULL. - */ - public function testEncryptNull() { - // Test unencrypted. - $this->state->expects($this->any()) - ->method('get') - ->willReturn(NULL); - $this->client->expects($this->any()) - ->method('doGetEncryptionProfile') - ->willReturn(NULL); - $this->assertFalse($this->client->getAccessToken()); - } - - /** - * @covers ::encrypt - * - * This test covers the case where access token is not NULL. - */ - public function testEncryptNotNull() { - // Test unencrypted. - $this->state->expects($this->any()) - ->method('get') - ->willReturn('not null'); - $this->client->expects($this->any()) - ->method('doGetEncryptionProfile') - ->willReturn($this->encryptionProfile); - $this->encryption->expects($this->any()) - ->method('decrypt') - ->willReturn($this->accessToken); - $this->assertEquals($this->accessToken, $this->client->getAccessToken()); - } - -} diff --git a/modules/salesforce_jwt/config/schema/salesforce_jwt.schema.yml b/modules/salesforce_jwt/config/schema/salesforce_jwt.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..1bef35d46118dafd25b3a0fafe721cd3e0e5dc7a --- /dev/null +++ b/modules/salesforce_jwt/config/schema/salesforce_jwt.schema.yml @@ -0,0 +1,16 @@ +salesforce.auth_provider_settings.jwt: + type: mapping + label: 'Salesforce JWT Provider Settings' + mapping: + consumer_key: + type: string + label: 'Consumer Key' + login_user: + type: string + label: 'Login User' + login_url: + type: uri + label: 'Login URL' + encrypt_key: + type: key.key.[%key] + label: 'Encryption Key ID' diff --git a/modules/salesforce_jwt/salesforce_jwt.info.yml b/modules/salesforce_jwt/salesforce_jwt.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..040a7403776d9e8109fe4d06e7a6f16f3095f976 --- /dev/null +++ b/modules/salesforce_jwt/salesforce_jwt.info.yml @@ -0,0 +1,8 @@ +name: Salesforce JWT Auth Provider +type: module +description: Provides key-based Salesforce authentication. +core: 8.x +package: Salesforce +dependencies: + - salesforce + - key diff --git a/modules/salesforce_jwt/src/Consumer/JWTCredentials.php b/modules/salesforce_jwt/src/Consumer/JWTCredentials.php new file mode 100644 index 0000000000000000000000000000000000000000..c1b997e96a95244b1613ed9527b9a55e27405bb3 --- /dev/null +++ b/modules/salesforce_jwt/src/Consumer/JWTCredentials.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\salesforce_jwt\Consumer; + +use Drupal\salesforce\Consumer\SalesforceCredentials; + +/** + * JWT credentials. + */ +class JWTCredentials extends SalesforceCredentials { + + /** + * Pre-authorized login user for JWT OAuth authentication. + * + * @var string + */ + protected $loginUser; + + /** + * Id of authorization key for this JWT Credential. + * + * @var string + */ + protected $keyId; + + /** + * {@inheritdoc} + */ + public function __construct($consumerKey, $loginUrl, $loginUser, $keyId) { + parent::__construct($consumerKey, $loginUrl); + $this->loginUser = $loginUser; + $this->keyId = $keyId; + } + + /** + * Login user getter. + * + * @return string + * The login user. + */ + public function getLoginUser() { + return $this->loginUser; + } + + /** + * Authorization key getter. + * + * @return string + * The key id. + */ + public function getKeyId() { + return $this->keyId; + } + +} 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 0000000000000000000000000000000000000000..24f5f6884a925272855a602613144455439c18d8 --- /dev/null +++ b/modules/salesforce_jwt/src/Plugin/SalesforceAuthProvider/SalesforceJWTPlugin.php @@ -0,0 +1,287 @@ +<?php + +namespace Drupal\salesforce_jwt\Plugin\SalesforceAuthProvider; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\salesforce_jwt\Consumer\JWTCredentials; +use Drupal\salesforce\SalesforceAuthProviderPluginBase; +use OAuth\Common\Http\Uri\Uri; +use Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Token\TokenInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Firebase\JWT\JWT; + +/** + * JWT Oauth plugin. + * + * @Plugin( + * id = "jwt", + * label = @Translation("Salesforce JWT OAuth") + * ) + */ +class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase { + + /** + * The credentials for this auth plugin. + * + * @var \Drupal\salesforce_jwt\Consumer\JWTCredentials + */ + protected $credentials; + + /** + * {@inheritdoc} + */ + const SERVICE_TYPE = 'jwt'; + + /** + * {@inheritdoc} + */ + const LABEL = 'JWT'; + + /** + * SalesforceAuthServiceBase constructor. + * + * @param string $id + * The plugin / auth config id. + * @param \Drupal\salesforce_jwt\Consumer\JWTCredentials $credentials + * The credentials. + * @param \OAuth\Common\Http\Client\ClientInterface $httpClient + * Http client wrapper. + * @param \Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface $storage + * Token storage. + * + * @throws \OAuth\OAuth2\Service\Exception\InvalidScopeException + * On error. + */ + public function __construct($id, JWTCredentials $credentials, ClientInterface $httpClient, SalesforceAuthTokenStorageInterface $storage) { + parent::__construct($credentials, $httpClient, $storage, [], new Uri($credentials->getLoginUrl())); + $this->id = $id; + } + + public function getConsumerSecret() { + return parent::getConsumerSecret(); // TODO: Change the autogenerated stub + } + + + /** + * {@inheritdoc} + */ + 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')); + } + + /** + * {@inheritdoc} + */ + public static function defaultConfiguration() { + $defaults = parent::defaultConfiguration(); + return array_merge($defaults, [ + 'login_user' => '', + 'encrypt_key' => '', + ]); + } + + /** + * {@inheritdoc} + */ + 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 <a href="https://drupal.org/project/key">Key</a> module. Please install before adding a JWT Auth config.')); + return $form; + } + if (!$this->keyRepository()->getKeyNamesAsOptions(['type' => 'authentication'])) { + $this->messenger()->addError($this->t('Please <a href="@href">add an authentication key</a> 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 input type here because its #process method doesn't + // fire on ajax, so 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->getKeyId(), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + if (!$this->keyRepository()) { + $form_state->setError($form, $this->t('JWT Auth requires <a href="https://drupal.org/project/key">Key</a> module. Please install before adding a JWT Auth config.')); + return; + } + parent::validateConfigurationForm($form, $form_state); + $this->setConfiguration($form_state->getValues()); + try { + $this->validateCredentials($this->getLoginUrl()); + } + catch (\Exception $e) { + $form_state->setError($form, $e->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + parent::save($form, $form_state); + try { + $this->setConfiguration($form_state->getValues()); + $this->getToken($this->getLoginUrl()); + \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()])); + } + } + + /** + * Validate credentials prior to saving them. + * + * @param string $login_url + * The login URL, from form input, against which to validate. + * + * @return \OAuth\Common\Token\TokenInterface|\OAuth\OAuth2\Token\StdOAuth2Token + * On success. + * + * @throws \OAuth\Common\Http\Exception\TokenResponseException + * On error. + */ + protected function validateCredentials($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); + return $token; + } + + /** + * 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 \OAuth\Common\Token\TokenInterface $token + * The JWT OAuth token to refresh. + * + * @return \OAuth\Common\Token\TokenInterface + * On success. + * + * @throws \OAuth\OAuth2\Service\Exception\MissingRefreshTokenException + * On error. + * @throws \OAuth\Common\Http\Exception\TokenResponseException + * On error. + */ + 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. + */ + protected function generateAssertion() { + $key = $this->keyRepository()->getKey($this->credentials->getKeyId())->getKeyValue(); + $token = $this->generateAssertionClaim(); + return JWT::encode($token, $key, 'RS256'); + } + + /** + * Returns a JSON encoded JWT Claim. + * + * @return array + * The claim array. + */ + protected function generateAssertionClaim() { + return [ + 'iss' => $this->credentials->getConsumerKey(), + 'sub' => $this->credentials->getLoginUser(), + 'aud' => $this->credentials->getLoginUrl(), + 'exp' => \Drupal::time()->getCurrentTime() + 60, + ]; + } + +} diff --git a/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php b/modules/salesforce_mapping/src/Form/SalesforceMappingDeleteForm.php index 066fc61ac62ee29ca4c7866ad0197ea957b38809..ec011e9b4aa0fc2e5ba82a3e5f854ec8d3b59de7 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 7a3531c86f6d82a6e98039971af88edb22e064ee..a79db8b7815074a664f24484255c030a642a6b5e 100644 --- a/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php +++ b/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php @@ -30,7 +30,7 @@ abstract class SalesforceMappingFormCrudBase extends SalesforceMappingFormBase { $object_type_options = $this->getSalesforceObjectTypeOptions(); } 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 <a href="@href">check your credentials</a> and try again: %message', ['@href' => $href->toString(), '%message' => $e->getMessage()]), 'error'); return $form; } diff --git a/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php.orig b/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php.orig new file mode 100644 index 0000000000000000000000000000000000000000..7a3531c86f6d82a6e98039971af88edb22e064ee --- /dev/null +++ b/modules/salesforce_mapping/src/Form/SalesforceMappingFormCrudBase.php.orig @@ -0,0 +1,513 @@ +<?php + +namespace Drupal\salesforce_mapping\Form; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\salesforce_mapping\MappingConstants; +use Drupal\Core\Url; +use Drupal\salesforce\Event\SalesforceEvents; +use Drupal\salesforce\Event\SalesforceErrorEvent; + +/** + * Salesforce Mapping Form base. + */ +abstract class SalesforceMappingFormCrudBase extends SalesforceMappingFormBase { + + /** + * The storage controller. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $storageController; + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // Perform our salesforce queries first, so that if we can't connect we + // don't waste time on the rest of the form. + try { + $object_type_options = $this->getSalesforceObjectTypeOptions(); + } + catch (\Exception $e) { + $href = new Url('salesforce.authorize'); + drupal_set_message($this->t('Error when connecting to Salesforce. Please <a href="@href">check your credentials</a> and try again: %message', ['@href' => $href->toString(), '%message' => $e->getMessage()]), 'error'); + return $form; + } + $form = parent::buildForm($form, $form_state); + $mapping = $this->entity; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#default_value' => $mapping->label(), + '#required' => TRUE, + '#weight' => -30, + ]; + $form['id'] = [ + '#type' => 'machine_name', + '#required' => TRUE, + '#default_value' => $mapping->id(), + '#maxlength' => 255, + '#machine_name' => [ + 'exists' => ['Drupal\salesforce_mapping\Entity\SalesforceMapping', 'load'], + 'source' => ['label'], + ], + '#disabled' => !$mapping->isNew(), + '#weight' => -20, + ]; + + $form['drupal_entity'] = [ + '#title' => $this->t('Drupal entity'), + '#type' => 'details', + '#attributes' => [ + 'id' => 'edit-drupal-entity', + ], + // Gently discourage admins from breaking existing fieldmaps: + '#open' => $mapping->isNew(), + ]; + + $entity_types = $this->getEntityTypeOptions(); + $form['drupal_entity']['drupal_entity_type'] = [ + '#title' => $this->t('Drupal Entity Type'), + '#id' => 'edit-drupal-entity-type', + '#type' => 'select', + '#description' => $this->t('Select a Drupal entity type to map to a Salesforce object.'), + '#options' => $entity_types, + '#default_value' => $mapping->drupal_entity_type, + '#required' => TRUE, + '#empty_option' => $this->t('- Select -'), + '#ajax' => [ + 'callback' => [$this, 'bundleCallback'], + 'event' => 'change', + 'wrapper' => 'drupal_bundle', + ], + ]; + + $form['drupal_entity']['drupal_bundle'] = [ + '#title' => $this->t('Bundle'), + '#type' => 'select', + '#default_value' => $mapping->drupal_bundle, + '#empty_option' => $this->t('- Select -'), + // Bundle select options will get completely replaced after user selects + // an entity, but we include all possibilities here for js-free + // compatibility (for simpletest) + '#options' => $this->getBundleOptions(), + '#required' => TRUE, + '#prefix' => '<div id="drupal_bundle">', + '#suffix' => '</div>', + // Don't expose the bundle listing until user has selected an entity. + '#states' => [ + 'visible' => [ + ':input[name="drupal_entity_type"]' => ['!value' => ''], + ], + ], + ]; + $input = $form_state->getUserInput(); + if (!empty($input) && !empty($input['drupal_entity_type'])) { + $entity_type = $input['drupal_entity_type']; + } + else { + $entity_type = $form['drupal_entity']['drupal_entity_type']['#default_value']; + } + $bundle_info = $this->entityManager->getBundleInfo($entity_type); + + if (!empty($bundle_info)) { + $form['drupal_entity']['drupal_bundle']['#options'] = []; + $form['drupal_entity']['drupal_bundle']['#title'] = $this->t('@entity_type Bundle', ['@entity_type' => $entity_types[$entity_type]]); + foreach ($bundle_info as $key => $info) { + $form['drupal_entity']['drupal_bundle']['#options'][$key] = $info['label']; + } + } + + $form['salesforce_object'] = [ + '#title' => $this->t('Salesforce object'), + '#id' => 'edit-salesforce-object', + '#type' => 'details', + // Gently discourage admins from breaking existing fieldmaps: + '#open' => $mapping->isNew(), + ]; + + $salesforce_object_type = ''; + if (!empty($form_state->getValues()) && !empty($form_state->getValue('salesforce_object_type'))) { + $salesforce_object_type = $form_state->getValue('salesforce_object_type'); + } + elseif ($mapping->salesforce_object_type) { + $salesforce_object_type = $mapping->salesforce_object_type; + } + $form['salesforce_object']['salesforce_object_type'] = [ + '#title' => $this->t('Salesforce Object'), + '#id' => 'edit-salesforce-object-type', + '#type' => 'select', + '#description' => $this->t('Select a Salesforce object to map.'), + '#default_value' => $salesforce_object_type, + '#options' => $object_type_options, + '#required' => TRUE, + '#empty_option' => $this->t('- Select -'), + ]; + + // @TODO either change sync_triggers to human readable values, or make them work as hex flags again. + $trigger_options = $this->getSyncTriggerOptions(); + $form['sync_triggers'] = [ + '#title' => t('Action triggers'), + '#type' => 'details', + '#open' => TRUE, + '#tree' => TRUE, + '#description' => t('Select which actions on Drupal entities and Salesforce + objects should trigger a synchronization. These settings are used by the + salesforce_push and salesforce_pull modules.' + ), + ]; + if (empty($trigger_options)) { + $form['sync_triggers']['#description'] .= ' ' . $this->t('<br/><em>No trigger options are available when Salesforce Push and Pull modules are disabled. Enable one or both modules to allow Push or Pull processing.</em>'); + } + + foreach ($trigger_options as $option => $label) { + $form['sync_triggers'][$option] = [ + '#title' => $label, + '#type' => 'checkbox', + '#default_value' => !empty($mapping->sync_triggers[$option]), + ]; + } + + if ($this->moduleHandler->moduleExists('salesforce_pull')) { + // @TODO should push and pull settings get moved into push and pull modules? + $form['pull'] = [ + '#title' => t('Pull Settings'), + '#type' => 'details', + '#description' => '', + '#open' => TRUE, + '#tree' => FALSE, + '#states' => [ + 'visible' => [ + ':input[name^="sync_triggers[pull"]' => ['checked' => TRUE], + ], + ], + ]; + + if (!$mapping->isNew()) { + $form['pull']['last_pull_date'] = [ + '#type' => 'item', + '#title' => t('Last Pull Date: %last_pull', ['%last_pull' => $mapping->getLastPullTime() ? \Drupal::service('date.formatter')->format($mapping->getLastPullTime()) : 'never']), + '#markup' => t('Resetting last pull date will cause salesforce pull module to query for updated records without respect for the pull trigger date. This is useful, for example, to re-pull all records after a purge.'), + ]; + $form['pull']['last_pull_reset'] = [ + '#type' => 'button', + '#value' => t('Reset Last Pull Date'), + '#disabled' => $mapping->getLastPullTime() == NULL, + '#limit_validation_errors' => [], + '#validate' => ['::lastPullReset'], + ]; + + $form['pull']['last_delete_date'] = [ + '#type' => 'item', + '#title' => t('Last Delete Date: %last_pull', ['%last_pull' => $mapping->getLastDeleteTime() ? \Drupal::service('date.formatter')->format($mapping->getLastDeleteTime()) : 'never']), + '#markup' => t('Resetting last delete date will cause salesforce pull module to query for deleted record without respect for the pull trigger date.'), + ]; + $form['pull']['last_delete_reset'] = [ + '#type' => 'button', + '#value' => t('Reset Last Delete Date'), + '#disabled' => $mapping->getLastDeleteTime() == NULL, + '#limit_validation_errors' => [], + '#validate' => ['::lastDeleteReset'], + ]; + + // This doesn't work until after mapping gets saved. + // @TODO figure out best way to alert admins about this, or AJAX-ify it. + $form['pull']['pull_trigger_date'] = [ + '#type' => 'select', + '#title' => t('Date field to trigger pull'), + '#description' => t('Poll Salesforce for updated records based on the given date field. Defaults to "Last Modified Date".'), + '#required' => $mapping->salesforce_object_type, + '#default_value' => $mapping->pull_trigger_date, + '#options' => $this->getPullTriggerOptions(), + ]; + } + + $form['pull']['pull_where_clause'] = [ + '#title' => t('Pull query SOQL "Where" clause'), + '#type' => 'textarea', + '#description' => t('Add a "where" SOQL condition clause to limit records pulled from Salesforce. e.g. Email != \'\' AND RecordType.DevelopName = \'ExampleRecordType\''), + '#default_value' => $mapping->pull_where_clause, + ]; + + $form['pull']['pull_where_clause'] = [ + '#title' => t('Pull query SOQL "Where" clause'), + '#type' => 'textarea', + '#description' => t('Add a "where" SOQL condition clause to limit records pulled from Salesforce. e.g. Email != \'\' AND RecordType.DevelopName = \'ExampleRecordType\''), + '#default_value' => $mapping->pull_where_clause, + ]; + + $form['pull']['pull_frequency'] = [ + '#title' => t('Pull Frequency'), + '#type' => 'number', + '#default_value' => $mapping->pull_frequency, + '#description' => t('Enter a frequency, in seconds, for how often this mapping should be used to pull data to Drupal. Enter 0 to pull as often as possible. FYI: 1 hour = 3600; 1 day = 86400. <em>NOTE: pull frequency is shared per-Salesforce Object. The setting is exposed here for convenience.</em>'), + ]; + } + + if ($this->moduleHandler->moduleExists('salesforce_push')) { + $form['push'] = [ + '#title' => t('Push Settings'), + '#type' => 'details', + '#description' => t('The asynchronous push queue is always enabled in Drupal 8: real-time push fails are queued for async push. Alternatively, you can choose to disable real-time push and use async-only.'), + '#open' => TRUE, + '#tree' => FALSE, + '#states' => [ + 'visible' => [ + ':input[name^="sync_triggers[push"]' => ['checked' => TRUE], + ], + ], + ]; + + $form['push']['async'] = [ + '#title' => t('Disable real-time push'), + '#type' => 'checkbox', + '#description' => t('When real-time push is disabled, enqueue changes and push to Salesforce asynchronously during cron. When disabled, push changes immediately upon entity CRUD, and only enqueue failures for async push.'), + '#default_value' => $mapping->async, + ]; + + $form['push']['push_frequency'] = [ + '#title' => t('Push Frequency'), + '#type' => 'number', + '#default_value' => $mapping->push_frequency, + '#description' => t('Enter a frequency, in seconds, for how often this mapping should be used to push data to Salesforce. Enter 0 to push as often as possible. FYI: 1 hour = 3600; 1 day = 86400.'), + '#min' => 0, + ]; + + $form['push']['push_limit'] = [ + '#title' => t('Push Limit'), + '#type' => 'number', + '#default_value' => $mapping->push_limit, + '#description' => t('Enter the maximum number of records to be pushed to Salesforce during a single queue batch. Enter 0 to process as many records as possible, subject to the global push queue limit.'), + '#min' => 0, + ]; + + $form['push']['push_retries'] = [ + '#title' => t('Push Retries'), + '#type' => 'number', + '#default_value' => $mapping->push_retries, + '#description' => t("Enter the maximum number of attempts to push a record to Salesforce before it's considered failed. Enter 0 for no limit."), + '#min' => 0, + ]; + + $form['push']['weight'] = [ + '#title' => t('Weight'), + '#type' => 'select', + '#options' => array_combine(range(-50, 50), range(-50, 50)), + '#description' => t('Not yet in use. During cron, mapping weight determines in which order items will be pushed. Lesser weight items will be pushed before greater weight items.'), + '#default_value' => $mapping->weight, + ]; + + $description = t('Check this box to disable cron push processing for this mapping, and allow standalone processing. A URL will be generated after saving the mapping.'); + if ($mapping->id()) { + $standalone_url = Url::fromRoute( + 'salesforce_push.endpoint.salesforce_mapping', + [ + 'salesforce_mapping' => $mapping->id(), + 'key' => \Drupal::state()->get('system.cron_key'), + ], + ['absolute' => TRUE]) + ->toString(); + $description = t('Check this box to disable cron push processing for this mapping, and allow standalone processing via this URL: <a href=":url">:url</a>', [':url' => $standalone_url]); + } + + $form['push']['push_standalone'] = [ + '#title' => t('Enable standalone push queue processing'), + '#type' => 'checkbox', + '#description' => $description, + '#default_value' => $mapping->push_standalone, + ]; + + // If global standalone is enabled, then we force this mapping's + // standalone property to true. + if ($this->config('salesforce.settings')->get('standalone')) { + $settings_url = Url::fromRoute('salesforce.global_settings'); + $form['push']['push_standalone']['#default_value'] = TRUE; + $form['push']['push_standalone']['#disabled'] = TRUE; + $form['push']['push_standalone']['#description'] .= ' ' . t('See also <a href="@url">global standalone processing settings</a>.', ['@url' => $settings_url]); + } + } + + $form['meta'] = [ + '#type' => 'details', + '#open' => TRUE, + '#tree' => FALSE, + '#title' => t('Additional properties'), + ]; + + $form['meta']['weight'] = [ + '#title' => t('Weight'), + '#type' => 'select', + '#options' => array_combine(range(-50, 50), range(-50, 50)), + '#description' => t('During cron, mapping weight determines in which order items will be pushed or pulled. Lesser weight items will be pushed or pulled before greater weight items.'), + '#default_value' => $mapping->weight, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $bundles = $this->entityManager->getBundleInfo($form_state->getValue('drupal_entity_type')); + if (empty($bundles[$form_state->getValue('drupal_bundle')])) { + $form_state->setErrorByName('drupal_bundle', $this->t('Invalid bundle for entity type.')); + } + $button = $form_state->getTriggeringElement(); + if ($button['#id'] != $form['actions']['submit']['#id']) { + // Skip validation unless we hit the "save" button. + return; + } + + parent::validateForm($form, $form_state); + + if ($this->entity->doesPull()) { + try { + $this->client->query($this->entity->getPullQuery()); + } + catch (\Exception $e) { + $form_state->setError($form['pull']['pull_where_clause'], $this->t('Test pull query returned an error. Please check logs for error details.')); + \Drupal::service('event_dispatcher')->dispatch(SalesforceEvents::ERROR, new SalesforceErrorEvent($e)); + } + } + } + + /** + * Submit handler for "reset pull timestamp" button. + */ + public function lastPullReset(array $form, FormStateInterface $form_state) { + $mapping = $this->entity->setLastPullTime(NULL); + $this->entityTypeManager + ->getStorage('salesforce_mapped_object') + ->setForcePull($mapping); + } + + /** + * Submit handler for "reset delete timestamp" button. + */ + public function lastDeleteReset(array $form, FormStateInterface $form_state) { + $this->entity->setLastDeleteTime(NULL); + } + + /** + * Ajax callback for salesforce_mapping_form() bundle selection. + */ + public function bundleCallback($form, FormStateInterface $form_state) { + return $form['drupal_entity']['drupal_bundle']; + } + + /** + * Return an array of all bundle options, for javascript-free fallback. + */ + protected function getBundleOptions() { + $entities = $this->getEntityTypeOptions(); + $bundles = $this->entityManager->getAllBundleInfo(); + $options = []; + foreach ($bundles as $entity => $bundle_info) { + if (empty($entities[$entity])) { + continue; + } + foreach ($bundle_info as $bundle => $info) { + $entity_label = $entities[$entity]; + $options[(string) $entity_label][$bundle] = (string) $info['label']; + } + } + return $options; + } + + /** + * Return a list of Drupal entity types for mapping. + * + * @return array + * An array of values keyed by machine name of the entity with the label as + * the value, formatted to be appropriate as a value for #options. + */ + protected function getEntityTypeOptions() { + $options = []; + $mappable_entity_types = $this->mappableEntityTypes + ->getMappableEntityTypes(); + foreach ($mappable_entity_types as $entity_type_id => $info) { + $options[$info->id()] = $info->getLabel(); + } + uasort($options, function ($a, $b) { + return strcmp($a->render(), $b->render()); + }); + return $options; + } + + /** + * Helper to retreive a list of object type options. + * + * @return array + * An array of values keyed by machine name of the object with the label as + * the value, formatted to be appropriate as a value for #options. + */ + protected function getSalesforceObjectTypeOptions() { + $sfobject_options = []; + + // Note that we're filtering SF object types to a reasonable subset. + $config = $this->config('salesforce.settings'); + $filter = $config->get('show_all_objects') ? [] : [ + 'updateable' => TRUE, + 'triggerable' => TRUE, + ]; + $sfobjects = $this->client->objects($filter); + foreach ($sfobjects as $object) { + $sfobject_options[$object['name']] = $object['label'] . ' (' . $object['name'] . ')'; + } + asort($sfobject_options); + return $sfobject_options; + } + + /** + * Return form options for available sync triggers. + * + * @return array + * Array of sync trigger options keyed by their machine name with their + * label as the value. + */ + protected function getSyncTriggerOptions() { + $options = []; + if ($this->moduleHandler->moduleExists('salesforce_push')) { + $options += [ + MappingConstants::SALESFORCE_MAPPING_SYNC_DRUPAL_CREATE => t('Drupal entity create (push)'), + MappingConstants::SALESFORCE_MAPPING_SYNC_DRUPAL_UPDATE => t('Drupal entity update (push)'), + MappingConstants::SALESFORCE_MAPPING_SYNC_DRUPAL_DELETE => t('Drupal entity delete (push)'), + ]; + } + if ($this->moduleHandler->moduleExists('salesforce_pull')) { + $options += [ + MappingConstants::SALESFORCE_MAPPING_SYNC_SF_CREATE => t('Salesforce object create (pull)'), + MappingConstants::SALESFORCE_MAPPING_SYNC_SF_UPDATE => t('Salesforce object update (pull)'), + MappingConstants::SALESFORCE_MAPPING_SYNC_SF_DELETE => t('Salesforce object delete (pull)'), + ]; + } + return $options; + } + + /** + * Return an array of Date fields suitable for use a pull trigger field. + * + * @return array + * The options array. + */ + private function getPullTriggerOptions() { + $options = []; + try { + $describe = $this->getSalesforceObject(); + } + catch (\Exception $e) { + // No describe results means no datetime fields. We're done. + return []; + } + + foreach ($describe->getFields() as $field) { + if ($field['type'] == 'datetime') { + $options[$field['name']] = $field['label']; + } + } + return $options; + } + +} diff --git a/modules/salesforce_oauth/config/schema/salesforce_oauth.schema.yml b/modules/salesforce_oauth/config/schema/salesforce_oauth.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..d4ac5f53ec95d206c493ce9d37dbbcaefc71d8d1 --- /dev/null +++ b/modules/salesforce_oauth/config/schema/salesforce_oauth.schema.yml @@ -0,0 +1,13 @@ +salesforce.auth_provider_settings.oauth: + type: mapping + label: 'Salesforce OAuth Provider Settings' + mapping: + consumer_key: + type: string + label: 'Consumer Key' + consumer_secret: + type: string + label: 'Consumer Secret' + login_url: + type: uri + label: 'Login URL' diff --git a/modules/salesforce_oauth/salesforce_oauth.info.yml b/modules/salesforce_oauth/salesforce_oauth.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..7d97f8f70ed84d21088cb9291e1bf32d494dc917 --- /dev/null +++ b/modules/salesforce_oauth/salesforce_oauth.info.yml @@ -0,0 +1,7 @@ +name: Salesforce OAuth user-agent Provider +type: module +description: Provides user-agent-based Salesforce OAuth authentication. +core: 8.x +package: Salesforce +dependencies: + - salesforce diff --git a/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php b/modules/salesforce_oauth/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php similarity index 89% rename from src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php rename to modules/salesforce_oauth/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php index f45134f82900b4573fc8bb0e37f81a58dfd8092f..b6f67cfae320550e4f5c82c38dcbdc4e1f0230a1 100644 --- a/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php +++ b/modules/salesforce_oauth/src/Plugin/SalesforceAuthProvider/SalesforceOAuthPlugin.php @@ -1,12 +1,12 @@ <?php -namespace Drupal\salesforce\Plugin\SalesforceAuthProvider; +namespace Drupal\salesforce_oauth\Plugin\SalesforceAuthProvider; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\TrustedRedirectResponse; -use Drupal\salesforce\Consumer\OAuthCredentials; +use Drupal\salesforce\Consumer\SalesforceCredentials; +use Drupal\salesforce\SalesforceAuthProviderInterface; use Drupal\salesforce\SalesforceAuthProviderPluginBase; -use Drupal\salesforce\SalesforceOAuthPluginInterface; use Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface; use OAuth\Common\Http\Client\ClientInterface; use OAuth\Common\Http\Uri\Uri; @@ -22,12 +22,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. */ -class SalesforceOAuthPlugin extends SalesforceAuthProviderPluginBase implements SalesforceOAuthPluginInterface { +class SalesforceOAuthPlugin extends SalesforceAuthProviderPluginBase implements SalesforceAuthProviderInterface { /** * Credentials. * - * @var \Drupal\salesforce\Consumer\OAuthCredentials + * @var \Drupal\salesforce\Consumer\SalesforceCredentials */ protected $credentials; @@ -46,7 +46,7 @@ class SalesforceOAuthPlugin extends SalesforceAuthProviderPluginBase implements * * @param string $id * The plugin id. - * @param \Drupal\salesforce\Consumer\OAuthCredentials $credentials + * @param \Drupal\salesforce\Consumer\SalesforceCredentials $credentials * The credentials. * @param \OAuth\Common\Http\Client\ClientInterface $httpClient * The oauth http client. @@ -56,7 +56,7 @@ class SalesforceOAuthPlugin extends SalesforceAuthProviderPluginBase implements * @throws \OAuth\OAuth2\Service\Exception\InvalidScopeException * Comment. */ - public function __construct($id, OAuthCredentials $credentials, ClientInterface $httpClient, SalesforceAuthTokenStorageInterface $storage) { + public function __construct($id, SalesforceCredentials $credentials, ClientInterface $httpClient, SalesforceAuthTokenStorageInterface $storage) { parent::__construct($credentials, $httpClient, $storage, [], new Uri($credentials->getLoginUrl())); $this->id = $id; } @@ -66,7 +66,7 @@ class SalesforceOAuthPlugin extends SalesforceAuthProviderPluginBase implements */ 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']); + $cred = new SalesforceCredentials($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')); } diff --git a/modules/salesforce_pull/salesforce_pull.module b/modules/salesforce_pull/salesforce_pull.module index 5904fe4e746b6b41f77af026b7697eb98fbe9513..b3adc649217aa81f65dd6c600d4b2f77c35fe653 100644 --- a/modules/salesforce_pull/salesforce_pull.module +++ b/modules/salesforce_pull/salesforce_pull.module @@ -9,8 +9,7 @@ * Implements hook_cron(). */ function salesforce_pull_cron() { - $sfapi = \Drupal::service('salesforce.client'); - if ($sfapi->isAuthorized()) { + if (\Drupal::service('plugin.manager.salesforce.auth_providers')->getToken()) { \Drupal::service('salesforce_pull.queue_handler')->getUpdatedRecords(); \Drupal::service('salesforce_pull.delete_handler')->processDeletedRecords(); } diff --git a/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php b/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php index ee9e7c294a4b009834db0f00fbe0eb916ca3de02..d27f739a611bb3a863b440b22359c694cbf01ebf 100644 --- a/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php +++ b/modules/salesforce_push/src/Plugin/SalesforcePushQueueProcessor/Rest.php @@ -2,6 +2,8 @@ namespace Drupal\salesforce_push\Plugin\SalesforcePushQueueProcessor; +use Drupal\salesforce\SalesforceAuthProviderInterface; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\PluginBase; @@ -34,13 +36,6 @@ class Rest extends PluginBase implements PushQueueProcessorInterface { */ protected $queue; - /** - * Salesforce client service. - * - * @var \Drupal\salesforce\Rest\RestClientInterface - */ - protected $client; - /** * Storage handler for SF mappings. * @@ -69,6 +64,13 @@ class Rest extends PluginBase implements PushQueueProcessorInterface { */ protected $etm; + /** + * Auth manager. + * + * @var \Drupal\salesforce\SalesforceAuthProviderPluginManager + */ + protected $authMan; + /** * Rest constructor. * @@ -90,14 +92,14 @@ class Rest extends PluginBase implements PushQueueProcessorInterface { * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, PushQueueInterface $queue, RestClientInterface $client, EntityTypeManagerInterface $etm, EventDispatcherInterface $eventDispatcher) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, PushQueueInterface $queue, EntityTypeManagerInterface $etm, EventDispatcherInterface $eventDispatcher, SalesforceAuthProviderPluginManager $authMan) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->queue = $queue; - $this->client = $client; $this->etm = $etm; $this->mappingStorage = $etm->getStorage('salesforce_mapping'); $this->mappedObjectStorage = $etm->getStorage('salesforce_mapped_object'); $this->eventDispatcher = $eventDispatcher; + $this->authMan = $authMan; } /** @@ -108,7 +110,8 @@ class Rest extends PluginBase implements PushQueueProcessorInterface { $container->get('queue.salesforce_push'), $container->get('salesforce.client'), $container->get('entity_type.manager'), - $container->get('event_dispatcher') + $container->get('event_dispatcher'), + $container->get('plugin.manager.salesforce.auth_providers') ); } @@ -116,7 +119,7 @@ class Rest extends PluginBase implements PushQueueProcessorInterface { * Process push queue items. */ public function process(array $items) { - if (!$this->client->isAuthorized()) { + if (!$this->authMan->getToken()) { throw new SuspendQueueException('Salesforce client not authorized.'); } foreach ($items as $item) { diff --git a/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php b/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php index d0cab159c4e432200cb198755e55c7241bbfd523..1d23fff25fd9246be4f1d1b810332c58ada29937 100644 --- a/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php +++ b/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php @@ -93,15 +93,6 @@ class SalesforcePushQueueProcessorRestTest extends UnitTestCase { $this->eventDispatcher, ]); - $this->client->expects($this->at(0)) - ->method('isAuthorized') - ->willReturn(TRUE); - - // Test suspend queue if not authorized. - $this->client->expects($this->at(1)) - ->method('isAuthorized') - ->willReturn(FALSE); - $this->handler->expects($this->once()) ->method('processItem') ->willReturn(NULL); diff --git a/modules/salesforce_soap/salesforce_soap.services.yml b/modules/salesforce_soap/salesforce_soap.services.yml index 3bf301f2887e09e0e6e9a78b75eb0acd255654b4..1c54fdbe189f3fb21011769a257a6a86f465cbda 100644 --- a/modules/salesforce_soap/salesforce_soap.services.yml +++ b/modules/salesforce_soap/salesforce_soap.services.yml @@ -1,4 +1,4 @@ services: salesforce.soap_client: class: Drupal\salesforce_soap\Soap\SoapClient - arguments: ['@salesforce.client'] + arguments: ['@salesforce.client', '@plugin.manager.salesforce.auth_providers'] diff --git a/modules/salesforce_soap/src/Soap/SoapClient.php b/modules/salesforce_soap/src/Soap/SoapClient.php index 28dbae35a4b5f1c4e87f16e1c61f10eccc0fddd8..3ed47af5f51fe0d91cf89f25cf2303fba92fa7b4 100644 --- a/modules/salesforce_soap/src/Soap/SoapClient.php +++ b/modules/salesforce_soap/src/Soap/SoapClient.php @@ -3,6 +3,7 @@ namespace Drupal\salesforce_soap\Soap; use Drupal\salesforce\Rest\RestClientInterface; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; use SforcePartnerClient; /** @@ -31,6 +32,13 @@ class SoapClient extends SforcePartnerClient implements SoapClientInterface { */ protected $wsdl; + /** + * Auth manager. + * + * @var \Drupal\salesforce\SalesforceAuthProviderPluginManager + */ + protected $authMan; + /** * Constructor which initializes the consumer. * @@ -40,7 +48,7 @@ class SoapClient extends SforcePartnerClient implements SoapClientInterface { * (Optional) Path to the WSDL that should be used. Defaults to using the * partner WSDL from the developerforce/force.com-toolkit-for-php package. */ - public function __construct(RestClientInterface $rest_api, $wsdl = NULL) { + public function __construct(RestClientInterface $rest_api, SalesforceAuthProviderPluginManager $authMan, $wsdl = NULL) { parent::__construct(); $this->restApi = $rest_api; @@ -66,19 +74,13 @@ class SoapClient extends SforcePartnerClient implements SoapClientInterface { public function connect() { $this->isConnected = FALSE; // Use the "isAuthorized" callback to initialize session headers. - if ($this->restApi->isAuthorized()) { - $this->createConnection($this->wsdl); - $token = $this->restApi->getAccessToken(); - if (!$token) { - $token = $this->restApi->refreshToken(); - } - $this->setSessionHeader($token); - $this->setEndPoint($this->restApi->getApiEndPoint('partner')); - $this->isConnected = TRUE; - } - else { + if (!$token = $this->authMan->getToken()) { throw new \Exception('Salesforce needs to be authorized to connect to this website.'); } + $this->createConnection($this->wsdl); + $this->setSessionHeader($token); + $this->setEndPoint($this->authMan->getProvider()->getApiEndpoint('partner')); + $this->isConnected = TRUE; } /** diff --git a/salesforce.install b/salesforce.install index 41b194482b260a0b930d3a31453ba39ad93fe5f4..46da9e73e5121bdb84e8ca777ab42395b20fc4ed 100644 --- a/salesforce.install +++ b/salesforce.install @@ -303,3 +303,26 @@ function salesforce_update_8006() { \Drupal::service('salesforce.auth_token_storage')->updateToken(); return "Updated legacy token to new plugin config."; } + +function salesforce_update_8401() { + // Enable salesforce_oauth module. + \Drupal::service('module_installer')->install(['salesforce_oauth']); + // purge old stateful values. + // clear old config. + throw new \Exception('foo'); + + \Drupal::configFactory()->getEditable('salesforce.settings') + ->clear('consumer_key') + ->clear('consumer_secret') + ->clear('login_url') + ->save(); + + \Drupal::state()->deleteMultiple([ + 'salesforce.access_token', + 'salesforce.refresh_token', + 'salesforce.identity', + 'salesforce.instance_url', + 'salesforce_encrypt.profile', + ]); + +} diff --git a/salesforce.links.action.yml b/salesforce.links.action.yml new file mode 100644 index 0000000000000000000000000000000000000000..f9d6829e9934c6ed3cdccecc7fbc10cc43f545a4 --- /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 2d7e8f1fc0f50f97c5e075f8ad79e5921bd9a3e2..92bae5c47a8bec58e1357a208fee1905a3a4c006 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 0000000000000000000000000000000000000000..f2715a9190f5ba9da1b07ccb11ee906f7c18baf0 --- /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 12bde9d8fd1cc016f4d9884ac166c858a643c781..97a01b89a4cdc12052b07d795b475e2b31920003 100644 --- a/salesforce.module +++ b/salesforce.module @@ -18,9 +18,9 @@ function salesforce_help($route_name, RouteMatchInterface $route_match) { if (!\Drupal::moduleHandler()->moduleExists('salesforce_mapping')) { $output .= '<p>' . t('In order to configure Salesforce Mappings, you must first enable the <a href=":url">Salesforce Mapping</a> module.', [':url' => (new Url('system.modules_list'))->toString()]) . '</p>'; } - $client = \Drupal::service('salesforce.client'); - if (!$client->isAuthorized()) { - $output .= '<p>' . t('You must <a href=":authorize">authorize your account with Salesforce</a> in order to configure Salesforce Mappings.', [':authorize' => (new Url('salesforce.authorize'))->toString()]) . '</p>'; + + if (!\Drupal::service('plugin.manager.salesforce.auth_providers')->getToken()) { + $output .= '<p>' . t('You must <a href=":authorize">authorize your account with Salesforce</a> in order to configure Salesforce Mappings.', [':authorize' => (new Url('salesforce.admin_config_salesforce'))->toString()]) . '</p>'; } return $output; diff --git a/salesforce.routing.yml b/salesforce.routing.yml index ffe3094f5ce7e01f8d546f576f64433392e44b75..adfd3683b09c05bfd53b94a0f80f235903b67de4 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 a72e9f999917c4349055b6749e24429d4f28d70d..a36c8753c52af6905fb85831c65f61cc4c9cf11e 100644 --- a/salesforce.services.yml +++ b/salesforce.services.yml @@ -1,7 +1,7 @@ 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 diff --git a/src/Consumer/OAuthCredentials.php b/src/Consumer/OAuthCredentials.php deleted file mode 100644 index 091672b61318458ee2f54e9294cb6ff22a588af2..0000000000000000000000000000000000000000 --- a/src/Consumer/OAuthCredentials.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -namespace Drupal\salesforce\Consumer; - -/** - * OAuth user agent credentials. - */ -class OAuthCredentials extends SalesforceCredentials { - - /** - * {@inheritdoc} - */ - public function __construct($consumerKey, $loginUrl, $consumerSecret) { - parent::__construct($consumerKey, $loginUrl); - $this->consumerSecret = $consumerSecret; - } - -} diff --git a/src/Consumer/SalesforceCredentials.php b/src/Consumer/SalesforceCredentials.php index 2ee0818f32e3e8ca03eefe003da0f83bc036e329..dfdb91c21830bbd3994d678d1724bf44847a708b 100644 --- a/src/Consumer/SalesforceCredentials.php +++ b/src/Consumer/SalesforceCredentials.php @@ -8,7 +8,7 @@ use OAuth\Common\Consumer\Credentials; /** * Salesforce credentials extension, for drupalisms. */ -abstract class SalesforceCredentials extends Credentials implements SalesforceCredentialsInterface { +class SalesforceCredentials extends Credentials implements SalesforceCredentialsInterface { /** * Login URL e.g. https://test.salesforce.com or https://login.salesforce.com. @@ -27,10 +27,11 @@ abstract class SalesforceCredentials extends Credentials implements SalesforceCr /** * {@inheritdoc} */ - public function __construct($consumerKey, $loginUrl) { + public function __construct($consumerKey, $loginUrl, $consumerSecret = NULL) { parent::__construct($consumerKey, NULL, NULL); $this->loginUrl = $loginUrl; $this->consumerKey = $consumerKey; + $this->consumerSecret = $consumerSecret; } /** diff --git a/src/Controller/SalesforceAuthListBuilder.php b/src/Controller/SalesforceAuthListBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..0937ebe7643f6b3f0cd4d842910c48810c32c3ad --- /dev/null +++ b/src/Controller/SalesforceAuthListBuilder.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\salesforce\Controller; + +use Drupal\Core\Config\Entity\ConfigEntityListBuilder; +use Drupal\Core\Entity\EntityInterface; + +/** + * List builder for salesforce_auth. + */ +class SalesforceAuthListBuilder extends ConfigEntityListBuilder { + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\salesforce\SalesforceAuthProviderInterface $plugin */ + $plugin = $entity->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 0ca9b91aa8a5e3931850e6d91ab42ced76c29626..0000000000000000000000000000000000000000 --- a/src/Controller/SalesforceController.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php - -namespace Drupal\salesforce\Controller; - -use Drupal\Component\Utility\UrlHelper; -use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Render\MetadataBubblingUrlGenerator; -use Drupal\salesforce\Rest\RestClientInterface; -use GuzzleHttp\Client; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -/** - * OAuth callback handler. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class SalesforceController extends ControllerBase { - - protected $client; - protected $httpClient; - - /** - * {@inheritdoc} - */ - public function __construct(RestClientInterface $rest, Client $httpClient, MetadataBubblingUrlGenerator $url_generator) { - $this->client = $rest; - $this->httpClient = $httpClient; - $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') - ); - } - - /** - * Wrapper for \Drupal::request(). - * - * @return \Symfony\Component\HttpFoundation\Request - * The currently active request object. - */ - protected function request() { - return \Drupal::request(); - } - - /** - * Display a success message on successful oauth. - */ - 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. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - 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->httpClient->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 0000000000000000000000000000000000000000..0b235729ad572dcaacf2edba3e932219c24e4de1 --- /dev/null +++ b/src/Controller/SalesforceOAuthController.php @@ -0,0 +1,69 @@ +<?php + +namespace Drupal\salesforce\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\Core\Url; +use Drupal\salesforce\Entity\SalesforceAuthConfig; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; + +/** + * + */ +class SalesforceOAuthController extends ControllerBase { + + protected $request; + protected $messenger; + protected $tempStore; + + /** + * {@inheritdoc} + */ + public function __construct(RequestStack $stack, MessengerInterface $messenger, PrivateTempStoreFactory $tempStoreFactory) { + $this->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 SalesforceAuthProviderInterface)) { + $this->messenger->addError('No OAuth config found. Please try again.'); + return new RedirectResponse(Url::fromRoute('entity.salesforce_auth.collection')->toString()); + } + + /** @var \Drupal\salesforce\SalesforceAuthProviderInterface $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/Form/AuthorizeForm.php b/src/Form/AuthorizeForm.php deleted file mode 100644 index 9c032a4137beb48664c9c7182473c4301ddb2226..0000000000000000000000000000000000000000 --- a/src/Form/AuthorizeForm.php +++ /dev/null @@ -1,220 +0,0 @@ -<?php - -namespace Drupal\salesforce\Form; - -use Drupal\Component\Utility\UrlHelper; -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Form\ConfigFormBase; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Routing\TrustedRedirectResponse; -use Drupal\Core\State\StateInterface; -use Drupal\Core\Url; -use Drupal\salesforce\Rest\RestClientInterface; -use Drupal\salesforce_encrypt\Rest\EncryptedRestClientInterface; -use GuzzleHttp\Exception\RequestException; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Drupal\salesforce\Event\SalesforceEvents; -use Drupal\salesforce\Event\SalesforceErrorEvent; - -/** - * Creates authorization form for Salesforce. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class AuthorizeForm extends ConfigFormBase { - - /** - * The Salesforce REST client. - * - * @var \Drupal\salesforce\Rest\RestClientInterface - */ - protected $client; - - /** - * The sevent dispatcher service.. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $eventDispatcher; - - /** - * The state keyvalue collection. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - - /** - * Constructs a \Drupal\system\ConfigFormBase object. - * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. - * @param \Drupal\salesforce\Rest\RestClientInterface $salesforce_client - * The factory for configuration objects. - * @param \Drupal\Core\State\StateInterface $state - * The state keyvalue collection to use. - * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher - * The event dispatcher. - */ - public function __construct(ConfigFactoryInterface $config_factory, RestClientInterface $salesforce_client, StateInterface $state, EventDispatcherInterface $event_dispatcher) { - parent::__construct($config_factory); - $this->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->client, EncryptedRestClientInterface::class); - $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->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->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->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->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 - * True is the given url is valid. - */ - public static function validEndpoint($url) { - return UrlHelper::isValid($url, TRUE); - } - - /** - * Validate handler for auth form. Basic sanity checking. - */ - 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->client->setConsumerKey($values['consumer_key']); - $this->client->setConsumerSecret($values['consumer_secret']); - $this->client->setLoginUrl($values['login_url']); - - try { - $path = $this->client->getAuthEndpointUrl(); - $query = [ - 'redirect_uri' => $this->client->getAuthCallbackUrl(), - 'response_type' => 'code', - 'client_id' => $this->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 2fe13c9137d304ca8a97d8a28bab26072e8cc49a..0000000000000000000000000000000000000000 --- a/src/Form/RevokeAuthorizationForm.php +++ /dev/null @@ -1,119 +0,0 @@ -<?php - -namespace Drupal\salesforce\Form; - -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Form\ConfigFormBase; -use Drupal\Core\Form\FormStateInterface; -use Drupal\salesforce\Event\SalesforceEvents; -use Drupal\salesforce\Event\SalesforceNoticeEvent; -use Drupal\salesforce\Rest\RestClientInterface; - -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - -/** - * Revoke the current oauth creds. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class RevokeAuthorizationForm extends ConfigFormBase { - - /** - * The Salesforce REST client. - * - * @var \Drupal\salesforce\Rest\RestClientInterface - */ - protected $client; - - /** - * The sevent dispatcher service.. - * - * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * The state keyvalue collection. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - - /** - * Constructs a \Drupal\system\ConfigFormBase object. - * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. - * @param \Drupal\salesforce\Rest\RestClientInterface $salesforce_client - * The factory for configuration objects. - * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher - * The event dispatcher. - */ - public function __construct(ConfigFactoryInterface $config_factory, RestClientInterface $salesforce_client, EventDispatcherInterface $event_dispatcher) { - parent::__construct($config_factory); - $this->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->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->client->setAccessToken(''); - $this->client->setRefreshToken(''); - $this->client->setInstanceUrl(''); - $this->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.")); - } - -} diff --git a/src/Form/SalesforceAuthDeleteForm.php b/src/Form/SalesforceAuthDeleteForm.php new file mode 100644 index 0000000000000000000000000000000000000000..048ba71d8b024770bfcd8c4e877555ad24529d97 --- /dev/null +++ b/src/Form/SalesforceAuthDeleteForm.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\salesforce\Form; + +use Drupal\Core\Entity\EntityConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; + +class SalesforceAuthDeleteForm extends EntityConfirmFormBase { + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->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 <a href="@href">assign a new auth provider</a> 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. + $this->messenger()->addStatus($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 0000000000000000000000000000000000000000..21eb56104a391737373ac8b56b5d54f49133bdb3 --- /dev/null +++ b/src/Form/SalesforceAuthForm.php @@ -0,0 +1,142 @@ +<?php + +namespace Drupal\salesforce\Form; + +use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\FormStateInterface; +use Drupal\salesforce\Plugin\SalesforceAuthProviderFormInterface; +use Drupal\salesforce_oauth\Entity\OAuthConfig; + +/** + * Entity form for salesforce_auth. + */ +class SalesforceAuthForm extends EntityForm { + + /** + * The config entity. + * + * @var \Drupal\salesforce\Entity\SalesforceAuthConfig + */ + protected $entity; + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $auth = $this->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' => '<div id="auth-settings">', + '#suffix' => '</div>', + ]; + $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')); + } + + public function save(array $form, FormStateInterface $form_state) { + parent::save($form, $form_state); + $this->entity->getPlugin()->save($form, $form_state); + } + + /** + * 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/src/Form/SalesforceAuthRevokeForm.php b/src/Form/SalesforceAuthRevokeForm.php new file mode 100644 index 0000000000000000000000000000000000000000..a69b21c5086a7fa9fee8c2f0dadb6ef7952960b7 --- /dev/null +++ b/src/Form/SalesforceAuthRevokeForm.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\salesforce\Form; + +use Drupal\Core\Entity\EntityConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; + +class AuthConfigDeleteForm extends EntityConfirmFormBase { + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->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 submitForm(array &$form, FormStateInterface $form_state) { + $this->entity->delete(); + + // Set a message that the entity was deleted. + $this->messenger()->addStatus($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 0000000000000000000000000000000000000000..aca5f81bd0dbf093e9602a47760f7f1aae61b3a7 --- /dev/null +++ b/src/Form/SalesforceAuthSettings.php @@ -0,0 +1,98 @@ +<?php + +namespace Drupal\salesforce\Form; + +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\salesforce\Event\SalesforceEvents; +use Drupal\salesforce\Event\SalesforceNoticeEvent; +use Drupal\salesforce\SalesforceAuthManager; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class SalesforceAuthSettings extends ConfigFormBase { + + protected $salesforceAuth; + protected $eventDispatcher; + + /** + * Constructs a \Drupal\system\ConfigFormBase object. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. + */ + public function __construct(ConfigFactoryInterface $config_factory, SalesforceAuthProviderPluginManager $salesforceAuth, EventDispatcherInterface $eventDispatcher) { + parent::__construct($config_factory); + $this->salesforceAuth = $salesforceAuth; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('plugin.manager.salesforce.auth_providers'), + $container->get('event_dispatcher') + ); + } + + + /** + * {@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($this->salesforceAuth->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.')); + $this->eventDispatcher->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 1dd19ddcd348eda07f503ddbda9e30c02ee6756a..fa66665e0919aa80d689f74c6ab52be9b7ef51d5 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -93,7 +93,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 <a href="@href">check your credentials</a> and try again: %message', ['@href' => $href->toString(), '%message' => $e->getMessage()]), 'error'); } diff --git a/src/Rest/RestClient.php b/src/Rest/RestClient.php index 26bfe039ac6463de613aa6dc28dd1af8802a5f25..eb28be374b5b4586f780a7d63c7a764c4658323c 100644 --- a/src/Rest/RestClient.php +++ b/src/Rest/RestClient.php @@ -4,11 +4,9 @@ namespace Drupal\salesforce\Rest; use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\NestedArray; -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\SalesforceAuthProviderPluginManager; use Drupal\salesforce\SelectQueryInterface; use Drupal\salesforce\SFID; @@ -17,7 +15,6 @@ use Drupal\salesforce\SelectQuery; use Drupal\salesforce\SelectQueryResult; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7\Response; use Drupal\Component\Datetime\TimeInterface; /** @@ -53,15 +50,6 @@ class RestClient implements RestClientInterface { */ protected $url; - /** - * Salesforce mutable config object. Useful for sets. - * - * @var \Drupal\Core\Config\Config - * - * @deprecated will be removed in 8.x-4.0 release. - */ - protected $mutableConfig; - /** * Salesforce immutable config object. Useful for gets. * @@ -90,16 +78,35 @@ class RestClient implements RestClientInterface { */ protected $json; - protected $httpClientOptions; + /** + * Auth provider manager. + * + * @var \Drupal\salesforce\SalesforceAuthProviderPluginManager + */ + protected $authManager; /** - * Token storage. + * Active auth provider. * - * @var \Drupal\salesforce\Storage\SalesforceAuthTokenStorage + * @var \Drupal\salesforce\SalesforceAuthProviderInterface + */ + protected $authProvider; + + /** + * Active auth provider config. * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. + * @var \Drupal\salesforce\Entity\SalesforceAuthConfig */ - private $storage; + protected $authConfig; + + /** + * Active auth token. + * + * @var \OAuth\OAuth2\Token\TokenInterface + */ + protected $authToken; + + protected $httpClientOptions; const CACHE_LIFETIME = 300; const LONGTERM_CACHE_LIFETIME = 86400; @@ -120,54 +127,35 @@ class RestClient implements RestClientInterface { * @param \Drupal\Component\Datetime\TimeInterface $time * The Time 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'); $this->immutableConfig = $this->configFactory->get('salesforce.settings'); $this->state = $state; $this->cache = $cache; $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; } - /** - * Storage helper. - * - * @return \Drupal\salesforce\Storage\SalesforceAuthTokenStorage - * The auth token storage service. - * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. - */ - private function storage() { - if (!$this->storage) { - $this->storage = \Drupal::service('salesforce.auth_token_storage'); - } - return $this->storage; - } - - /** - * {@inheritdoc} - */ - public function isAuthorized() { - return $this->getConsumerKey() && $this->getConsumerSecret() && $this->getRefreshToken(); - } - /** * {@inheritdoc} */ public function apiCall($path, array $params = [], $method = 'GET', $returnObject = FALSE) { - if (!$this->getAccessToken()) { - $this->refreshToken(); + if (!$this->authToken) { + $this->authManager->refreshToken(); } if (strpos($path, '/') === 0) { - $url = $this->getInstanceUrl() . $path; + $url = $this->authProvider->getInstanceUrl() . $path; } else { - $url = $this->getApiEndPoint() . $path; + $url = $this->authProvider->getApiEndPoint() . $path; } try { @@ -185,9 +173,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)); } @@ -229,12 +217,12 @@ class RestClient implements RestClientInterface { * @throws \GuzzleHttp\Exception\RequestException */ protected function apiHttpRequest($url, array $params, $method) { - if (!$this->getAccessToken()) { + if (!$this->authManager->getToken()) { throw new \Exception('Missing OAuth Token'); } $headers = [ - 'Authorization' => 'OAuth ' . $this->getAccessToken(), + 'Authorization' => 'OAuth ' . $this->authToken->getAccessToken(), 'Content-type' => 'application/json', ]; $data = NULL; @@ -248,11 +236,11 @@ class RestClient implements RestClientInterface { * {@inheritdoc} */ public function httpRequestRaw($url) { - if (!$this->getAccessToken()) { + if (!$this->authManager->getToken()) { 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); @@ -335,262 +323,6 @@ class RestClient implements RestClientInterface { return $data; } - /** - * {@inheritdoc} - */ - public function getApiEndPoint($api_type = 'rest') { - $url = &drupal_static(__FUNCTION__ . $api_type); - if (!isset($url)) { - $identity = $this->getIdentity(); - if (empty($identity)) { - return FALSE; - } - if (is_string($identity)) { - $url = $identity; - } - elseif (isset($identity['urls'][$api_type])) { - $url = $identity['urls'][$api_type]; - } - $url = str_replace('{version}', $this->getApiVersion(), $url); - } - return $url; - } - - /** - * {@inheritdoc} - */ - public function getApiVersion() { - if ($this->immutableConfig->get('use_latest')) { - $versions = $this->getVersions(); - $version = end($versions); - return $version['version']; - } - return $this->immutableConfig->get('rest_api_version.version'); - } - - /** - * {@inheritdoc} - */ - public function setApiVersion($use_latest = TRUE, $version = NULL) { - if ($use_latest) { - $this->mutableConfig->set('use_latest', $use_latest); - } - else { - $versions = $this->getVersions(); - if (empty($versions[$version])) { - throw new \Exception("Version $version is not available."); - } - $version = $versions[$version]; - $this->mutableConfig->set('rest_api_version', $version); - } - $this->mutableConfig->save(); - } - - /** - * {@inheritdoc} - */ - public function getConsumerKey() { - return $this->immutableConfig->get('consumer_key'); - } - - /** - * {@inheritdoc} - */ - public function setConsumerKey($value) { - $this->mutableConfig->set('consumer_key', $value)->save(); - SalesforceAuthProviderPluginManager::updateAuthConfig(); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getConsumerSecret() { - return $this->immutableConfig->get('consumer_secret'); - } - - /** - * {@inheritdoc} - */ - public function setConsumerSecret($value) { - $this->mutableConfig->set('consumer_secret', $value)->save(); - SalesforceAuthProviderPluginManager::updateAuthConfig(); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getLoginUrl() { - $login_url = $this->immutableConfig->get('login_url'); - return empty($login_url) ? 'https://login.salesforce.com' : $login_url; - } - - /** - * {@inheritdoc} - */ - public function setLoginUrl($value) { - $this->mutableConfig->set('login_url', $value)->save(); - SalesforceAuthProviderPluginManager::updateAuthConfig(); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getInstanceUrl() { - return $this->state->get('salesforce.instance_url'); - } - - /** - * {@inheritdoc} - */ - public function setInstanceUrl($url) { - $this->state->set('salesforce.instance_url', $url); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getAccessToken() { - $access_token = $this->state->get('salesforce.access_token'); - return isset($access_token) && mb_strlen($access_token) !== 0 ? $access_token : FALSE; - } - - /** - * {@inheritdoc} - */ - public function setAccessToken($token) { - $this->state->set('salesforce.access_token', $token); - $this->storage()->updateToken(); - return $this; - } - - /** - * Get refresh token. - */ - public function getRefreshToken() { - return $this->state->get('salesforce.refresh_token'); - } - - /** - * {@inheritdoc} - */ - public function setRefreshToken($token) { - $this->state->set('salesforce.refresh_token', $token); - $this->storage()->updateToken(); - return $this; - } - - /** - * {@inheritdoc} - */ - 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; - } - - /** - * {@inheritdoc} - */ - 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; - } - - /** - * {@inheritdoc} - */ - 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; - } - - /** - * {@inheritdoc} - */ - public function setIdentity($data) { - $this->state->set('salesforce.identity', $data); - $this->storage()->updateIdentity(); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getIdentity() { - return $this->state->get('salesforce.identity'); - } - - /** - * {@inheritdoc} - */ - public function getAuthCallbackUrl() { - return Url::fromRoute('salesforce.oauth_callback', [], [ - 'absolute' => TRUE, - 'https' => TRUE, - ])->toString(); - } - - /** - * {@inheritdoc} - */ - public function getAuthEndpointUrl() { - return $this->getLoginUrl() . '/services/oauth2/authorize'; - } - - /** - * {@inheritdoc} - */ - public function getAuthTokenUrl() { - return $this->getLoginUrl() . '/services/oauth2/token'; - } - /** * {@inheritdoc} */ @@ -600,7 +332,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)); @@ -667,9 +399,6 @@ class RestClient implements RestClientInterface { } } } - else { - $sobjects[$object['name']] = $object; - } } return $sobjects; } @@ -701,7 +430,7 @@ class RestClient implements RestClientInterface { 'records' => [], ]); } - $version_path = parse_url($this->getApiEndPoint(), PHP_URL_PATH); + $version_path = parse_url($this->authProvider->getApiEndPoint(), PHP_URL_PATH); $next_records_url = str_replace($version_path, '', $results->nextRecordsUrl()); return new SelectQueryResult($this->apiCall($next_records_url)); } diff --git a/src/Rest/RestClientInterface.php b/src/Rest/RestClientInterface.php index 8e6c35476aed579b1f37a1cf51d43f6d1120eee5..c986ae80ac0865fb60dddb32da1713c0aa70dec2 100644 --- a/src/Rest/RestClientInterface.php +++ b/src/Rest/RestClientInterface.php @@ -12,13 +12,6 @@ use GuzzleHttp\Psr7\Response; */ interface RestClientInterface { - /** - * Determine if this SF instance is fully configured. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function isAuthorized(); - /** * Make a call to the Salesforce REST API. * @@ -106,42 +99,6 @@ interface RestClientInterface { */ public function getHttpClientOption($option_name); - /** - * Get the API end point for a given type of the API. - * - * @param string $api_type - * E.g., rest, partner, enterprise. - * - * @return string - * Complete URL endpoint for API access. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getApiEndPoint($api_type = 'rest'); - - /** - * Wrapper for config rest_api_version.version. - * - * @return string - * The SF API version. - */ - public function getApiVersion(); - - /** - * Setter for config salesforce.settings rest_api_version and use_latest. - * - * @param bool $use_latest - * Use the latest version, instead of an explicit version number. - * @param int $version - * The explicit version number. Mutually exclusive with $use_latest. - * - * @throws \Exception - * @throws \GuzzleHttp\Exception\RequestException - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setApiVersion($use_latest = TRUE, $version = NULL); - /** * Get the api usage, as returned in the most recent API request header. * @@ -151,207 +108,6 @@ interface RestClientInterface { */ public function getApiUsage(); - /** - * Consumer key getter. - * - * @return string|null - * Consumer key. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getConsumerKey(); - - /** - * Consumer key setter. - * - * @param string $value - * Consumer key value. - * - * @return $this - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setConsumerKey($value); - - /** - * Comsumer secret getter. - * - * @return string|null - * Consumer secret. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getConsumerSecret(); - - /** - * Consumer key setter. - * - * @param string $value - * Consumer secret value. - * - * @return $this - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setConsumerSecret($value); - - /** - * Login url getter. - * - * @return string|null - * Login url. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getLoginUrl(); - - /** - * Login url setter. - * - * @param string $value - * The login url. - * - * @return $this - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setLoginUrl($value); - - /** - * Get the SF instance URL. Useful for linking to objects. - * - * @return string|null - * The instance url. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getInstanceUrl(); - - /** - * Set the SF instance URL. - * - * @param string $url - * The url. - * - * @return $this - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setInstanceUrl($url); - - /** - * Get the access token. - * - * @return string|null - * The access token. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getAccessToken(); - - /** - * Set the access token. - * - * @param string $token - * Access token from Salesforce. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setAccessToken($token); - - /** - * Set the refresh token. - * - * @param string $token - * Refresh token from Salesforce. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setRefreshToken($token); - - /** - * Refresh access token based on the refresh token. - * - * @throws \Exception - * - * @deprecated will be removed in 8.x-4.0 release. - */ - 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() - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function handleAuthResponse(Response $response); - - /** - * Retrieve and store the Salesforce identity given an ID url. - * - * @param string $id - * Identity URL. - * - * @throws \Exception - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function initializeIdentity($id); - - /** - * Return the Salesforce identity, which is stored in a variable. - * - * @return array - * Returns FALSE is no identity has been stored. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getIdentity(); - - /** - * Set the Salesforce identity, which is stored in a variable. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function setIdentity($data); - - /** - * Helper to build the redirect URL for OAUTH workflow. - * - * @return string - * Redirect URL. - * - * @see \Drupal\salesforce\Controller\SalesforceController - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getAuthCallbackUrl(); - - /** - * Get Salesforce oauth login endpoint. (OAuth step 1) - * - * @return string - * REST OAuth Login URL. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getAuthEndpointUrl(); - - /** - * Get Salesforce oauth token endpoint. (OAuth step 2) - * - * @return string - * REST OAuth Token URL. - * - * @deprecated will be removed in 8.x-4.0 release. - */ - public function getAuthTokenUrl(); - /** * Wrapper for "Versions" resource to list information about API releases. * diff --git a/src/Rest/RestResponse.php b/src/Rest/RestResponse.php index 298ef578910ac577f40f1ff98bcf91c5c6801023..a7d2f50b7be0b9d684c48d234f365f9808f404b3 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. @@ -34,7 +35,7 @@ class RestResponse extends Response { * @param \GuzzleHttp\Psr7\Response $response * A response. */ - 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/Rest/RestResponse_Describe.php b/src/Rest/RestResponse_Describe.php deleted file mode 100644 index 14759a13ba940ff3ec35ef7f53c9e427deb8aad2..0000000000000000000000000000000000000000 --- a/src/Rest/RestResponse_Describe.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -namespace Drupal\salesforce\Rest; - -/** - * Use RestResponseDescribe. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class RestResponse_Describe extends RestResponseDescribe { - -} diff --git a/src/Rest/RestResponse_Resources.php b/src/Rest/RestResponse_Resources.php deleted file mode 100644 index cbad6fce7e563ca4be94340ccaf87740338c34af..0000000000000000000000000000000000000000 --- a/src/Rest/RestResponse_Resources.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -namespace Drupal\salesforce\Rest; - -/** - * Use RestResponseResources. - * - * @deprecated will be removed in 8.x-4.0 release. - */ -class RestResponse_Resources extends RestResponseResources { - -} diff --git a/src/SalesforceAuthProviderInterface.php b/src/SalesforceAuthProviderInterface.php index 130de7a7910102b22da60d29d06204d4ca378033..aee8ea850900a5363e1333613d54999a568305a4 100644 --- a/src/SalesforceAuthProviderInterface.php +++ b/src/SalesforceAuthProviderInterface.php @@ -17,6 +17,7 @@ interface SalesforceAuthProviderInterface extends ServiceInterface, PluginFormIn const AUTH_TOKEN_PATH = '/services/oauth2/token'; const AUTH_ENDPOINT_PATH = '/services/oauth2/authorize'; const SOAP_CLASS_PATH = '/services/Soap/class/'; + const LATEST_API_VERSION = '44.0'; /** * Id of this service. @@ -130,6 +131,25 @@ interface SalesforceAuthProviderInterface extends ServiceInterface, PluginFormIn */ public function getAccessTokenEndpoint(); + /** + * Get the globally configured API version to use. + * + * @return string + * The string name of the API version. + */ + public function getApiVersion(); + + /** + * API Url for this plugin. + * + * @param string $api_type + * (optional) Which API for which to retrieve URL, defaults to "rest". + * + * @return string. + * The URL + */ + public function getApiEndpoint($api_type = 'rest'); + /** * Instance URL for this connection. * @@ -150,4 +170,24 @@ interface SalesforceAuthProviderInterface extends ServiceInterface, PluginFormIn */ public function save(array $form, FormStateInterface $form_state); + /** + * The auth provider service. + * + * @return \Drupal\salesforce\SalesforceAuthProviderInterface + * The auth provider service. + */ + public function service(); + + /** + * Complete the OAuth user-agent handshake. + * + * @return bool + * TRUE if oauth finalization was successful. + * + * @throws \OAuth\Common\Http\Exception\TokenResponseException + * + * @see \Drupal\salesforce\Controller\SalesforceOAuthController + */ + public function finalizeOauth(); + } diff --git a/src/SalesforceAuthProviderPluginBase.php b/src/SalesforceAuthProviderPluginBase.php index f65a73e8ac61900a315cd59978679fc0d3f8b9fc..a0580ac90b07e29274f152d77442b0bb4930148c 100644 --- a/src/SalesforceAuthProviderPluginBase.php +++ b/src/SalesforceAuthProviderPluginBase.php @@ -186,6 +186,38 @@ abstract class SalesforceAuthProviderPluginBase extends Salesforce implements Sa return $this->getAccessToken()->getExtraParams()['instance_url']; } + /** + * {@inheritdoc} + */ + public function getApiEndpoint($api_type = 'rest') { + $url = &drupal_static(self::CLASS . __FUNCTION__ . $api_type); + if (!isset($url)) { + $identity = $this->getIdentity(); + if (empty($identity)) { + return FALSE; + } + if (is_string($identity)) { + $url = $identity; + } + elseif (isset($identity['urls'][$api_type])) { + $url = $identity['urls'][$api_type]; + } + $url = str_replace('{version}', $this->getApiVersion(), $url); + } + return $url; + } + + /** + * {@inheritdoc} + */ + public function getApiVersion() { + $version = \Drupal::config('salesforce.settings')->get('rest_api_version.version'); + if (empty($version) || \Drupal::config('salesforce.settings')->get('use_latest')) { + return self::LATEST_API_VERSION; + } + return \Drupal::config('salesforce.settings')->get('rest_api_version.version'); + } + /** * {@inheritdoc} */ diff --git a/src/SalesforceAuthProviderPluginInterface.php b/src/SalesforceAuthProviderPluginInterface.php deleted file mode 100644 index 9e96fbf4db4b1e9ebdd7545326d556efee1400c0..0000000000000000000000000000000000000000 --- a/src/SalesforceAuthProviderPluginInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -namespace Drupal\salesforce; - -use Drupal\Component\Plugin\PluginInspectionInterface; -use Drupal\Core\Plugin\PluginFormInterface; - -/** - * Auth provider plugin interface. - */ -interface SalesforceAuthProviderPluginInterface extends PluginFormInterface, PluginInspectionInterface { - - /** - * The auth provider service. - * - * @return \Drupal\salesforce\SalesforceAuthProviderInterface - * The auth provider service. - */ - public function service(); - - /** - * Login URL set for this auth provider. - * - * @return string - * Login URL set for this auth provider. - */ - public function getLoginUrl(); - -} diff --git a/src/SalesforceAuthProviderPluginManager.php b/src/SalesforceAuthProviderPluginManager.php index 782b5583676fdeb96e7f06db96c00b5c2388db37..c5742a3655e0109e0422e24101828300a1d7498d 100644 --- a/src/SalesforceAuthProviderPluginManager.php +++ b/src/SalesforceAuthProviderPluginManager.php @@ -61,7 +61,7 @@ class SalesforceAuthProviderPluginManager extends DefaultPluginManager { /** * Backwards-compatibility for legacy singleton auth. * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. + * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.1. */ public static function updateAuthConfig() { $oauth = self::getAuthConfig(); @@ -79,7 +79,7 @@ class SalesforceAuthProviderPluginManager extends DefaultPluginManager { /** * Backwards-compatibility for legacy singleton auth. * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. + * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.1. */ public static function getAuthConfig() { $config = \Drupal::configFactory()->getEditable('salesforce.settings'); @@ -192,7 +192,7 @@ class SalesforceAuthProviderPluginManager extends DefaultPluginManager { /** * Force a refresh of the active token and return the fresh token. * - * @return \OAuth\OAuth2\Token\TokenInterface|null + * @return \OAuth\Common\Token\TokenInterface * The token. */ public function refreshToken() { diff --git a/src/SalesforceOAuthPluginInterface.php b/src/SalesforceOAuthPluginInterface.php deleted file mode 100644 index a4534c0e04bd2399dd6ae38bacb16afb0a9832f5..0000000000000000000000000000000000000000 --- a/src/SalesforceOAuthPluginInterface.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -namespace Drupal\salesforce; - -/** - * OAuth user-agent plugin interface. - * - * OAuth user-agent flow requires a 2-part handshake to complete authentication. - * This interface exposes methods to make the handshake possible. - * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. - */ -interface SalesforceOAuthPluginInterface extends SalesforceAuthProviderPluginInterface { - - /** - * Complete the OAuth user-agent handshake. - * - * @return bool - * TRUE if oauth finalization was successful. - * - * @throws \OAuth\Common\Http\Exception\TokenResponseException - * - * @see \Drupal\salesforce\Controller\SalesforceOAuthController - */ - public function finalizeOauth(); - - /** - * Getter for consumer secret. - * - * @return string - * The consumer secret. - */ - public function getConsumerSecret(); - -} diff --git a/src/Storage/SalesforceAuthTokenStorage.php b/src/Storage/SalesforceAuthTokenStorage.php index d7de34c94d50e532edfd961327028590ce796157..9cbe52778418ceb631e3f6b7e79ea77ef573fa1c 100644 --- a/src/Storage/SalesforceAuthTokenStorage.php +++ b/src/Storage/SalesforceAuthTokenStorage.php @@ -34,42 +34,6 @@ class SalesforceAuthTokenStorage implements SalesforceAuthTokenStorageInterface $this->state = $state; } - /** - * Backwards-compatibility for legacy singleton auth. - * - * @return string - * Id of the active oauth. - * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. - */ - private function service() { - $oauth = SalesforceAuthProviderPluginManager::getAuthConfig(); - return $oauth->id(); - } - - /** - * Backwards-compatibility for legacy singleton auth. - * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. - */ - public function updateToken() { - $this->storeAccessToken($this->service(), - new SalesforceToken( - $this->state->get('salesforce.access_token'), - $this->state->get('salesforce.refresh_token'))); - return $this; - } - - /** - * Backwards-compatibility for legacy singleton auth. - * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. - */ - public function updateIdentity() { - $this->storeIdentity($this->service(), $this->state->get('salesforce.identity')); - return $this; - } - /** * Token storage key for given service. * diff --git a/src/Tests/TestRestClient.php b/src/Tests/TestRestClient.php index 0f1f6eb1e52bdca5c6a4514dc882ca7d739bc93c..fa5185cfc9b634a060985277a2a81689770fe635 100644 Binary files a/src/Tests/TestRestClient.php and b/src/Tests/TestRestClient.php differ diff --git a/tests/src/Unit/AuthorizeFormTest.php b/tests/src/Unit/AuthorizeFormTest.php deleted file mode 100644 index 83471e168f347b71d97a46d221f031be1587c9ac..0000000000000000000000000000000000000000 --- a/tests/src/Unit/AuthorizeFormTest.php +++ /dev/null @@ -1,81 +0,0 @@ -<?php - -namespace Drupal\Tests\salesforce\Unit; - -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Form\FormState; -use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; -use Drupal\Core\Routing\TrustedRedirectResponse; -use Drupal\Core\State\StateInterface; -use Drupal\Core\Utility\UnroutedUrlAssembler; -use Drupal\Tests\UnitTestCase; -use Drupal\salesforce\Form\AuthorizeForm; -use Drupal\salesforce\Rest\RestClient; -use Prophecy\Argument; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpFoundation\RequestStack; -use Drupal\Core\Logger\LoggerChannelFactory; - -/** - * @coversDefaultClass \Drupal\salesforce\Form\AuthorizeForm - * @group salesforce - */ -class AuthorizeFormTest extends UnitTestCase { - - /** - * Set up for each test. - */ - public function setUp() { - parent::setUp(); - - $this->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 28e9ab29f3c52fbc4ee0b7a242f555f69269f082..f6e4a2cb4c2eb6b925062af972015124b5de59b2 100644 --- a/tests/src/Unit/RestClientTest.php +++ b/tests/src/Unit/RestClientTest.php @@ -3,6 +3,11 @@ namespace Drupal\Tests\salesforce\Unit; use Drupal\Component\Serialization\Json; +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\State\State; +use Drupal\salesforce\SalesforceAuthProviderPluginManager; +use Drupal\salesforce\Token\SalesforceToken; use Drupal\Tests\UnitTestCase; use Drupal\salesforce\Rest\RestClient; use Drupal\salesforce\Rest\RestResponse; @@ -12,6 +17,8 @@ use Drupal\salesforce\SFID; use Drupal\salesforce\SObject; use Drupal\salesforce\SelectQueryResult; use Drupal\salesforce\SelectQuery; +use OAuth\OAuth2\Token\TokenInterface; +use GuzzleHttp\Client; use GuzzleHttp\Psr7\Response as GuzzleResponse; use GuzzleHttp\Psr7\Request as GuzzleRequest; use GuzzleHttp\Exception\RequestException; @@ -32,27 +39,29 @@ class RestClientTest extends UnitTestCase { parent::setUp(); $this->salesforce_id = '1234567890abcde'; $this->methods = [ - 'getConsumerKey', - 'getConsumerSecret', - 'getRefreshToken', - 'getAccessToken', - 'refreshToken', - 'getApiEndPoint', 'httpRequest', ]; - $this->httpClient = $this->getMock('\GuzzleHttp\Client'); + $this->httpClient = $this->getMock(Client::CLASS); $this->configFactory = - $this->getMockBuilder('\Drupal\Core\Config\ConfigFactory') + $this->getMockBuilder(ConfigFactory::CLASS) ->disableOriginalConstructor() ->getMock(); $this->state = - $this->getMockBuilder('\Drupal\Core\State\State') + $this->getMockBuilder(State::CLASS) ->disableOriginalConstructor() ->getMock(); - $this->cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface'); + $this->cache = $this->getMock(CacheBackendInterface::CLASS); $this->json = $this->getMock(Json::CLASS); $this->time = $this->getMock(TimeInterface::CLASS); + $this->authToken = $this->getMock(TokenInterface::CLASS); + $this->authMan = + $this->getMockBuilder(SalesforceAuthProviderPluginManager::CLASS) + ->disableOriginalConstructor() + ->getMock(); + $this->authMan->expects($this->any()) + ->method('getToken') + ->willReturn($this->authToken); } /** @@ -86,27 +95,6 @@ class RestClientTest extends UnitTestCase { } } - /** - * @covers ::isAuthorized - */ - public function testAuthorized() { - $this->initClient(); - $this->client->expects($this->at(0)) - ->method('getConsumerKey') - ->willReturn($this->randomMachineName()); - $this->client->expects($this->at(1)) - ->method('getConsumerSecret') - ->willReturn($this->randomMachineName()); - $this->client->expects($this->at(2)) - ->method('getRefreshToken') - ->willReturn($this->randomMachineName()); - - $this->assertTrue($this->client->isAuthorized()); - - // Next one will fail because mocks only return for specific invocations. - $this->assertFalse($this->client->isAuthorized()); - } - /** * @covers ::apiCall */ @@ -154,10 +142,10 @@ class RestClientTest extends UnitTestCase { // First httpRequest() is position 4. // @TODO this is extremely brittle, exposes complexity in underlying client. Refactor this. - $this->client->expects($this->at(3)) + $this->client->expects($this->at(2)) ->method('httpRequest') ->willReturn($response_401); - $this->client->expects($this->at(4)) + $this->client->expects($this->at(3)) ->method('httpRequest') ->willReturn($response_200); diff --git a/tests/src/Unit/SalesforceControllerTest.php b/tests/src/Unit/SalesforceControllerTest.php deleted file mode 100644 index 94891e0ffa9ea13e1dbd576803d5023c378feb72..0000000000000000000000000000000000000000 --- a/tests/src/Unit/SalesforceControllerTest.php +++ /dev/null @@ -1,130 +0,0 @@ -<?php - -namespace Drupal\Tests\salesforce\Unit; - -use Drupal\Component\Serialization\Json; -use Drupal\Core\Render\MetadataBubblingUrlGenerator; -use Drupal\salesforce\Controller\SalesforceController; -use Drupal\salesforce\Rest\RestClient; -use Drupal\Tests\UnitTestCase; - -use GuzzleHttp\Psr7\Response; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Drupal\Component\Datetime\TimeInterface; - -/** - * @coversDefaultClass \Drupal\salesforce\Controller\SalesforceController - * @group salesforce - * - * @deprecated BC legacy auth scheme only. will be removed in 8.x-4.0. - */ -class SalesforceControllerTest extends UnitTestCase { - - /** - * Set up for each test. - */ - public function setUp() { - parent::setUp(); - - $this->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()); - } - -}