Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ def safe_key() -> str:
SOCIALACCOUNT_PROVIDERS = {
"salesforce": {
"SCOPE": ["web", "full", "refresh_token"],
"OAUTH_PKCE_ENABLED": True,
"APP": {
"client_id": SFDX_CLIENT_ID,
"secret": SFDX_CLIENT_SECRET,
Expand Down
39 changes: 38 additions & 1 deletion metadeploy/api/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from django_rq import job as django_rq_job
from rq.exceptions import ShutDownImminentException
from rq.worker import StopRequested
from sfdo_template_helpers.crypto import fernet_decrypt, fernet_encrypt

from .cci_configs import MetaDeployCCI, extract_user_and_repo
from .cleanup import cleanup_user_data
Expand Down Expand Up @@ -177,6 +178,34 @@ def prepend_python_path(path):
sys.path = prev_path


def _save_updated_user_tokens(user, org_config):
"""Write any rotated OAuth tokens back to the user's SocialToken record.

Salesforce uses refresh token rotation: each time the refresh token is used,
a new one is returned that must be stored for the next refresh. Without this,
the second job run for a user will fail with invalid_grant / expired token.
"""
social_token = user.social_account.socialtoken_set.first()
if not social_token:
return

changed = False
new_access_token = org_config.access_token
new_refresh_token = org_config.refresh_token

if new_access_token and new_access_token != fernet_decrypt(social_token.token):
social_token.token = fernet_encrypt(new_access_token)
changed = True
if new_refresh_token and new_refresh_token != fernet_decrypt(
social_token.token_secret
):
social_token.token_secret = fernet_encrypt(new_refresh_token)
changed = True

if changed:
social_token.save()


def run_flows(
*,
plan: Plan,
Expand Down Expand Up @@ -275,7 +304,15 @@ def run_flows(
]
org = ctx.keychain.get_org(current_org)
if not settings.METADEPLOY_FAST_FORWARD:
result.run(ctx, plan, steps, org)
try:
# Refresh the access token right before running so long-polling
# tasks (e.g. SetOrgWideDefaults) start with the freshest token.
if result.user:
org_config.refresh_oauth_token(ctx.keychain)
result.run(ctx, plan, steps, org)
finally:
if result.user:
_save_updated_user_tokens(result.user, org)


run_flows_job = job(run_flows)
Expand Down
1 change: 1 addition & 0 deletions metadeploy/api/salesforce.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def refresh_access_token(
try:
org_config = OrgConfig(config, org_name, keychain=keychain)
org_config.refresh_oauth_token(keychain, is_sandbox=sbx_login)
config.update(org_config.config)
return org_config
except HTTPError as err:
_handle_sf_error(err, scratch_org=scratch_org)
Expand Down
50 changes: 35 additions & 15 deletions requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
#
aioredis==1.3.1
# via channels-redis
annotated-types==0.7.0
# via pydantic
ansi2html==1.9.1
# via -r requirements/prod.in
appdirs==1.4.4
# via fs
asgiref==3.7.2
# via
# channels
Expand All @@ -23,8 +23,7 @@ attrs==23.2.0
# automat
# service-identity
# twisted
authlib==1.2.1
# via simple-salesforce
# zeep
autobahn==23.6.2
# via daphne
automat==22.10.0
Expand Down Expand Up @@ -53,7 +52,7 @@ channels-redis==3.4.1
# via -r requirements/prod.in
charset-normalizer==3.2.0
# via requests
click==8.1.6
click==8.3.3
# via
# cumulusci
# django-rq-scheduler
Expand All @@ -69,14 +68,13 @@ crontab==1.0.1
# via rq-scheduler
cryptography==41.0.7
# via
# authlib
# autobahn
# cumulusci
# pyjwt
# pyopenssl
# service-identity
# sfdo-template-helpers
cumulusci==4.0.1
cumulusci==4.10.0
# via -r requirements/prod.in
daphne==3.0.2
# via channels
Expand Down Expand Up @@ -150,8 +148,6 @@ faker-nonprofit==1.0.0
# via snowfakery
freezegun==1.4.0
# via rq-scheduler
fs==2.4.16
# via cumulusci
github3-py==4.0.1
# via
# -r requirements/prod.in
Expand Down Expand Up @@ -179,6 +175,8 @@ importlib-metadata==6.8.0
# via keyring
incremental==22.10.0
# via twisted
isodate==0.7.2
# via zeep
jinja2==3.1.2
# via
# cumulusci
Expand All @@ -194,7 +192,9 @@ logfmt==0.4
# -r requirements/prod.in
# sfdo-template-helpers
lxml==4.9.3
# via cumulusci
# via
# cumulusci
# zeep
markdown==3.5.2
# via sfdo-template-helpers
markdown-it-py==2.2.0
Expand All @@ -206,18 +206,24 @@ markupsafe==2.1.3
# werkzeug
mdurl==0.1.2
# via markdown-it-py
more-itertools==11.0.2
# via simple-salesforce
msgpack==1.0.7
# via channels-redis
natsort==8.4.0
# via robotframework-pabot
oauthlib==3.2.2
# via requests-oauthlib
packaging==23.2
# via django-js-reverse
# via
# cumulusci
# django-js-reverse
pillow==10.2.0
# via
# -r requirements/prod.in
# django-colorfield
platformdirs==4.9.6
# via zeep
psutil==5.9.6
# via cumulusci
psycopg2-binary==2.9.9
Expand All @@ -230,17 +236,20 @@ pyasn1-modules==0.3.0
# via service-identity
pycparser==2.21
# via cffi
pydantic==1.10.12
pydantic==2.9.2
# via
# cumulusci
# snowfakery
pydantic-core==2.23.4
# via pydantic
pygments==2.17.2
# via rich
pyjwt[crypto]==2.8.0
# via
# cumulusci
# django-allauth
# github3-py
# simple-salesforce
pyopenssl==23.3.0
# via twisted
python-baseconv==1.2.2
Expand All @@ -261,6 +270,7 @@ pytz==2023.3.post1
# via
# cumulusci
# djangorestframework
# zeep
pyyaml==6.0.1
# via
# cumulusci
Expand All @@ -275,16 +285,23 @@ requests==2.29.0
# cumulusci
# django-allauth
# github3-py
# requests-file
# requests-futures
# requests-oauthlib
# requests-toolbelt
# robotframework-requests
# salesforce-bulk
# simple-salesforce
# snowfakery
# zeep
requests-file==3.0.1
# via zeep
requests-futures==1.0.1
# via cumulusci
requests-oauthlib==1.3.1
# via django-allauth
requests-toolbelt==1.0.0
# via zeep
rich==13.9.4
# via cumulusci
robotframework==6.1.1
Expand Down Expand Up @@ -332,18 +349,17 @@ service-identity==24.1.0
# twisted
sfdo-template-helpers @ https://github.com/SFDO-Tooling/sfdo-template-helpers/archive/v0.23.0.tar.gz
# via -r requirements/prod.in
simple-salesforce==1.11.4
simple-salesforce==1.12.9
# via
# cumulusci
# salesforce-bulk
six==1.16.0
# via
# automat
# bleach
# fs
# python-dateutil
# salesforce-bulk
snowfakery==4.0.0
snowfakery==4.2.1
# via cumulusci
sqlalchemy==1.4.49
# via
Expand All @@ -358,6 +374,8 @@ txaio==23.1.1
typing-extensions==4.7.1
# via
# pydantic
# pydantic-core
# simple-salesforce
# twisted
unicodecsv==0.14.1
# via salesforce-bulk
Expand All @@ -377,6 +395,8 @@ whitenoise==6.6.0
# via -r requirements/prod.in
xmltodict==0.13.0
# via cumulusci
zeep==4.3.2
# via simple-salesforce
zipp==3.17.0
# via importlib-metadata
zope-interface==6.1
Expand Down