Skip to content

Commit fced96b

Browse files
committed
Merge branch 'release/26.7.0'
2 parents 01ae3fd + efc1d60 commit fced96b

22 files changed

Lines changed: 88 additions & 78 deletions

File tree

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO.
44

5+
26.7.0 (2026-04-08)
6+
===================
7+
8+
- ORCiD Integration Project - BE Part
9+
510
26.6.3 (2026-04-02)
611
===================
712

admin/management/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@
1919
re_path(r'^empty_metadata_dataarchive_registration_bulk_resync', views.EmptyMetadataDataarchiveRegistrationBulkResync.as_view(),
2020
name='empty-metadata-dataarchive-registration-bulk-resync'),
2121
re_path(r'^sync_notification_templates', views.SyncNotificationTemplates.as_view(),
22-
name='sync_notification_templates')
22+
name='sync_notification_templates'),
23+
re_path(r'^remove_orcid_from_user_social', views.RemoveOrcidFromUserSocial.as_view(),
24+
name='remove_orcid_from_user_social')
2325
]

admin/management/views.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from osf.management.commands.fetch_cedar_metadata_templates import ingest_cedar_metadata_templates
1313
from osf.management.commands.sync_doi_metadata import sync_doi_metadata, sync_doi_empty_metadata_dataarchive_registrations
1414
from osf.management.commands.populate_notification_types import populate_notification_types
15+
from osf.management.commands.remove_orcid_from_user_social import remove_orcid_from_user_social
1516
from scripts.find_spammy_content import manage_spammy_content
1617
from django.urls import reverse
1718
from django.shortcuts import redirect
@@ -181,3 +182,11 @@ def post(self, request):
181182
populate_notification_types()
182183
messages.success(request, 'Notification templates have been successfully synced.')
183184
return redirect(reverse('management:commands'))
185+
186+
187+
class RemoveOrcidFromUserSocial(ManagementCommandPermissionView):
188+
189+
def post(self, request):
190+
remove_orcid_from_user_social()
191+
messages.success(request, 'Orcid from user social have been successfully removed.')
192+
return redirect(reverse('management:commands'))

admin/templates/management/commands.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ <h4><u>Sync Notification Templates</u></h4>
165165
</nav>
166166
</form>
167167
</section>
168+
<section>
169+
<h4><u>Remove existing orcid info from user social</u></h4>
170+
<p>
171+
Use this management command to remove existing orcid info from user social.
172+
</p>
173+
<form method="post"
174+
action="{% url 'management:remove_orcid_from_user_social'%}">
175+
{% csrf_token %}
176+
<nav>
177+
<input class="btn btn-success" type="submit" value="Run" />
178+
</nav>
179+
</form>
180+
</section>
168181
</div>
169182
</section>
170183
{% endblock %}

api/base/schemas/social-schema.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@
6767
"academiaInstitution": {
6868
"description": "The academiaInstitution for the given user",
6969
"type": "string"
70-
},
71-
"orcid": {
72-
"description": "The orcid for the given user",
73-
"type": "string"
7470
}
7571
},
7672
"additionalProperties": false

api/users/views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,11 @@ def post(self, request, *args, **kwargs):
755755
# 1. update user oauth, with pending status
756756
external_identity[external_id_provider][external_id] = 'LINK'
757757
if external_id_provider in user.external_identity:
758-
user.external_identity[external_id_provider].update(external_identity[external_id_provider])
758+
# v1 looks to be used because of /confirm/external/ usage for auth but add orcid external identity rewrite updates for v2 as well
759+
if external_id_provider == settings.EXTERNAL_IDENTITY_PROFILE.get('OrcidProfile'):
760+
user.external_identity[external_id_provider] = external_identity[external_id_provider]
761+
else:
762+
user.external_identity[external_id_provider].update(external_identity[external_id_provider])
759763
else:
760764
user.external_identity.update(external_identity)
761765
if not user.accepted_terms_of_service and accepted_terms_of_service:
@@ -1153,7 +1157,6 @@ class ConfirmEmailView(generics.CreateAPIView):
11531157

11541158
def _process_external_identity(self, user, external_identity, service_url):
11551159
"""Handle all external_identity logic, including task enqueueing and url updates."""
1156-
11571160
provider = next(iter(external_identity))
11581161
if provider not in user.external_identity:
11591162
raise ValidationError('External-ID provider mismatch.')

api_tests/users/views/test_user_detail.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,6 @@ def user_one(self):
453453
twitter='userOneTwitter',
454454
linkedIn='userOneLinkedIn',
455455
impactStory='userOneImpactStory',
456-
orcid='userOneOrcid',
457456
researcherId='userOneResearcherId'
458457
)
459458
)
@@ -508,7 +507,6 @@ def data_new_user_one(self, user_one):
508507
'twitter': ['http://twitter.com/newtwitter'],
509508
'linkedIn': ['https://www.linkedin.com/newLinkedIn'],
510509
'impactStory': 'https://impactstory.org/newImpactStory',
511-
'orcid': 'http://orcid.org/newOrcid',
512510
'researcherId': 'http://researcherid.com/rid/newResearcherId',
513511
}},
514512
}}
@@ -916,7 +914,6 @@ def test_partial_patch_user_logged_in(self, app, user_one, url_user_one):
916914
assert user_one.social['twitter'] in social['twitter']
917915
assert user_one.social['linkedIn'] in social['linkedIn']
918916
assert user_one.social['impactStory'] in social['impactStory']
919-
assert user_one.social['orcid'] in social['orcid']
920917
assert user_one.social['researcherId'] in social['researcherId']
921918
assert user_one.fullname == 'new_fullname'
922919
assert user_one.suffix == 'The Millionth'
@@ -933,7 +930,6 @@ def test_patch_all_social_fields(self, app, user_one, url_user_one, mock_spam_he
933930
'academiaProfileID': 'okokokok',
934931
'ssrn': 'aaaa',
935932
'impactStory': 'why not',
936-
'orcid': 'ork-id',
937933
'researchGate': 'Why are there so many of these',
938934
'researcherId': 'ok-lastone',
939935
'academiaInstitution': 'Center for Open Science'
@@ -1005,7 +1001,6 @@ def test_partial_patch_user_logged_in_no_social_fields(
10051001
assert user_one.social['twitter'] in social['twitter']
10061002
assert user_one.social['linkedIn'] in social['linkedIn']
10071003
assert user_one.social['impactStory'] in social['impactStory']
1008-
assert user_one.social['orcid'] in social['orcid']
10091004
assert user_one.social['researcherId'] in social['researcherId']
10101005
assert user_one.fullname == 'new_fullname'
10111006
assert user_one.suffix == 'The Millionth'
@@ -1056,7 +1051,6 @@ def test_put_user_logged_in(self, app, user_one, data_new_user_one, url_user_one
10561051
assert 'newtwitter' in social['twitter'][0]
10571052
assert 'newLinkedIn' in social['linkedIn'][0]
10581053
assert 'newImpactStory' in social['impactStory']
1059-
assert 'newOrcid' in social['orcid']
10601054
assert 'newResearcherId' in social['researcherId']
10611055
user_one.reload()
10621056
assert user_one.fullname == data_new_user_one['data']['attributes']['full_name']
@@ -1069,7 +1063,6 @@ def test_put_user_logged_in(self, app, user_one, data_new_user_one, url_user_one
10691063
assert 'newtwitter' in social['twitter'][0]
10701064
assert 'newLinkedIn' in social['linkedIn'][0]
10711065
assert 'newImpactStory' in social['impactStory']
1072-
assert 'newOrcid' in social['orcid']
10731066
assert 'newResearcherId' in social['researcherId']
10741067

10751068
def test_update_user_sanitizes_html_properly(

api_tests/users/views/test_user_exceptions.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def user(self):
2222
twitter='userOneTwitter',
2323
linkedIn='userOneLinkedIn',
2424
impactStory='userOneImpactStory',
25-
orcid='userOneOrcid',
2625
researcherId='userOneResearcherId'
2726
)
2827
)

api_tests/users/views/test_user_external_login.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def csrf_token(self):
4848
@pytest.fixture()
4949
def session_data(self):
5050
session = SessionStore()
51-
session['auth_user_external_id_provider'] = 'orcid'
51+
session['auth_user_external_id_provider'] = 'ORCID'
5252
session['auth_user_external_id'] = '1234-1234-1234-1234'
5353
session['auth_user_fullname'] = 'external login'
5454
session['auth_user_external_first_login'] = True
@@ -62,7 +62,7 @@ def test_external_login(self, app, payload, url, session_data, csrf_token):
6262
with capture_notifications():
6363
res = app.post_json_api(url, payload, headers={'X-CSRFToken': csrf_token})
6464
assert res.status_code == 200
65-
assert res.json == {'external_id_provider': 'orcid', 'auth_user_fullname': 'external login'}
65+
assert res.json == {'external_id_provider': 'ORCID', 'auth_user_fullname': 'external login'}
6666
assert not OSFUser.objects.get(username='freddie@mercury.com').is_confirmed
6767

6868
def test_invalid_payload(self, app, url, session_data, csrf_token):
@@ -84,6 +84,24 @@ def test_existing_user(self, app, payload, url, user_one, session_data, csrf_tok
8484
with capture_notifications():
8585
res = app.post_json_api(url, payload, headers={'X-CSRFToken': csrf_token})
8686
assert res.status_code == 200
87-
assert res.json == {'external_id_provider': 'orcid', 'auth_user_fullname': 'external login'}
87+
assert res.json == {'external_id_provider': 'ORCID', 'auth_user_fullname': 'external login'}
8888
user_one.reload()
8989
assert user_one.username in user_one.unconfirmed_emails
90+
91+
def test_existing_user_orcid_overwrites(self, app, payload, url, user_one, session_data, csrf_token):
92+
user_one.external_identity = {
93+
'ORCID': {
94+
'0000-0000-0000-0000': 'LINK',
95+
}
96+
}
97+
user_one.save()
98+
app.set_cookie(CSRF_COOKIE_NAME, csrf_token)
99+
app.set_cookie(settings.COOKIE_NAME, str(session_data))
100+
assert user_one.external_identity['ORCID'] == {'0000-0000-0000-0000': 'LINK'}
101+
assert '0000-0000-0000-0000' in user_one.external_identity['ORCID']
102+
payload['data']['attributes']['email'] = user_one.username
103+
with capture_notifications():
104+
res = app.post_json_api(url, payload, headers={'X-CSRFToken': csrf_token})
105+
assert res.status_code == 200
106+
user_one.reload()
107+
assert user_one.external_identity['ORCID'] == {'1234-1234-1234-1234': 'LINK'}

framework/auth/views.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,6 @@ def external_login_confirm_email_get(auth, uid, token):
644644

645645
user.date_last_logged_in = timezone.now()
646646
user.external_identity[provider][provider_id] = 'VERIFIED'
647-
user.social[provider.lower()] = provider_id
648647
del user.email_verifications[token]
649648
user.verification_key = generate_verification_key()
650649
user.save()
@@ -1094,7 +1093,11 @@ def external_login_email_post():
10941093
# 1. update user oauth, with pending status
10951094
external_identity[external_id_provider][external_id] = 'LINK'
10961095
if external_id_provider in user.external_identity:
1097-
user.external_identity[external_id_provider].update(external_identity[external_id_provider])
1096+
# v1 looks to be used because of /confirm/external/ usage for auth but add orcid external identity rewrite updates for v2 as well
1097+
if external_id_provider == settings.EXTERNAL_IDENTITY_PROFILE.get('OrcidProfile'):
1098+
user.external_identity[external_id_provider] = external_identity[external_id_provider]
1099+
else:
1100+
user.external_identity[external_id_provider].update(external_identity[external_id_provider])
10981101
else:
10991102
user.external_identity.update(external_identity)
11001103
if not user.accepted_terms_of_service and form.accepted_terms_of_service.data:
@@ -1129,7 +1132,6 @@ def external_login_email_post():
11291132
campaign=None,
11301133
accepted_terms_of_service=accepted_terms_of_service
11311134
)
1132-
# TODO: [#OSF-6934] update social fields, verified social fields cannot be modified
11331135
user.save()
11341136
# 3. send confirmation email
11351137
send_confirm_email_async(

0 commit comments

Comments
 (0)