Skip to content

[ENG-11829] Revoke ORCID trusted party access during GDPR delete - Part 1 - #11885

Merged
cslzchen merged 7 commits into
CenterForOpenScience:feature/gdpr-delete-orcid-rewritefrom
Vlad0n20:fix/ENG-11829-2
Sep 2, 2026
Merged

[ENG-11829] Revoke ORCID trusted party access during GDPR delete - Part 1#11885
cslzchen merged 7 commits into
CenterForOpenScience:feature/gdpr-delete-orcid-rewritefrom
Vlad0n20:fix/ENG-11829-2

Conversation

@Vlad0n20

@Vlad0n20 Vlad0n20 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Ticket

Purpose

Revoke ORCID trusted party access during GDPR delete - Part 1

Changes

Side Effects

QE Notes

CE Notes

Documentation

@cslzchen cslzchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st pass done

Comment thread framework/auth/cas.py
Comment thread osf/models/user.py Outdated
Comment thread framework/auth/cas.py Outdated
Comment thread framework/auth/cas.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated

@cslzchen cslzchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2nd pass done ⭐

Comment thread framework/auth/cas.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread website/settings/defaults.py Outdated
@cslzchen
cslzchen changed the base branch from feature/pbs-26-15 to feature/gdpr-delete-orcid-rewrite August 28, 2026 13:09

@cslzchen cslzchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3rd pass done 🌟

This is the main PR now, please update ticket title.

Comment thread osf/models/user.py
Comment thread osf/models/user.py
Comment thread osf/models/user.py Outdated
Comment thread osf/models/user.py Outdated
Comment thread osf/migrations/0053_gdpr_delete_and_orcid_revoke.py
Comment thread framework/auth/cas.py
Comment on lines +260 to +264
sentry.log_message(
f'CAS response ORCID attributes: user=[{user._id}], orcidId=[{orcid_id}], '
f'orcidAccessToken=[{"present" if access_token else "missing"}]',
level=logging.WARNING,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future (new ticket), change this to debug log and only log empty access token error after we dev tested on staging1.

Comment thread framework/auth/cas.py
Comment on lines +270 to +274
sentry.log_message(
f'ORCID token stored on external_identity_tokens: user=[{user._id}], '
f'provider_id=[{orcid_id}], access_token=[{"present" if access_token else "missing"}]',
level=logging.INFO,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Comment thread framework/auth/cas.py
Comment thread framework/auth/cas.py
Comment thread website/settings/defaults.py Outdated
@Vlad0n20 Vlad0n20 changed the title Fix/eng 11829 2 [ENG-11829] - revoke ORCID trusted party access Sep 1, 2026

@cslzchen cslzchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality looks good, I am going to merge and deploy to staging1.

As for my review comments, you can include it in your follow-up PR #11888

Comment thread osf/models/user.py
Comment on lines 815 to +835
for service in user.external_identity:
for service_id in user.external_identity[service].keys():
if not (
service_id in self.external_identity.get(service, '') and
self.external_identity[service][service_id] == 'VERIFIED'
):
# Prevent 'CREATE', merging user has already been created.
external = user.external_identity[service][service_id]
status = 'VERIFIED' if external == 'VERIFIED' else 'LINK'
if self.external_identity.get(service):
self.external_identity[service].update(
{service_id: status}
)
else:
self.external_identity[service] = {
service_id: status
}

token_entry = user.external_identity_tokens.get(service, {}).get(service_id)
if token_entry:
self.external_identity_tokens.setdefault(service, {})[service_id] = token_entry

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking the merge, but I have a question on how external identity is handled during merge?

e.g. two user have different ORCiD, it seems this will create two ORCiD entries, and I think this is the same behavior for tokens

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked — confirmed, this is real. Tracing it: if self already has ORCID id A VERIFIED and the merged-away user has a different id B also VERIFIED, the if not (...) guard is true for B (it's not yet in self.external_identity['ORCID']), so it falls into self.external_identity[service].update({service_id: status}), which adds B alongside A instead of replacing it. Same for the new token line (setdefault(service, {})[service_id] = token_entry) — it adds a second key. So a merge can leave self.external_identity['ORCID'] and self.external_identity_tokens['ORCID'] each with two entries.

This loop itself is pre-2016 generic multi-provider merge logic (git blame points to the original commit), so it's not something this PR introduced — the new token line just mirrors the same existing per-service_id loop. But it does mean the "a user has at most one ORCID identity" assumption _clear_identifying_information relies on (next(iter(...)), grabs one arbitrary entry) doesn't hold for merged accounts: if a user merges in an account with a different verified ORCID, GDPR delete will only revoke one of the two tokens and silently leave the other with live trusted-party access to ORCID.

Agreed this shouldn't block this PR. Want this tracked as a separate ticket, or should I fix _clear_identifying_information now to iterate/revoke all ORCID token entries instead of just the first one?

Comment thread osf/models/user.py
Comment thread osf/models/user.py
Comment on lines +63 to +64
'auth_user_external_id_access_token': user_dict.get('external_id_access_token'),
'auth_user_external_id_refresh_token': user_dict.get('external_id_refresh_token'),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of the scope of PR but is fine to have it here.

Comment thread framework/auth/views.py
Comment on lines +1053 to +1054
external_id_access_token = session.get('auth_user_external_id_access_token', None)
external_id_refresh_token = session.get('auth_user_external_id_refresh_token', None)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, out of scope but it's ok

Comment thread framework/auth/views.py
Comment thread framework/auth/views.py
campaign=None,
accepted_terms_of_service=accepted_terms_of_service
)
cas.save_orcid_access_token_to_user(user, external_id, external_id_access_token, external_id_refresh_token)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

@cslzchen cslzchen changed the title [ENG-11829] - revoke ORCID trusted party access [ENG-11829] Revoke ORCID trusted party access during GDPR delete - Part 1 Sep 2, 2026
@cslzchen
cslzchen merged commit 51fb2fa into CenterForOpenScience:feature/gdpr-delete-orcid-rewrite Sep 2, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants