Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • project/salesforce
  • issue/salesforce-2975914
  • issue/salesforce-3168388
  • issue/salesforce-3169218
  • issue/salesforce-3072830
  • issue/salesforce-3170030
  • issue/salesforce-3170137
  • issue/salesforce-3173210
  • issue/salesforce-3177326
  • issue/salesforce-3181364
  • issue/salesforce-3279290
  • issue/salesforce-3203287
  • issue/salesforce-3204383
  • issue/salesforce-3204704
  • issue/salesforce-3217061
  • issue/salesforce-3217586
  • issue/salesforce-3102133
  • issue/salesforce-3210809
  • issue/salesforce-3221747
  • issue/salesforce-3222661
  • issue/salesforce-3222940
  • issue/salesforce-3226511
  • issue/salesforce-3226070
  • issue/salesforce-3222683
  • issue/salesforce-3120102
  • issue/salesforce-3231850
  • issue/salesforce-3281008
  • issue/salesforce-3213468
  • issue/salesforce-3247923
  • issue/salesforce-3255740
  • issue/salesforce-3257058
  • issue/salesforce-3276564
  • issue/salesforce-3284336
  • issue/salesforce-3310785
  • issue/salesforce-3312317
  • issue/salesforce-3312979
  • issue/salesforce-3320557
  • issue/salesforce-3340664
  • issue/salesforce-3358568
  • issue/salesforce-3404815
  • issue/salesforce-3400562
  • issue/salesforce-3400903
  • issue/salesforce-3400897
  • issue/salesforce-3405570
  • issue/salesforce-3349963
  • issue/salesforce-3389025
  • issue/salesforce-3154024
  • issue/salesforce-3395912
  • issue/salesforce-3396890
  • issue/salesforce-3398897
  • issue/salesforce-3408393
  • issue/salesforce-3410886
  • issue/salesforce-3411280
  • issue/salesforce-3414401
  • issue/salesforce-3414659
  • issue/salesforce-3415336
  • issue/salesforce-3415242
  • issue/salesforce-3417891
  • issue/salesforce-3417994
  • issue/salesforce-3419551
  • issue/salesforce-3419804
  • issue/salesforce-3382005
  • issue/salesforce-3418124
  • issue/salesforce-3427415
  • issue/salesforce-3427904
  • issue/salesforce-3428593
  • issue/salesforce-3443380
  • issue/salesforce-3443506
  • issue/salesforce-3439625
  • issue/salesforce-3442186
  • issue/salesforce-3445088
  • issue/salesforce-3432569
  • issue/salesforce-3449141
  • issue/salesforce-3449163
  • issue/salesforce-3460395
  • issue/salesforce-3472489
  • issue/salesforce-3472706
  • issue/salesforce-3460502
  • issue/salesforce-3477260
  • issue/salesforce-3209120
  • issue/salesforce-3491809
  • issue/salesforce-3495316
  • issue/salesforce-3492972
  • issue/salesforce-3497252
  • issue/salesforce-3502753
  • issue/salesforce-3503227
  • issue/salesforce-3511455
  • issue/salesforce-3512519
  • issue/salesforce-3514383
89 results
Show changes
Commits on Source (125)
Showing
with 386 additions and 335 deletions
abcdg
activateable
addtional
adminlink
aggregatable
apexrest
apicalls
atribtary
birthdate
Bject
Chunkify
commandfile
Configurationform
createable
creds
DERP
developerforce
devname
dobj
dprop
elementtype
entup
faultcode
fieldables
fieldmap
fieldmaps
fugly
Giwj
Giwo
gname
govcloud
howsmyssl
ignorefile
lastupdate
layoutable
listviewable
loggable
LONGTERM
mergeable
multipicklist
multipicklists
nillable
notvie
permissionable
picklist
Picklists
Prefilterable
prepull
profilepic
Readby
Reauth
replicateable
resultd
retrieveable
Revisionuser
SDFC
sevent
sfapi
sfco
sfdc
sfdf
sfdo
sfdom
sfdrt
sfeq
sffield
sfid
sfids
sfif
sfiq
sflo
sflp
sflr
sfobj
sfobject
sfobjectid
sfobjects
Sforce
sfpall
sfpd
sfpf
sfpm
sfpmap
sfpq
sfprune
sfpsf
sfpu
sfpushq
sfqo
sfro
sfrq
sfrt
sfrv
sfrvk
singl
sobj
sobject
sobjects
SOQL
soql
Statefull
stringifying
timeframe
timstamp
triggerable
typehint
undeletable
unpushable
unweildy
updateable
vals
webforms
WIAS
WSOD
include:
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file:
- '/includes/include.drupalci.main.yml'
- '/includes/include.drupalci.variables.yml'
- '/includes/include.drupalci.workflows.yml'
variables:
OPT_IN_TEST_MAX_PHP: 1
# Broaden test coverage.
OPT_IN_TEST_NEXT_MINOR: 1
OPT_IN_TEST_NEXT_MAJOR: 1
_CSPELL_IGNORE_PATHS: 'src/Tests/*.json, modules/salesforce_mapping/config/schema/'
\ No newline at end of file
see documentation https://www.drupal.org/docs/contributed-modules/salesforce-suite/quick-start
ABOUT
-----
This module suite implements a mapping functionality between Salesforce
objects and Drupal entities. In other words, for each of your supported Drupal
entities (e.g. node, user, or entities supported by extensions), you can
assign Salesforce objects that will be created / updated when the entity is
saved. For each such assignment, you choose which Drupal and Salesforce fields
should be mapped to one another.
This suite also includes an API architecture which allows for additional
modules to be easily plugged in (e.g. for webforms, contact form submits,
etc).
For a more detailed description of each component module, see below.
REQUIREMENTS
------------
1) You need a Salesforce account. Developers can register here:
http://www.developerforce.com/events/regular/registration.php
2) You will need to create a remote application/connected app for
authorization. In Salesforce go to Your Name > Setup > Create > Apps then
create a new Connected App. Set the callback URL to:
https://<your site>/salesforce/oauth_callback (must use SSL)
Select at least 'Perform requests on your behalf at any time' for OAuth Scope
as well as the appropriate other scopes for your application.
Additional information:
https://help.salesforce.com/help/doc/en/remoteaccess_about.htm
3) Your site needs to be SSL enabled to authorize the remote application using
OAUTH.
4) If using the SOAP API, PHP to have been compiled with SOAP web services and
OpenSSL support, as per:
http://php.net/soap
http://php.net/openssl
5) Required modules
Entity API - http://drupal.org/project/entity
Libraries, only for SOAP API - http://drupal.org/project/libraries
See Installation below for installing other required dependencies.
AUTHORIZATION / CONNECTED APP CONFIGURATION
-------------------------------------------
You can supply your connected app's consumer key, consumer secret, and login
URL to the Salesforce Authorization form found at
admin/config/salesforce/authorize. This information will be stored into
your site's mutable/exportable configuration and used to authorize your site
with Salesforce.
Alternately you can supply or override this configuration using your site's
settings.php file. For example, a developer might add the following to
his/her settings.local.php file to connect his/her development environment to
a Salesforce sandbox:
$config['salesforce.settings']['consumer_key'] = 'foo';
$config['salesforce.settings']['consumer_secret'] = 'bar';
$config['salesforce.settings']['login_url'] = 'https://test.salesforce.com';
Supplying your connected app configuration exclusively by way of settings.php
has additional benefits in terms of security and flexibility:
- Keeps this sensitive configuration out of the database (and out of version
control if the site's configuration is tracked in code).
- Allows for easily substituting environment-specific overrides for these
values. If you track your site's settings.php file in version control, you
can create a settings.local.php file for each of your Salesforce-conencted
environments with the connected app configuration appropriate for the
specific environment (see default.settings.php for the code to enable this
core feature).
- Reduces the likelihood of a development or staging environment accidentally
connecting to your production Salesforce instance.
If you choose the settings.php route, you'll need to supply dummy-values to
the form at admin/config/salesforce/authorize. Rest assured the real values
you've specified via settings.php will be used to establish the connection to
Salesforce, even though you cannot see them in the configuration form.
INSTALLATION:
-------------
When installing through Composer, require each submodules used seperately in
addition to the base salesforce module to insure installation of all necessary
depedencies. For example, use 'composer require drupal/salesforce_mapping' to
include drupal/dynamic_entity_reference and drupal/typed_data, both required
by salesforce_mapping.
MODULES:
--------
Salesforce (salesforce):
OAUTH2 authorization and wrapper around the Salesforce REST API.
Salesforce Example (salesforce_example)
Salesforce examples.
Salesforce Logger (salesforce_logger)
Consolidated logging for Salesforce Log events.
Salesforce JWT Auth Provider (salesforce_jwt)
Provides key-based Salesforce authentication.
Salesforce Mapping (salesforce_mapping)
Map Drupal entities to Salesforce fields, including field level mapping.
Salesforce Mapping UI (salesforce_mapping_ui)
User interface for managing Salesforce mappings.
Salesforce OAuth user-agent Provider (salesforce_oauth)
Provides user-agent-based Salesforce OAuth authentication.
Salesforce Push (salesforce_push):
Push Drupal entity updates into Salesforce.
Salesforce Pull (salesforce_pull):
Pull Salesforce object updates into Drupal.
Salesforce Soap (salesforce_soap):
Lightweight wrapper around the SOAP API, using the OAUTH access token, to
fill in functional gaps missing in the REST API. Requires the Salesforce PHP
Toolkit.
Salesforce Webform (salesforce_webform)
Adds support for webforms fields in Salesforce Mapping.
or project page https://www.drupal.org/project/salesforce
......@@ -27,25 +27,22 @@
"extra": {
"drush": {
"services": {
"drush.services.yml": "^9"
"drush.services.yml": ">=9"
}
}
},
"require": {
"consolidation/output-formatters": "^3.2.0 || ^4.1",
"consolidation/output-formatters": "^3.2.0 || ^4.5",
"lusitanian/oauth": "^0.8.11",
"firebase/php-jwt": "^5.0 || ^6.0",
"drupal/address": "^2.0",
"drupal/key": "^1",
"drupal/dynamic_entity_reference": "^3 || ^4",
"drupal/typed_data": "^2",
"messageagency/force.com-toolkit-for-php": "^1.0.2",
"ext-soap": "*",
"ext-json": "*"
},
"require-dev": {
"drupal/address": "^1.8",
"drupal/key": "^1.14",
"firebase/php-jwt": "^5.0",
"lusitanian/oauth": "^0.8.11",
"drupal/dynamic_entity_reference": "^1.9 || ^2.0",
"drupal/typed_data": "^1.0-alpha5",
"messageagency/force.com-toolkit-for-php": "^1.0.1",
"ext-soap": "*"
},
"suggest": {
"drupal/address": "Required for salesforce_address",
"drupal/dynamic_entity_reference": "Required for salesforce_mapping.",
......
rest_api_version:
label: ""
url: ""
version: "39.0"
version: "52.0"
use_latest: true
global_push_limit: 100000
pull_max_queue_size: 100000
......@@ -9,3 +9,5 @@ show_all_objects: false
standalone: false
limit_mapped_object_revisions: 10
salesforce_auth_provider: ''
short_term_cache_lifetime: 3600
long_term_cache_lifetime: 604800
......@@ -2,18 +2,6 @@ salesforce.settings:
type: config_object
label: 'Salesforce Settings'
mapping:
consumer_key:
type: string
label: 'Salesforce consumer key'
description: 'Consumer key of the Salesforce remote application you want to grant access to.'
consumer_secret:
type: string
label: 'Salesforce consumer secret'
description: 'Consumer secret of the Salesforce remote application you want to grant access to.'
login_url:
type: string
label: 'Login URL'
description: 'API login URL, either https://login.salesforce.com or https://test.salesforce.com.'
global_push_limit:
type: integer
label: 'Global push queue limit'
......@@ -42,6 +30,14 @@ salesforce.settings:
type: string
label: 'Default authorization provider id'
description: 'A salesforce_auth config entity id which provides API authorization.'
short_term_cache_lifetime:
type: integer
label: "Short term cache lifetime"
description: "Value, in seconds, to store short term meta data. This is used for, e.g., the list of Object Types, Object Descriptions, and Record Types."
long_term_cache_lifetime:
type: integer
label: "Long term cache lifetime"
description: "Value, in seconds, to store long term meta data. This is used for, e.g., the list of API versions."
rest_api_version:
type: mapping
label: 'REST API Version'
......
name: Salesforce Address
type: module
description: A custom Address Field Widget for Salesforce compatibility.
core_version_requirement: ^8.7.7 || ^9
core_version_requirement: ^10.3 || ^11
package: Salesforce
dependencies:
- address:address
......@@ -19,7 +19,7 @@ function salesforce_address_help($route_name, RouteMatchInterface $route_match)
of the Address Field widget from the Address module, which is compatible
with Salesforce address formatting, which uses a single, multi-line
field for the street address rather than multiple lines. If you are
syncing Address fields with Saleforce addresses, you can save a lot of
syncing Address fields with Salesforce addresses, you can save a lot of
sync trouble by enabling this widget on your Form configurations for
your Address fields.') . '</p>';
return $output;
......
......@@ -21,6 +21,7 @@ class AddressStreetAsTextArea extends Address {
$element = Address::addressElements($element, $value);
$element["address_line1"]["#type"] = "textarea";
$element["address_line1"]["#rows"] = "2";
$element["address_line1"]["#maxlength"] = "255";
$element["address_line2"]["#access"] = FALSE;
return $element;
}
......
name: Salesforce Example
type: module
description: Salesforce Examples
core_version_requirement: ^8.7.7 || ^9
core_version_requirement: ^10.3 || ^11
package: Salesforce
dependencies:
- salesforce:salesforce_push
......
......@@ -5,9 +5,9 @@
* Contains salesforce_example.module.
*/
use Drupal\salesforce\SFID;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\salesforce\SFID;
/**
* Implements hook_help().
......@@ -29,7 +29,7 @@ function salesforce_example_help($route_name, RouteMatchInterface $route_match)
* Implements hook_entity_insert().
*
* For this example we are simply calling a "manage" function and passing a
* parameter to indicate what type of operaiton is taking place.
* parameter to indicate what type of operation is taking place.
*/
function salesforce_example_entity_insert(EntityInterface $entity) {
_salesforce_example_entity_manage($entity, 'insert');
......@@ -39,7 +39,7 @@ function salesforce_example_entity_insert(EntityInterface $entity) {
* Implements hook_entity_update().
*
* For this example we are simply calling a "manage" function and passing a
* parameter to indicate what type of operaiton is taking place.
* parameter to indicate what type of operation is taking place.
*/
function salesforce_example_entity_update(EntityInterface $entity) {
_salesforce_example_entity_manage($entity, 'update');
......@@ -49,7 +49,7 @@ function salesforce_example_entity_update(EntityInterface $entity) {
* Implements hook_entity_delete().
*
* For this example we are simply calling a "manage" function and passing a
* parameter to indicate what type of operaiton is taking place.
* parameter to indicate what type of operation is taking place.
*/
function salesforce_example_entity_delete(EntityInterface $entity) {
_salesforce_example_entity_manage($entity, 'delete');
......@@ -69,7 +69,7 @@ function salesforce_example_entity_delete(EntityInterface $entity) {
*
* If you're not familiar with Drupal Commerce object relationships, all you
* need to know is that Products are umbrella entities around Product
* Variations. Products have Product Variatinos. So if you have a Drupal
* Variations. Products have Product variations. So if you have a Drupal
* T-Shirt in sizes S,M,L,XL, the Product is "Drupal T-Shirt" and you will have
* four Product Variations, one for each size. The Product object will have
* entity references to each Product Variation.
......@@ -116,7 +116,7 @@ function _salesforce_example_entity_manage(EntityInterface &$entity, $op) {
// Commerce model).
$course_sfdc_id = $sf->field('Parent_Product__c');
// Create an SFID object using the vlaue.
// Create an SFID object using the value.
$sfid = new SFID($course_sfdc_id);
// Use the storage object to load the mapped object(s) that correspond
......@@ -126,7 +126,7 @@ function _salesforce_example_entity_manage(EntityInterface &$entity, $op) {
if (is_array($mapped_objects)) {
// We are lazily assuming that there will only be one corresponding
// prodct object and that it will be the first item in the returned
// product object and that it will be the first item in the returned
// array.
$mapped_object = current($mapped_objects);
......
services:
salesforce_example.push_params:
class: Drupal\salesforce_example\EventSubscriber\SalesforceExampleSubscriber
arguments: []
arguments: ['@salesforce.client', '@logger.factory','@messenger']
tags:
- { name: event_subscriber }
<?php
// phpcs:ignorefile
namespace Drupal\salesforce_example\EventSubscriber;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\salesforce\Event\SalesforceEvents;
use Drupal\salesforce\Rest\RestClientInterface;
use Drupal\salesforce_mapping\Event\SalesforcePullEvent;
use Drupal\salesforce_mapping\Event\SalesforcePushOpEvent;
use Drupal\salesforce_mapping\Event\SalesforcePushAllowedEvent;
use Drupal\salesforce_mapping\Event\SalesforcePushOpEvent;
use Drupal\salesforce_mapping\Event\SalesforcePushParamsEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\salesforce_mapping\Event\SalesforceQueryEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class SalesforceExampleSubscriber.
......@@ -24,6 +29,47 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The Salesforce REST client.
*
* @var \Drupal\salesforce\Rest\RestClientInterface
*/
protected $client;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Create a new Salesforce object.
*
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\salesforce\Rest\RestClientInterface $salesforce_client
* The factory for configuration objects.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(
LoggerInterface $logger,
RestClientInterface $salesforce_client,
MessengerInterface $messenger,
) {
$this->logger = $logger;
$this->client = $salesforce_client;
$this->messenger = $messenger;
}
/**
* SalesforcePushAllowedEvent callback.
*
......@@ -79,7 +125,7 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
// Do Y.
break;
}
\Drupal::messenger()->addStatus('push success example subscriber!: ' . $event->getMappedObject()->sfid());
$this->messenger->addStatus('push success example subscriber!: ' . $event->getMappedObject()->sfid());
}
/**
......@@ -89,7 +135,7 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
* The event.
*/
public function pushFail(SalesforcePushOpEvent $event) {
\Drupal::messenger()->addStatus('push fail example: ' . $event->getMappedObject()->id());
$this->messenger->addStatus('push fail example: ' . $event->getMappedObject()->id());
}
/**
......@@ -104,7 +150,7 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
case 'contact':
// Add attachments to the Contact pull mapping so that we can save
// profile pics. See also ::pullPresave.
$query = $event->getQuery();
$query = $event->getQuery()->accessCheck(FALSE);
// Add a subquery:
$query->fields[] = "(SELECT Id FROM Attachments WHERE Name = 'example.jpg' LIMIT 1)";
// Add a field from lookup:
......@@ -131,7 +177,7 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
// Attachment data, if given.
$account = $event->getEntity();
$sf_data = $event->getMappedObject()->getSalesforceRecord();
$client = \Drupal::service('salesforce.client');
$client = $this->client;
// Fetch the attachment URL from raw sf data.
$attachments = [];
try {
......@@ -145,7 +191,7 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
return;
}
// If Attachments field was set, it will contain a URL from which we can
// fetch the attached binary. We must append "body" to the retreived URL
// fetch the attached binary. We must append "body" to the retrieved URL
// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_blob_retrieve.htm
$attachment_url = $attachments['records'][0]['attributes']['url'];
$attachment_url = $client->getInstanceUrl() . $attachment_url . '/Body';
......@@ -156,7 +202,7 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
}
catch (\Exception $e) {
// Unable to fetch file data from SF.
\Drupal::logger('db')->error($this->t('failed to fetch attachment for user @user', ['@user' => $account->id()]));
$this->logger->error($this->t('failed to fetch attachment for user @user', ['@user' => $account->id()]));
return;
}
......@@ -165,11 +211,11 @@ class SalesforceExampleSubscriber implements EventSubscriberInterface {
// Attach the new file id to the user entity.
/* var \Drupal\file\FileInterface */
if ($file = file_save_data($file_data, $destination, FileSystemInterface::EXISTS_REPLACE)) {
if ($file = \Drupal::service('file.repository')->writeData($file_data, $destination, FileSystemInterface::EXISTS_REPLACE)) {
$account->user_picture->target_id = $file->id();
}
else {
\Drupal::logger('db')->error('failed to save profile pic for user ' . $account->id());
$this->logger->error('failed to save profile pic for user ' . $account->id());
}
break;
......
......@@ -4,9 +4,9 @@ namespace Drupal\salesforce_example\Plugin\SalesforceMappingField;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\salesforce_mapping\SalesforceMappingFieldPluginBase;
use Drupal\salesforce_mapping\Entity\SalesforceMappingInterface;
use Drupal\salesforce_mapping\MappingConstants;
use Drupal\salesforce_mapping\SalesforceMappingFieldPluginBase;
/**
* Adapter for entity Constant and fields.
......@@ -31,7 +31,7 @@ class Hardcoded extends SalesforceMappingFieldPluginBase {
'#disabled' => TRUE,
];
// @TODO: "Constant" as it's implemented now should only be allowed to be set to "Push". In the future: create "Pull" logic for constant, which pulls a constant value to a Drupal field. Probably a separate mapping field plugin.
// @todo "Constant" as it's implemented now should only be allowed to be set to "Push". In the future: create "Pull" logic for constant, which pulls a constant value to a Drupal field. Probably a separate mapping field plugin.
$pluginForm['direction']['#options'] = [
MappingConstants::SALESFORCE_MAPPING_DIRECTION_DRUPAL_SF => $pluginForm['direction']['#options'][MappingConstants::SALESFORCE_MAPPING_DIRECTION_DRUPAL_SF],
];
......
......@@ -25,8 +25,8 @@
"source": "http://cgit.drupalcode.org/salesforce"
},
"require": {
"drupal/key": "^1.14",
"firebase/php-jwt": "^5.0",
"drupal/key": "^1.14 || 1.x-dev",
"firebase/php-jwt": "^5.0 || ^6.0",
"lusitanian/oauth": "^0.8.11"
}
}
name: Salesforce JWT Auth Provider
type: module
description: Provides key-based Salesforce authentication.
core_version_requirement: ^8.7.7 || ^9
core_version_requirement: ^10.3 || ^11
package: Salesforce
configure: salesforce.auth_config
dependencies:
- salesforce:salesforce
- key:key (>= 1.14)
- key:key
<?php
namespace Drupal\salesforce_jwt\Consumer;
/**
* JWT Gov Cloud credentials.
*/
class JWTGovCloudCredentials extends JWTCredentials {
/**
* Token URL for JWT OAuth authentication.
*
* @var string
*/
protected $tokenUrl;
/**
* {@inheritdoc}
*/
public function __construct($consumerKey, $loginUrl, $loginUser, $keyId, $tokenUrl) {
parent::__construct($consumerKey, $loginUrl, $loginUser, $keyId);
$this->tokenUrl = $tokenUrl;
}
/**
* Constructor helper.
*
* @param array $configuration
* Plugin configuration.
*
* @return \Drupal\salesforce_jwt\Consumer\JWTGovCloudCredentials
* Credentials, valid or not.
*/
public static function create(array $configuration) {
return new static($configuration['consumer_key'], $configuration['login_url'], $configuration['login_user'], $configuration['encrypt_key'], $configuration['token_url']);
}
/**
* Token url getter.
*
* @return string
* The token url.
*/
public function getTokenUrl() {
return $this->tokenUrl;
}
/**
* {@inheritdoc}
*/
public function isValid() {
return !empty($this->loginUser) && !empty($this->consumerId) && !empty($this->keyId) && !empty($this->tokenUrl);
}
}
<?php
namespace Drupal\salesforce_jwt\Plugin\SalesforceAuthProvider;
use Drupal\Core\Form\FormStateInterface;
use OAuth\Common\Http\Uri\Uri;
/**
* JWT Oauth plugin.
*
* @Plugin(
* id = "jwt_govcloud",
* label = @Translation("Salesforce JWT OAuth for GovCloud"),
* credentials_class = "\Drupal\salesforce_jwt\Consumer\JWTGovCloudCredentials"
* )
*/
class SalesforceJWTGovCloudPlugin extends SalesforceJWTPlugin {
/**
* {@inheritdoc}
*/
public static function defaultConfiguration() {
$defaults = parent::defaultConfiguration();
return array_merge($defaults, [
'token_url' => '',
]);
}
/**
* {@inheritdoc}
*/
public function getTokenUrl() {
return $this->getCredentials()->getTokenUrl();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['token_url'] = [
'#title' => $this->t('Token URL'),
'#type' => 'textfield',
'#default_value' => $this->getCredentials()->getTokenUrl(),
'#description' => $this->t('Enter a token URL, like https://yourcompany.my.salesforce.com'),
'#required' => TRUE,
];
return $form;
}
/**
* Overrides AbstractService::requestAccessToken for jwt-bearer flow.
*
* This is only intended to use the token url instead of login url.
*
* @param string $assertion
* The JWT assertion.
* @param string $state
* Not used.
*
* @return \OAuth\Common\Token\TokenInterface
* Access Token.
*
* @throws \OAuth\Common\Http\Exception\TokenResponseException
*/
public function requestAccessToken($assertion, $state = NULL) {
$data = [
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $assertion,
];
$response = $this->httpClient->retrieveResponse(new Uri($this->getTokenUrl() . static::AUTH_TOKEN_PATH), $data, ['Content-Type' => 'application/x-www-form-urlencoded']);
$token = $this->parseAccessTokenResponse($response);
$this->storage->storeAccessToken($this->service(), $token);
return $token;
}
}
......@@ -2,16 +2,18 @@
namespace Drupal\salesforce_jwt\Plugin\SalesforceAuthProvider;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\key\KeyRepositoryInterface;
use Drupal\salesforce\SalesforceAuthProviderPluginBase;
use OAuth\Common\Http\Uri\Uri;
use Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface;
use Firebase\JWT\JWT;
use OAuth\Common\Http\Client\ClientInterface;
use OAuth\Common\Http\Uri\Uri;
use OAuth\Common\Token\TokenInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Firebase\JWT\JWT;
/**
* JWT Oauth plugin.
......@@ -39,27 +41,19 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
protected $keyRepository;
/**
* SalesforceAuthServiceBase constructor.
*
* @param array $configuration
* Configuration.
* @param string $plugin_id
* Plugin id.
* @param mixed $plugin_definition
* Plugin definition.
* @param \OAuth\Common\Http\Client\ClientInterface $httpClient
* Http client wrapper.
* @param \Drupal\salesforce\Storage\SalesforceAuthTokenStorageInterface $storage
* Token storage.
* @param \Drupal\key\KeyRepositoryInterface $keyRepository
* Key repository.
* Time service.
*
* @throws \OAuth\OAuth2\Service\Exception\InvalidScopeException
* On error.
* @var \Drupal\Component\Datetime\TimeInterface
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ClientInterface $httpClient, SalesforceAuthTokenStorageInterface $storage, KeyRepositoryInterface $keyRepository) {
protected $time;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ClientInterface $httpClient, SalesforceAuthTokenStorageInterface $storage, ConfigFactoryInterface $configFactory, KeyRepositoryInterface $keyRepository, TimeInterface $time) {
$this->keyRepository = $keyRepository;
parent::__construct($configuration, $plugin_id, $plugin_definition, $httpClient, $storage);
$this->time = $time;
parent::__construct($configuration, $plugin_id, $plugin_definition, $httpClient, $storage, $configFactory);
}
/**
......@@ -67,7 +61,7 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$configuration = array_merge(self::defaultConfiguration(), $configuration);
return new static($configuration, $plugin_id, $plugin_definition, $container->get('salesforce.http_client_wrapper'), $container->get('salesforce.auth_token_storage'), $container->get('key.repository'));
return new static($configuration, $plugin_id, $plugin_definition, $container->get('salesforce.http_client_wrapper'), $container->get('salesforce.auth_token_storage'), $container->get('config.factory'), $container->get('key.repository'), $container->get('datetime.time'));
}
/**
......@@ -93,7 +87,11 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
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()]));
$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'] = [
......@@ -177,6 +175,7 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
$response = $this->httpClient->retrieveResponse(new Uri($this->getLoginUrl() . static::AUTH_TOKEN_PATH), $data, ['Content-Type' => 'application/x-www-form-urlencoded']);
$token = $this->parseAccessTokenResponse($response);
$this->storage->storeAccessToken($this->service(), $token);
$this->refreshIdentity($token);
return $token;
}
......@@ -184,7 +183,9 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
* {@inheritDoc}
*/
public function refreshAccessToken(TokenInterface $token) {
return $this->requestAccessToken($this->generateAssertion());
$token = $this->requestAccessToken($this->generateAssertion());
$this->refreshIdentity($token);
return $token;
}
/**
......@@ -194,7 +195,9 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
* JWT Assertion.
*/
protected function generateAssertion() {
$key = $this->keyRepository->getKey($this->getCredentials()->getKeyId())->getKeyValue();
$key = $this->keyRepository->getKey($this->getCredentials()->getKeyId()) ?
$this->keyRepository->getKey($this->getCredentials()->getKeyId())
->getKeyValue() : '';
$token = $this->generateAssertionClaim();
return JWT::encode($token, $key, 'RS256');
}
......@@ -211,7 +214,7 @@ class SalesforceJWTPlugin extends SalesforceAuthProviderPluginBase {
'iss' => $cred->getConsumerKey(),
'sub' => $cred->getLoginUser(),
'aud' => $cred->getLoginUrl(),
'exp' => \Drupal::time()->getCurrentTime() + 60,
'exp' => $this->time->getCurrentTime() + 60,
];
}
......
<?php
namespace Drupal\Tests\salesforce_jwt\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Test JWT Auth.
*
* @group salesforce_jwt
*/
class SalesforceJwtTest extends WebDriverTestBase {
/**
* Default theme required for D9.
*
* @var string
*/
protected $defaultTheme = 'stark';
/**
* A key entity to use for testing.
*
* @var \Drupal\key\KeyInterface
*/
protected $testKey;
/**
* Modules.
*
* @var array
*/
public static $modules = [
'key',
'typed_data',
'dynamic_entity_reference',
'salesforce',
'salesforce_test_rest_client',
'salesforce_jwt',
];
/**
* Admin user to test form.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* Id of shared cert key.
*/
const KEY_ID = 'salesforce_jwt_test_key';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['authorize salesforce']);
$this->drupalLogin($this->adminUser);
$this->createTestKey(self::KEY_ID, 'authentication', 'file');
\Drupal\key\Entity\Key::load(self::KEY_ID)
->set('key_provider_settings', [
'file_location' => __DIR__ . '/testKey.pem',
'strip_line_breaks' => FALSE,
])->save();
}
/**
* Test adding a jwt provider plugin.
*/
public function testJwtAuth() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalGet('admin/config/salesforce/authorize/add');
$labelField = $page->findField('label');
$label = $this->randomString();
$labelField->setValue($label);
$page->findField('provider')->setValue('jwt');
$assert_session->assertWaitOnAjaxRequest();
$edit = [
'provider_settings[consumer_key]' => 'foo',
'provider_settings[login_user]' => 'bar',
'provider_settings[login_url]' => 'https://login.salesforce.com',
'provider_settings[encrypt_key]' => self::KEY_ID,
];
foreach ($edit as $key => $value) {
$assert_session->fieldExists($key);
$page->fillField($key, $value);
}
$this->createScreenshot(\Drupal::root() . '/sites/default/files/simpletest/sfjwt-1.png');
$page->pressButton('Save');
// Weird behavior from testbot: machine name field doesn't seem to work
// as expected. Machine name field doesn't appear until after clicking
// "save", so we fill it and have to click save again. IDKWTF.
if ($page->findField('id')) {
$page->fillField('id', strtolower($this->randomMachineName()));
$this->createScreenshot(\Drupal::root() . '/sites/default/files/simpletest/sfjwt-2.png');
$page->pressButton('Save');
}
$assert_session->assertWaitOnAjaxRequest();
$this->createScreenshot(\Drupal::root() . '/sites/default/files/simpletest/sfjwt-3.png');
$assert_session->addressEquals('admin/config/salesforce/authorize/list');
$assert_session->pageTextContainsOnce($label);
$assert_session->pageTextContainsOnce('Authorized');
$assert_session->pageTextContainsOnce('Salesforce JWT OAuth');
}
/**
* Make a key for testing operations that require a key.
*/
protected function createTestKey($id, $type = NULL, $provider = NULL) {
$keyArgs = [
'id' => $id,
'label' => 'Test key',
];
if ($type != NULL) {
$keyArgs['key_type'] = $type;
}
if ($provider != NULL) {
$keyArgs['key_provider'] = $provider;
}
$this->testKey = \Drupal\key\Entity\Key::create($keyArgs);
$this->testKey->save();
return $this->testKey;
}
}