Summary
API credentials are stored in State key value store, which does not allow them to be set from settings.php and is contrary to widely adopted practice
To do
Update config definitions to include SF consumer key, secret, and login URLDone!Update references use config instead of stateDone!Create an update hook to copy the values from state to configDone!Add instructions in README(done), project page, and example code in salesforce_example to demonstrate how to use
Original post
Right now I can't see how I would set and/or override this module's API credentials in environment-specific settings.php files, as is typically done. Instead of config or settings storage, it seems the creds are currently stored in state (in the database).
If the module stored the keys in settings we could do something like this:
// Sandbox Salesforce config.
$settings['salesforce.consumer_key'] = 'foo';
$settings['salesforce.consumer_secret'] = 'bar';
$settings['salesforce.login_url'] = 'https://test.salesforce.com';
So first: are you all achieving something like this in some way I'm currently unaware of?
If not, I hope to provide a patch. For reasons of security and automation I'd prefer to store our API keys as environment-specific environment variables and provide them to Drupal via settings.php. Ideally we would not have the Salesforce API credentials stored in the Drupal database at all.
| Comment | File | Size | Author |
|---|---|---|---|
| #26 | salesforce-remove-immutable-config-from-auth-form-2961514-26.patch | 4.18 KB | chrisolof |
| #18 | interdiff_15-18.txt | 11.06 KB | chrisolof |
| #18 | salesforce-api-creds-to-config-2961514-18.patch | 27.65 KB | chrisolof |
| #15 | interdiff_11-15.txt | 1.84 KB | chrisolof |
| #15 | salesforce-api-creds-to-config-2961514-15.patch | 16.18 KB | chrisolof |
Comments
Comment #2
aaronbaumanThey need to stay in state, because rest endpoints change per salesforce org (sandbox vs production).
But, you can override state vars from settings.php similar to config:
Comment #3
chrisolof@aaronbauman
I'm not finding this to be the case. If I set one of these in my settings.php file as follows:
This has absolutely no effect on what comes back from a
drush eval "var_export(\Drupal::state()->get('salesforce.consumer_key'));". What's in my database always comes back - and if I clear the stored value from the DB I get backNULL, regardless of what's in my settings.php file. This corresponds to what I see on the Salesforce authorize form, which I understand to use the same\Drupal::state()->get().If instead I issue a
drush eval "var_export(\Dr\Core\Site\Settings::get('salesforce.consumer_key'));", I do see the "foo" value come back.Am I missing something? Is this actually working for you?
If not, my proposal is to shift these three API configuration items (salesforce.consumer_key, salesforce.consumer_secret, and salesforce.login_url) from state storage into a storage system that can be easily overridden per environment. Originally I was thinking of just having this stuff defined in settings.php (no form fields / no db storage). I think this could would work well, but might be disruptive to the existing installations that won't have anything defined in settings.php. I suppose we could look first for a setting and fall back to state when that doesn't exist. Looking at other modules I see a pretty big trend toward utilizing config (which does allow settings.php overrides - so this would work too). Config is nice in that a module update hook may be able to move the existing state values into the site's active config, and would still allow for setting the values in the authorize form, which users are accustomed to. You could use settings.php to get the job done, but you wouldn't have to.
Anyway just ideas - thoughts?
Comment #4
aaronbaumanMmm, you're right.
Looks like $settings overrides the Drupal\Core\Site\Settings service.
I know there's a way to do this from settings.php but can't find an example - i'll keep looking and let you know
We can switch to using config once drupal core properly supports different config sets per environment.
(Open issue, I'm too lazy to actually find it)
Comment #5
aaronbaumanAfter researching, there is no way to override State from settings.php
Config doesn't work for users who want to keep their credentials out of YML (and therefore version control), and requires additional overhead to manage different values per environment.
Settings might be the best option, but since Settings are read-only they can't be exposed in a form.
I'm open to any solutions which will accommodate:
Maybe a primary source in Settings, with fallback to State would work?
Comment #6
chrisolofThis certainly seems like the simplest solution in terms of implementation and it should have zero impact on existing installations.
My only hesitation with this approach is that every module I've analyzed that also deals with this where-to-store-the-api-keys space is using config. Here are some examples (think of any API-connected Drupal module you've used and you may find the same):
- Commerce stripe
- Commerce paypal
- S3 filesystem
- Mailchimp
Talking with others at DrupalCon it's clear config can be used to supply API keys to Drupal in a way that keeps the (real) keys out of version control and out of the database:
Without contrib:
1. Save and export dummy/placeholder values for the API keys - like "[value-supplied-via-config-override]" or "0000" - you get the idea. These non-sensitive values do get tracked in version control. Or if the API fields are optional you just don't put values into the fields.
2. Settings.php / settings.local.php is used to replace those placeholder/empty values with real ones (currently, awkwardly the overridden values or even the fact that overrides are in place will not show in the UI - but there's a fix in the works for this). See Configuration override system for details and why overridden values aren't shown as default values in config form fields.
With contrib:
While I haven't personally verified this to work, it sounds like folks are using Config ignore in combination with Settings.php / settings.local.php config overrides to completely keep sensitive configuration (like API keys/endpoints) out of their codebase. Again, I haven't verified, but it sounds like dummy/placeholder values aren't necessary because those sensitive items you've config-ignored are not included in the exported config files at all.
Anyway that's what's pulling me toward config here. I don't love it, but it is what the larger community appears to be doing - and it does allow for setting / updating the values through the UI, overriding the values in settings.php, and (in round-about way) keeping the values out of version control (if you know what you're doing).
Thoughts? Personally I'm on the fence...
Comment #7
aaronbaumanYeah, as insufficient as it is, sounds like moving this back into config is the way to go.
If that's what other modules are doing, then presumably efforts to improve config management will be moving in that direction as well.
Updated issue summary with todos, but lemme know if i missed anything.
Comment #8
chrisolofAdded login URL to what should move into config (login URL also changes for us as we move from prod to staging & development environments - so having that in config means we can override that per environment too).
Comment #9
aaronbaumanChanging this to task
Comment #10
chrisolofComment #11
chrisolofPatch attached. Working well for me, but certainly needs review.
Also wasn't sure where to include details about this in the salesforce_example module, but I've got a new section in the readme dedicated to this.
Anyway let me know what you think and if you have any questions about what's going on in here. Thanks!
Comment #13
chrisolofWhoops sorry it looks like I broke some tests - updated patch coming soon...
Comment #14
aaronbaumanMaybe a cache problem on config?
On these lines:
try comparing to the results of RestClient::getConsumerKey(), etc
Comment #15
chrisolofYeah it seemed like the config object in the test was stale. I think this will pass now.
Comment #16
aaronbaumanThis patch looks great.
Sorry, I should have caught this before:
Drupal\salesforce_encrypt\Rest\RestClientneeds to be updated too to use config instead of state.I think this comprises
getDecryptedandsetDecrypted, as well as the injected dependencies viaSalesforceEncryptServiceProvider.Doesn't look like there's any test coverage for this, but i'm not sure that it's totally necessary. If you can figure out a basic test in < 5 minutes, then go fot it, otherwise just those changes. Thanks again for this patch!
Comment #17
aaronbaumanPS: planning to get this and #2899460: Handling of field properties into a 3.1 release within the next couple weeks.
Comment #18
chrisolofGood catch. I reworked
Drupal\salesforce_encrypt\Rest\RestClientso that it focuses more on becoming an encryption/decryption layer atop its parentDrupal\salesforce\Rest\RestClient, rather than doing direct storage and retrieval itself. It no longer cares where each value ends up or is sourced from (state vs config - that's left to the parent to decide), so the move to config no longer breaks it. Should make ongoing maintenance / adjustments easier as they'll cascade better from parent to child (rather than needing to be repeated in both).Anyway this is working well for me in a test environment but it certainly needs review and more testing.
Comment #19
aaronbaumanLooks great, love the elegance of this approach too.
I'll try to give this a more thorough review by the end of this week.
Thanks again.
Comment #20
aaronbaumanThis is in, thanks again for your work here.
Only change i had to make is to switch to RestClient::getConsumerKey and ::getConsumerSecret on the auth form (instead of getting it from config) so that it could be decrypted before populating the default input value.
Comment #22
alanburke commentedJust a quick note to say thanks for this.
It's working really well for us.
Comment #23
aaronbaumanGlad to hear it.
Updated status.
Comment #24
chrisolofAwesome! Thanks for bringing this in. We're using it across a number of environments and it seems to be working well.
One concern with the change to RestClient::getConsumerKey and ::getConsumerSecret for default values in the auth form:
RestClient::getConsumerKey and ::getConsumerSecret return immutable config, which means overrides come through. Populating the form with overrides (immutable config) means overrides can creep into config any time someone authorizes a site. It's basically what the new config system is trying to eliminate:
- Configuration override system doc page
Suggestion: We show (mutable / non-overridden) config values here in the form, decrypted if necessary.
I'll try and follow with a patch here soon so you can see better what I'm proposing. I think it'll be a pretty simple/small change.
Comment #25
aaronbaumanAhh - yeah, that makes sense
Reopening
Comment #26
chrisolofAttached patch shows mutable / non-overridden config values in the auth form, decrypted when necessary. Needs review.
Comment #28
aaronbaumanThis is great, thanks. committed to fd80671
As a followup, i'm gonna move encryption profile assignment setting from State to Config
Comment #30
kaypro4 commentedHey there, I think that this change is resulting in the consumer key and secret being blown away (and auth broken) in my deployed environments since we don't have those two keys in config in source.
I'm trying to track what happened here and how best to address. We track both config files (obviously) and settings in source so neither are great for storing the secret value. I could store it as an env var on the server but won't go there unless I have to.
I see some decrypt/encrypt code added as a result of this change so wondering if storing the secret encrypted in config in source is an option. But don't see any info in the readme on the workflow to encrypt the config value.
Sorry for the confusion, and thanks for any support!
Matt
Comment #31
aaronbauman@kaypro4 sounds like you need to enable and set up salesforce_encrypt
get your keys set up, create an encryption profile, and assign it to your salesforce_encrypt config.
Then your config values - including consumer key, consumer secret, and identity - will be encrypted and safer to commit to version control