Index: salesforce/README.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/salesforce/README.txt,v retrieving revision 1.4.6.9 diff -u -p -r1.4.6.9 README.txt --- salesforce/README.txt 17 May 2010 20:14:25 -0000 1.4.6.9 +++ salesforce/README.txt 19 May 2010 13:38:39 -0000 @@ -27,6 +27,16 @@ You will need to know your login data an http://php.net/soap +RECOMMENDED: + +1) Download and install your organization's generated Enterprise WSDL file. + (see WORKING WITH WSDL FILES) + +2) AES encryption + http://drupal.org/project/aes + (see SOME NOTES ABOUT SECURITY). + + INSTALLATION: 1) Download, uncompress and situate the module as per usual. @@ -85,6 +95,19 @@ QUICKSTART: mapping for and manually create a Salesforce object for it. +SOME NOTES ABOUT SECURITY: +By default all SalesForce credentials are stored in the variables table, +unencrypted. If this is a problem for you, this module supports encryption via +aes module http://drupal.org/project/aes. You will need to create a directory +outside your webroot (you can use the same one you used for your WSDL) wherein +your encryption key will be stored. Your credentials will thus forth be +encrypted as securely as AES allows. PLEASE NOTE: your data is still only as +secure as your network. It may be possible for a savvy attacker to access your +data at any of various points between your Drupal site and SalesForce.com. As +this always, you should educate yourself about the risks involved before storing +and transferring sensitive data across the internet. + + WORKING WITH WSDL FILES If you do not upload a WSDL file, Salesforce module will use a default .wsdl Index: salesforce/salesforce_api/salesforce_api.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/salesforce/salesforce_api/salesforce_api.admin.inc,v retrieving revision 1.2.2.27 diff -u -p -r1.2.2.27 salesforce_api.admin.inc --- salesforce/salesforce_api/salesforce_api.admin.inc 17 May 2010 20:14:25 -0000 1.2.2.27 +++ salesforce/salesforce_api/salesforce_api.admin.inc 19 May 2010 13:38:39 -0000 @@ -10,7 +10,7 @@ /** * The settings form at admin/settings/salesforce. */ -function salesforce_api_settings_form() { +function salesforce_api_settings_form($form_state) { $form = array(); // Use the username field to collapse the API settings fieldset. @@ -25,26 +25,61 @@ function salesforce_api_settings_form() '#weight' => -10, ); $form['api']['salesforce_api_username'] = array( - '#type' => 'textfield', + '#type' => 'password', '#title' => t('Username'), '#description' => t('Should be in the form of an e-mail address.'), - '#default_value' => variable_get('salesforce_api_username', ''), - '#required' => TRUE, + '#default_value' => $form_state['values']['salesforce_api_username'], + '#required' => !variable_get('salesforce_api_username', FALSE), ); $form['api']['salesforce_api_password'] = array( '#type' => 'password', '#title' => t('Password'), '#description' => t('Enter the password used when logging into Salesforce.'), - '#default_value' => variable_get('salesforce_api_password', ''), + '#default_value' => $form_state['values']['salesforce_api_password'], + '#required' => !variable_get('salesforce_api_password', FALSE), ); $form['api']['salesforce_api_token'] = array( - '#type' => 'textfield', + '#type' => 'password', '#title' => t('Security token'), - '#description' => t('Set your security token by logging into Salesforce and navigating to Setup > My Personal Information > Reset My Security Token.'), - '#required' => TRUE, - '#default_value' => variable_get('salesforce_api_token', ''), - ); - + '#description' => t('Set your security token by logging into Salesforce and + navigating to Setup > My Personal Information > Reset My Security Token.'), + '#default_value' => $form_state['values']['salesforce_api_token'], + '#required' => !variable_get('salesforce_api_token', FALSE), + ); + + if (!empty($username)) { + $form['api']['#description'] = + t('Salesforce.com connection is working properly.
Edit the following + fields only if you wish to change your login credentials.'); + $form['api']['salesforce_api_reset_credentials'] = array( + '#type' => 'checkbox', + '#title' => 'Clear SalesForce API credentials', + '#description' => 'Erase current API login settings. Clearing SalesForce + API credentials will prevent your website from connecting to Salesforce.com', + ); + } + $form['api']['encryption'] = array( + '#type' => 'item', + '#title' => 'Encryption', + '#value' => t('It is highly recommended that you use encryption to protect your SalesForce.com credentials. Encryption is supported via AES module.'), + 'status' => array('#type' => 'item', '#title' => FALSE), + ); + if (salesforce_api_encryption_available(array('display_all' => TRUE))) { + $form['api']['encryption']['status']['#value'] = + 'Encryption is available and configured properly.'; + $form['api']['encryption']['salesforce_api_encrypt'] = array( + '#type' => 'checkbox', + '#title' => 'Encrypt SalesForce credentials (HIGHLY RECOMMENDED)', + '#description' => 'Note: enabling this setting will not encrypt existing credentials.', + '#default_value' => TRUE, + ); + } + else { + $form['api']['encryption']['status']['#value'] = + 'AES is not installed - encryption is not + available.'; + } + $form['log'] = array( '#type' => 'fieldset', '#title' => t('Log settings'), @@ -102,11 +137,11 @@ function salesforce_api_settings_form() $wsdl_dir = variable_get('salesforce_api_dir_wsdl', FALSE); $form['wsdl'] = array( '#type' => 'fieldset', - '#title' => 'WSDL directory', - '#description' => 'Your organization\'s WSDL file can expose potentially sensitive information. It is highly recommended that your WSDL file be stored outside your webroot. Please enter either a path relative to your webroot (e.g. ../wsdl) or a fully qualified path (e.g. /home/username/wsdl) in which to store your WSDL.', + '#title' => t('WSDL directory'), + '#description' => t('Your organization\'s WSDL file can expose potentially sensitive information. It is highly recommended that your WSDL file be stored outside your webroot. Please enter either a path relative to your webroot (e.g. ../wsdl) or a fully qualified path (e.g. /home/username/wsdl) in which to store your WSDL.'), 'salesforce_api_dir_wsdl' => array( '#type' => 'textfield', - '#title' => 'WSDL Directory', + '#title' => t('WSDL Directory'), '#default_value' => $wsdl_dir, ), '#collapsible' => TRUE, @@ -121,21 +156,56 @@ function salesforce_api_settings_form() } /** - * Settings form validate handler to verify new salesforce credentials before saving them. + * Settings form validate handler to verify new salesforce credentials before + * saving them. */ function salesforce_api_settings_form_validate($form, &$form_state) { $values = $form_state['values']; - if (!salesforce_api_connect($values['salesforce_api_username'], $values['salesforce_api_password'], $values['salesforce_api_token'], TRUE)) { - // If not, prevent the user from overwriting the current configuration. - form_set_error('salesforce_api_username', t('Unable to connect to Salesforce. Please check your credentials.')); + + if (isset($values['salesforce_api_dir_wsdl']) + && !file_exists($values['salesforce_api_dir_wsdl'])) { + form_set_error('salesforce_api_dir_wsdl', 'The specified WSDL directory does not exist. Please make sure the directory exists, check your input, and try again.'); } - else { - drupal_set_message(t('Salesforce connection established.')); + + // If we are clearing values, no need to continue validation. + if ($values['salesforce_api_reset_credentials']) { + return; } - if (isset($form_state['values']['salesforce_api_dir_wsdl']) - && !file_exists($form_state['values']['salesforce_api_dir_wsdl'])) { - form_set_error('salesforce_api_dir_wsdl', 'The specified directory does not exist. Please make sure the directory exists, check your input, and try again.'); + foreach (array('salesforce_api_username', 'salesforce_api_password', 'salesforce_api_token') as $value) { + if (empty($values[$value])) { + $errors[$value] = ucwords(str_replace('salesforce_api_', ' ', $value)) . ' is required'; + } + } + + if (count($errors) == 3) { + // If all 3 fields are empty, the user has not tried to change credentials. + // Unset the form values in order to preserve existing credentials. + unset($form_state['values']['salesforce_api_username'], + $form_state['salesforce_api_password'], + $form_state['values']['salesforce_api_token']); + return; + } + elseif (!empty($errors)) { + drupal_set_message(t('Unable to reset SalesForce API credentials.'), 'error'); + foreach ($errors as $field => $error) { + form_set_error($field, $error); + } + // If we got errors already no need to continue with validation. + return; + } + + // If we are setting or resetting values, test the connection. + $connection = salesforce_api_connect($values['salesforce_api_username'], + $values['salesforce_api_password'], $values['salesforce_api_token'], TRUE); + if (is_object($connection)) { + drupal_set_message(t('Connection established. SalesForce credentials updated.')); + } else { + drupal_set_message( + t('Resetting SalesForce API credentials failed.', 'error')); + form_set_error('salesforce_api_username', t('Unable to connect to Salesforce. Please check your credentials.')); + form_set_error('salesforce_api_password'); + form_set_error('salesforce_api_token'); } } @@ -143,21 +213,36 @@ function salesforce_api_settings_form_va * Settings form submit handler so that password doesn't get deleted. */ function salesforce_api_settings_form_submit($form, &$form_state) { - // If the user hit "Save Configuration" and the required field - // salesforce_api_password is blank, try to get it from variables - if ($form_state['values']['submit'] == $form_state['values']['op'] and empty($form_state['values']['salesforce_api_password'])) { - $pass = variable_get('salesforce_api_password', FALSE); - if (isset($pass)) { - $form_state['values']['salesforce_api_password'] = $pass; - } - } - if (variable_get('salesforce_api_dir_wsdl', FALSE) != - $form_state['values']['salesforce_api_dir_wsdl']) { + $values = $form_state['values']; + + if (variable_get('salesforce_api_dir_wsdl', FALSE) + != $values['salesforce_api_dir_wsdl']) { drupal_set_message('Please make sure the WSDL directory is writeable, and upload a valid SalesForce .xml or .wsdl file.'); $form_state['redirect'] = array( SALESFORCE_PATH_UPDATE_WSDL, 'destination=' . SALESFORCE_PATH_ADMIN); } + + if ($values['salesforce_api_reset_credentials']) { + unset($form_state['values']['salesforce_api_reset_credentials']); + foreach (array('username', 'password', 'token', 'encrypt') as $value) { + variable_del('salesforce_api_' . $value); + unset($form_state['values']['salesforce_api_' . $value]); + } + drupal_set_message('SalesForce credentials reset.'); + // If credentials were reset, we don't need to continue to encryption. + return; + } + + if ($values['salesforce_api_encrypt'] + && !empty($values['salesforce_api_username'])) { + $form_state['values']['salesforce_api_username'] = + salesforce_api_encrypt($values['salesforce_api_username']); + $form_state['values']['salesforce_api_password'] = + salesforce_api_encrypt($values['salesforce_api_password']); + $form_state['values']['salesforce_api_token'] = + salesforce_api_encrypt($values['salesforce_api_token']); + } } /** Index: salesforce/salesforce_api/salesforce_api.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/salesforce/salesforce_api/salesforce_api.install,v retrieving revision 1.2.2.12 diff -u -p -r1.2.2.12 salesforce_api.install --- salesforce/salesforce_api/salesforce_api.install 23 Feb 2010 20:54:11 -0000 1.2.2.12 +++ salesforce/salesforce_api/salesforce_api.install 19 May 2010 13:38:39 -0000 @@ -150,6 +150,14 @@ function salesforce_api_requirements($ph $description = t('Unable to connect to Salesforce using current credentials.', array('!url' => url(SALESFORCE_PATH_ADMIN))); $severity = REQUIREMENT_ERROR; } + elseif (!salesforce_api_encryption_available(array('check_config' => FALSE))) { + $description = t('Encryption is unavailable. Using encryption is highly recommended in order to better secure your data.'); + $severity = REQUIREMENT_ERROR; + } + elseif (!salesforce_api_encryption_available(array('check_config' => TRUE))) { + $description = t('SalesForce encryption is enabled but not configured securely.'); + $severity = REQUIREMENT_WARNING; + } else { $description = ''; $severity = REQUIREMENT_OK; Index: salesforce/salesforce_api/salesforce_api.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/salesforce/salesforce_api/salesforce_api.module,v retrieving revision 1.2.2.39 diff -u -p -r1.2.2.39 salesforce_api.module --- salesforce/salesforce_api/salesforce_api.module 17 May 2010 20:14:25 -0000 1.2.2.39 +++ salesforce/salesforce_api/salesforce_api.module 19 May 2010 13:38:39 -0000 @@ -194,13 +195,25 @@ function salesforce_api_connect($usernam return $sf; } - // Boolean, whether we are connecting with the default website user or not. - $default_site_user = $username == variable_get('salesforce_api_username', FALSE); + // Load up the sitewide API credentials if none were provided. + $encrypted = variable_get('salesforce_api_encrypt', FALSE); + $default_username = $encrypted + ? salesforce_api_decrypt(variable_get('salesforce_api_username', '')) + : variable_get('salesforce_api_username', ''); + $username = $username ? $username : $default_username; + $password = $password + ? $password + : ($encrypted + ? salesforce_api_decrypt(variable_get('salesforce_api_password', '')) + : variable_get('salesforce_api_password', '')); + $token = $token + ? $token + : ($encrypted + ? salesforce_api_decrypt(variable_get('salesforce_api_token', '')) + : variable_get('salesforce_api_token', '')); - // Load up the sitewide API credentials if no others were provided: - $username = $username ? $username : variable_get('salesforce_api_username', ''); - $password = $password ? $password : variable_get('salesforce_api_password', ''); - $token = $token ? $token : variable_get('salesforce_api_token', ''); + // Boolean, whether we are connecting with the default website user or not. + $default_site_user = $username == $default_username; // Include the file that defines the class. require_once(drupal_get_path('module', 'salesforce_api') .'/salesforce.class.inc'); @@ -864,6 +877,68 @@ function salesforce_api_cron() { return; } +/** + * Wrappers for encryption lib. Right now only AES encryption is supported. + * If/when other methods are supported, this abstraction layer will make the + * transition easier. + */ +function salesforce_api_decrypt($value) { + return aes_decrypt($value); +} +function salesforce_api_encrypt($value) { + return aes_encrypt($value); +} +function salesforce_api_encryption_available($options = array()) { + $defaults = array( + 'check_config' => TRUE, + 'display_errors' => FALSE, + 'display_warnings' => FALSE, + 'display_all' => FALSE, + 'fail_threshold' => 'warnings', + ); + $options = array_merge($defaults, $options); + extract($options); + $errors = array(); + $warnings = array(); + + if (!module_exists('aes')) { + $errors[] = 'AES Encryption module is not installed.'; + } + elseif ($check_config) { + if (!variable_get('aes_key_path', FALSE) + || variable_get('aes_key_storage_method', FALSE) != 'File') { + $warnings[] = 'AES Encryption is installed but not configured securely. + Please go configure AES Encryption to use + file storage to enable encryption for SalesForce credentials.'; + } + } + + if ($display_errors || $display_all) { + foreach ($errors as $msg) { + drupal_set_message(t($msg), 'error'); + } + } + + if ($display_warnings || $display_all) { + foreach ($warnings as $msg) { + drupal_set_message(t($msg), 'warning'); + } + } + + switch ($fail_threshold) { + case 'errors': { + if (empty($errors)) { + return TRUE; + } + } + case 'warnings': { + if (empty($errors) && empty($warnings)) { + return TRUE; + } + } + } + return FALSE; +} /** * Wrapper for SOAP SforceBaseClient::describeSObject