diff --git a/src/core/admin.py b/src/core/admin.py index 740e7ae5ea..60789723f4 100755 --- a/src/core/admin.py +++ b/src/core/admin.py @@ -8,6 +8,7 @@ from django.contrib.auth.admin import UserAdmin from django.utils.safestring import mark_safe from django.template.defaultfilters import truncatewords +from django.conf import settings from utils import admin_utils from core import models, forms @@ -134,6 +135,12 @@ class AccountAdmin(UserAdmin): admin_utils.PasswordResetInline, ] + def get_readonly_fields(self, request, obj=None): + if settings.ENABLE_ORCID: + return ["orcid"] + else: + return [] + def _roles_in(self, obj): if obj: journals = journal_models.Journal.objects.filter( diff --git a/src/core/logic.py b/src/core/logic.py index 85479724e1..5c9a54f65f 100755 --- a/src/core/logic.py +++ b/src/core/logic.py @@ -148,6 +148,35 @@ def send_confirmation_link(request, new_user): ) +def send_orcid_request(request, user): + context = { + "user": user, + "user_profile_url": request.site_type.site_url( + reverse("core_edit_profile"), + ), + } + log_dict = {"level": "Info", "types": "ORCID Request", "target": None} + + user.date_orcid_requested = timezone.now() + user.save() + + if user.is_active: + template = "orcid_request" + subject = "subject_orcid_request" + else: + template = "orcid_activate_request" + subject = "subject_orcid_activate_request" + + notify_helpers.send_email_with_body_from_setting_template( + request, + template, + subject, + user.email, + context, + log_dict=log_dict, + ) + + def resize_and_crop( img_path, size=settings.DEFAULT_CROP_SIZE, diff --git a/src/core/migrations/0110_account_orcid_token_account_orcid_token_expiration_and_more.py b/src/core/migrations/0110_account_orcid_token_account_orcid_token_expiration_and_more.py new file mode 100644 index 0000000000..b4b3356419 --- /dev/null +++ b/src/core/migrations/0110_account_orcid_token_account_orcid_token_expiration_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.26 on 2026-01-21 20:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0109_salutation_name_20250707_1420"), + ] + + operations = [ + migrations.AddField( + model_name="account", + name="orcid_token", + field=models.CharField(blank=True, max_length=40, null=True), + ), + migrations.AddField( + model_name="account", + name="orcid_token_expiration", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="orcidtoken", + name="access_token", + field=models.CharField(blank=True, max_length=40, null=True), + ), + migrations.AddField( + model_name="orcidtoken", + name="access_token_expiration", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/src/core/migrations/0111_account_date_orcid_requested.py b/src/core/migrations/0111_account_date_orcid_requested.py new file mode 100644 index 0000000000..65b6d50922 --- /dev/null +++ b/src/core/migrations/0111_account_date_orcid_requested.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.26 on 2026-02-13 18:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0110_account_orcid_token_account_orcid_token_expiration_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='date_orcid_requested', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index e02a2ab8b3..2c63b3a977 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -71,6 +71,7 @@ from utils import logic as utils_logic from utils.forms import plain_text_validator from production import logic as production_logic +from utils.orcid import is_token_valid fs = JanewayFileSystemStorage() logger = get_logger(__name__) @@ -485,6 +486,9 @@ class Account(AbstractBaseUser, PermissionsMixin): orcid = models.CharField( max_length=40, null=True, blank=True, verbose_name=_("ORCiD") ) + orcid_token = models.CharField(max_length=40, null=True, blank=True) + orcid_token_expiration = models.DateTimeField(null=True, blank=True) + date_orcid_requested = models.DateTimeField(blank=True, null=True) twitter = models.CharField( max_length=300, null=True, blank=True, verbose_name=_("Twitter Handle") ) @@ -948,6 +952,12 @@ def hypothesis_username(self): )[:30] return username.lower() + def get_orcid_url(self): + return f"{settings.ORCID_URL.replace('oauth/authorize', '')}{self.orcid}" + + def is_orcid_token_valid(self): + return is_token_valid(self.orcid, self.orcid_token) + def generate_expiry_date(): return timezone.now() + timedelta(days=1) @@ -959,6 +969,8 @@ class OrcidToken(models.Model): expiry = models.DateTimeField( default=generate_expiry_date, verbose_name=_("Expires on") ) + access_token = models.CharField(max_length=40, null=True, blank=True) + access_token_expiration = models.DateTimeField(null=True, blank=True) def __str__(self): return "ORCiD Token [{0}] - {1}".format(self.orcid, self.token) diff --git a/src/core/tests/test_app.py b/src/core/tests/test_app.py index 280f5a83cf..cd14d93a09 100755 --- a/src/core/tests/test_app.py +++ b/src/core/tests/test_app.py @@ -15,6 +15,7 @@ from django.urls.base import clear_script_prefix from django.utils import timezone from django.core import mail +from journal.tests.utils import make_test_journal from utils.testing import helpers from utils import setting_handler, install @@ -219,7 +220,7 @@ def test_register_with_orcid_token(self, record_mock): self.assertContains(response, "Campbell") self.assertContains(response, "Kasey") self.assertContains(response, "campbell@evu.edu") - self.assertNotContains(response, "Register with ORCiD") + self.assertNotContains(response, "Register with ORCID") self.assertContains(response, "http://sandbox.orcid.org/0000-0000-0000-0000") self.assertContains( response, @@ -252,13 +253,13 @@ def test_register_with_orcid_token(self, record_mock): def test_registration(self): response = self.client.get(reverse("core_register")) self.assertEqual(response.status_code, 200) - self.assertContains(response, "Register with ORCiD") + self.assertContains(response, "Register with ORCID") @override_settings(ENABLE_ORCID=False) def test_registration(self): response = self.client.get(reverse("core_register")) self.assertEqual(response.status_code, 200) - self.assertNotContains(response, "Register with ORCiD") + self.assertNotContains(response, "Register with ORCID") @override_settings(URL_CONFIG="domain", CAPTCHA_TYPE=None) def test_mixed_case_login_different_case(self): @@ -582,3 +583,80 @@ def setUp(self): ) clear_script_prefix() + + @override_settings(ENABLE_ORCID=False) + def test_profile_orcid_disabled(self): + self.client.force_login(self.admin_user) + response = self.client.get(reverse("core_edit_profile")) + self.assertContains( + response, '' + ) + + def test_profile_orcid_enabled_no_orcid(self): + # Profile should offer to connect orcid + self.client.force_login(self.admin_user) + response = self.client.get(reverse("core_edit_profile")) + self.assertNotContains(response, "ORCID could not be validated.") + self.assertContains(response, "Connect your ORCID") + + @override_settings(ORCID_URL="https://sandbox.orcid.org/oauth/authorize") + def test_profile_orcid_unverified(self): + self.admin_user.orcid = "0000-0000-0000-0000" + self.admin_user.save() + self.client.force_login(self.admin_user) + response = self.client.get(reverse("core_edit_profile")) + self.assertContains(response, "ORCID iD could not be validated.") + self.assertContains(response, "Connect your ORCID") + self.assertContains(response, "https://sandbox.orcid.org/0000-0000-0000-0000") + + @patch.object(models.Account, "is_orcid_token_valid") + @override_settings(ORCID_URL="https://sandbox.orcid.org/oauth/authorize") + def test_profile_orcid(self, mock_method): + # override is_orcid_token valid make if valid + mock_method.return_value = True + self.admin_user.orcid = "0000-0000-0000-0000" + self.admin_user.orcid_token = "0a0aaaaa-0aa0-0000-aa00-a00aa0a00000" + self.admin_user.save() + self.client.force_login(self.admin_user) + response = self.client.get(reverse("core_edit_profile")) + self.assertContains(response, "https://sandbox.orcid.org/0000-0000-0000-0000") + self.assertContains(response, "remove_orcid") + self.assertContains( + response, '' + ) + self.assertNotContains(response, "ORCID could not be validated.") + + @patch.object(models.Account, "is_orcid_token_valid") + @override_settings( + URL_CONFIG="domain", ORCID_URL="https://sandbox.orcid.org/oauth/authorize" + ) + def test_profile_orcid_not_admin(self, mock_method): + mock_method.return_value = True + + journal_kwargs = { + "code": "fetests", + "domain": "fetests.janeway.systems", + } + journal = make_test_journal(**journal_kwargs) + + journal_manager = helpers.create_user( + "jmanager@mailinator.com", ["journal-manager"], journal=journal + ) + journal_manager.is_active = True + journal_manager.save() + + self.regular_user.orcid = "0000-0000-0000-0000" + self.regular_user.orcid_token = "0a0aaaaa-0aa0-0000-aa00-a00aa0a00000" + self.regular_user.save() + + self.client.force_login(journal_manager) + + url = reverse("core_user_edit", kwargs={"user_id": self.regular_user.pk}) + response = self.client.get(url, SERVER_NAME=journal.domain) + + self.assertContains(response, "https://sandbox.orcid.org/0000-0000-0000-0000") + self.assertContains( + response, '' + ) + self.assertNotContains(response, "ORCID could not be validated.") + self.assertNotContains(response, "remove_orcid") diff --git a/src/core/tests/test_views.py b/src/core/tests/test_views.py index 3747d32fa5..2f33ccd85f 100644 --- a/src/core/tests/test_views.py +++ b/src/core/tests/test_views.py @@ -379,7 +379,7 @@ def test_no_orcid_code_redirects_with_next(self): @override_settings(URL_CONFIG="domain") @override_settings(ENABLE_ORCID=True) def test_no_orcid_id_redirects_with_next(self, retrieve_tokens): - retrieve_tokens.return_value = None + retrieve_tokens.return_value = None, None, None get_data = { "code": "12345", "next": self.next_url_raw, @@ -399,7 +399,7 @@ def test_action_login_account_found_redirects_to_next( self, retrieve_tokens, ): - retrieve_tokens.return_value = self.user_orcid_uri + retrieve_tokens.return_value = None, None, self.user_orcid_uri get_data = { "code": "12345", "next": self.next_url_raw, @@ -422,7 +422,11 @@ def test_action_login_matching_email_redirects_to_next( orcid_details, ): # Change ORCID so it doesn't work - retrieve_tokens.return_value = "https://orcid.org/0000-0001-2312-3123" + retrieve_tokens.return_value = ( + None, + None, + "https://orcid.org/0000-0001-2312-3123", + ) # Return an email that will work orcid_details.return_value = {"emails": [self.user_email]} @@ -449,7 +453,11 @@ def test_action_login_failure_redirects_with_next( orcid_details, ): # Change ORCID so it doesn't work - retrieve_tokens.return_value = "https://orcid.org/0000-0001-2312-3123" + retrieve_tokens.return_value = ( + None, + None, + "https://orcid.org/0000-0001-2312-3123", + ) orcid_details.return_value = {"emails": []} get_data = { @@ -471,7 +479,7 @@ def test_action_login_failure_redirects_with_next( @override_settings(URL_CONFIG="domain") @override_settings(ENABLE_ORCID=True) def test_action_register_redirects_with_next(self, retrieve_tokens): - retrieve_tokens.return_value = self.user_orcid_uri + retrieve_tokens.return_value = None, None, self.user_orcid_uri get_data = { "code": "12345", "next": self.next_url_raw, diff --git a/src/core/views.py b/src/core/views.py index 174c6a3712..68aa31d8c8 100755 --- a/src/core/views.py +++ b/src/core/views.py @@ -215,7 +215,9 @@ def user_login_orcid(request): # There is an orcid code, meaning the user has authenticated on orcid.org. # Make another request to orcid.org to verify it. - orcid_id = orcid.retrieve_tokens(orcid_code, request.site_type) + access_token, expiration, orcid_id = orcid.retrieve_tokens( + orcid_code, request.site_type + ) # If verification did not work, send them to the regular login page. if not orcid_id: @@ -261,7 +263,11 @@ def user_login_orcid(request): # Then send the user to a decision page that tells them # the ORCID login did not work and they will need to register. models.OrcidToken.objects.filter(orcid=orcid_id).delete() - new_token = models.OrcidToken.objects.create(orcid=orcid_id) + new_token = models.OrcidToken.objects.create( + orcid=orcid_id, + access_token=access_token, + access_token_expiration=expiration, + ) return redirect( logic.reverse_with_next( "core_orcid_registration", @@ -282,6 +288,24 @@ def user_login_orcid(request): kwargs={"orcid_token": str(new_token.token)}, ) ) + elif action == "add_profile_orcid": + if not request.user.is_authenticated: + messages.add_message( + request, + messages.WARNING, + _("You must be logged in to connect an ORCID to your account."), + ) + return redirect(logic.reverse_with_next("core_login", next_url)) + request.user.orcid = orcid_id + request.user.orcid_token = access_token + request.user.orcid_expiration = expiration + request.user.save() + messages.add_message( + request, + messages.SUCCESS, + _("Your ORCID has been connected to your account."), + ) + return redirect(logic.reverse_with_next("core_edit_profile", next_url)) @login_required @@ -434,6 +458,9 @@ def register(request, orcid_token=None): if form.is_valid(): if token_obj: new_user = form.save() + new_user.orcid_token = token_obj.access_token + new_user.orcid_expiration = token_obj.access_token_expiration + new_user.save() if new_user.orcid: orcid_details = orcid.get_orcid_record_details(token_obj.orcid) for orcid_affil in orcid_details.get("affiliations", []): @@ -545,6 +572,7 @@ def edit_profile(request): :return: HttpResponse object """ user = request.user + form = forms.EditAccountForm(instance=user) send_reader_notifications = False next_url = request.GET.get("next", "") @@ -659,6 +687,12 @@ def edit_profile(request): elif "export" in request.POST: return logic.export_gdpr_user_profile(user) + elif "remove_orcid" in request.POST: + if orcid.revoke_token(user.orcid_token): + user.orcid = None + user.orcid_token = None + user.save() + form = forms.EditAccountForm(instance=user) template = "admin/core/accounts/edit_profile.html" context = { @@ -1534,24 +1568,32 @@ def user_edit(request, user_id): next_url = request.GET.get("next", "") if request.POST: - form = forms.EditAccountForm(request.POST, request.FILES, instance=user) - registration_form = forms.AdminUserForm( - request.POST, instance=user, request=request - ) - - if form.is_valid() and registration_form.is_valid(): - registration_form.save() - form.save() + if "request_orcid" in request.POST: + logic.send_orcid_request(request, user) messages.add_message( request, messages.SUCCESS, - "User account updated.", + _("Successfully requested ORCID iD from user."), + ) + else: + form = forms.EditAccountForm(request.POST, request.FILES, instance=user) + registration_form = forms.AdminUserForm( + request.POST, instance=user, request=request ) - if next_url: - return redirect(next_url) - else: - return redirect(reverse("core_manager_users")) + if form.is_valid() and registration_form.is_valid(): + registration_form.save() + form.save() + messages.add_message( + request, + messages.SUCCESS, + "User account updated.", + ) + + if next_url: + return redirect(next_url) + else: + return redirect(reverse("core_manager_users")) template = "core/manager/users/edit.html" context = { diff --git a/src/repository/urls.py b/src/repository/urls.py index f8251dd254..8bf5fb4623 100755 --- a/src/repository/urls.py +++ b/src/repository/urls.py @@ -68,6 +68,12 @@ views.repository_delete_author, name="repository_delete_author", ), + re_path( + r"^manager/(?P\d+)/author/(?P\d+)/request_orcid/(?P[-\w]+)/$", + views.repository_request_orcid, + name="repository_request_orcid", + ), + re_path( r"^submit/(?P\d+)/authors/order/$", views.preprints_author_order, diff --git a/src/repository/views.py b/src/repository/views.py index 82c7015219..5134983dad 100644 --- a/src/repository/views.py +++ b/src/repository/views.py @@ -27,6 +27,7 @@ models as core_models, forms as core_forms, views as core_views, + logic as core_logic, ) from journal import models as journal_models from utils import ( @@ -1466,6 +1467,28 @@ def preprints_author_order(request, preprint_id): return HttpResponse("Complete") +@login_required +def repository_request_orcid(request, preprint_id, account_id, redirect_path): + user = get_object_or_404( + core_models.Account, + pk=account_id, + ) + core_logic.send_orcid_request(request, user) + messages.add_message( + request, + messages.SUCCESS, + f"Successfully requested ORCID from {user.full_name()}", + ) + + return redirect( + reverse( + redirect_path, + kwargs={ + "preprint_id": preprint_id, + }, + ) + ) + @login_required @require_POST diff --git a/src/templates/admin/elements/accounts/orcid_field.html b/src/templates/admin/elements/accounts/orcid_field.html new file mode 100644 index 0000000000..fe631e79f0 --- /dev/null +++ b/src/templates/admin/elements/accounts/orcid_field.html @@ -0,0 +1,53 @@ +{% load orcid %} +{% load static %} + +
+ + {% for error in form.orcid.errors %} +
+ + {{ error|escape }} +
+ {% endfor %} + {% if form.orcid.value %} + +

+ + ORCID logo {{ form.instance.get_orcid_url }} + +

+ {% if form.instance.is_orcid_token_valid %} + {% if form.instance == request.user or request.user.is_admin %} +

+ +

+ {% endif %} + {% else %} +

ORCID iD could not be validated.

+ {% endif %} + {% endif %} + {% if not form.orcid.value or not form.instance.is_orcid_token_valid %} + {% if form.instance == request.user %} + + Connect your ORCID iD + + {% else %} + {% if form.instance.date_orcid_requested %} +

Request sent: {{ form.instance.date_orcid_requested|date:"m/d/Y" }}

+ {% endif %} + + {% endif %} + {% endif %} + {% if field.help_text %} +

{{ field.help_text|safe }}

+ {% endif %} +
diff --git a/src/templates/admin/elements/accounts/user_form.html b/src/templates/admin/elements/accounts/user_form.html index a0fdfdb389..42774ed9f9 100644 --- a/src/templates/admin/elements/accounts/user_form.html +++ b/src/templates/admin/elements/accounts/user_form.html @@ -14,11 +14,18 @@

{% trans "Social Media and Accounts" %}

{% include "admin/elements/forms/field.html" with field=form.twitter %} {% include "admin/elements/forms/field.html" with field=form.facebook %} - {% include "admin/elements/forms/field.html" with field=form.orcid %} {% include "admin/elements/forms/field.html" with field=form.github %} {% include "admin/elements/forms/field.html" with field=form.linkedin %} {% include "admin/elements/forms/field.html" with field=form.website %} + {% if not settings.ENABLE_ORCID %} + {% include "admin/elements/forms/field.html" with field=form.orcid %} + {% endif %}
+{% if settings.ENABLE_ORCID %} +
+ {% include 'admin/elements/accounts/orcid_field.html' with form=form %} +
+{% endif %}

{% trans "Biography and Signature" %}

diff --git a/src/templates/admin/elements/repository/orcid.html b/src/templates/admin/elements/repository/orcid.html new file mode 100644 index 0000000000..05d736d743 --- /dev/null +++ b/src/templates/admin/elements/repository/orcid.html @@ -0,0 +1,10 @@ +{% if author.account.orcid %} + {{ author.account.orcid }} +{% else %} + {% if author.account.date_orcid_requested %} +

Request sent: {{ author.account.date_orcid_requested|date:"m/d/Y" }}

+ {% endif %} + + Request + +{% endif %} diff --git a/src/templates/admin/repository/article.html b/src/templates/admin/repository/article.html index ce851b814b..49d892fb04 100644 --- a/src/templates/admin/repository/article.html +++ b/src/templates/admin/repository/article.html @@ -227,6 +227,7 @@

Authors

{% trans 'Name' %} {% trans 'Email' %} + {% trans 'ORCID' %} {% trans 'Affiliation' %} {% trans 'Edit' %} {% trans 'Delete' %} @@ -237,6 +238,9 @@

Authors

{{ author.account.full_name }} {{ author.account.email }} + + {% include "admin/elements/repository/orcid.html" %} + {% if author.affiliation %}{{ author.affiliation }}{% else %} {{ author.account.institution }}{% endif %} diff --git a/src/templates/admin/repository/submit/authors.html b/src/templates/admin/repository/submit/authors.html index 6d256c22d9..3465816ce0 100644 --- a/src/templates/admin/repository/submit/authors.html +++ b/src/templates/admin/repository/submit/authors.html @@ -75,6 +75,7 @@

{% trans 'Authors' %}

{% trans 'Name' %} {% trans 'Email' %} + {% trans 'ORCID' %} {% trans 'Affiliation' %} {% trans 'Delete' %} @@ -84,6 +85,9 @@

{% trans 'Authors' %}

{{ author.account.full_name }} {{ author.account.email }} + + {% include "admin/elements/repository/orcid.html" %} + {{ author.display_affiliation }} diff --git a/src/templates/admin/repository/submit/review.html b/src/templates/admin/repository/submit/review.html index f4dbd2ce29..0f47d44798 100644 --- a/src/templates/admin/repository/submit/review.html +++ b/src/templates/admin/repository/submit/review.html @@ -55,12 +55,16 @@

Authors

Email Address First Name Last Name + ORCID {% for author in preprint.preprintauthor_set.all %} {{ author.account.email }} {{ author.account.first_name }} {{ author.account.last_name }} + + {% include "admin/elements/repository/orcid.html" %} + {% endfor %} diff --git a/src/utils/install/journal_defaults.json b/src/utils/install/journal_defaults.json index ff88ba275a..29e464730d 100644 --- a/src/utils/install/journal_defaults.json +++ b/src/utils/install/journal_defaults.json @@ -1669,6 +1669,44 @@ "journal-manager" ] }, + { + "group": { + "name": "email" + }, + "setting": { + "description": "Email sent when user requests co-authors add ORCID iD.", + "is_translatable": true, + "name": "orcid_request", + "pretty_name": "ORCID iD Request", + "type": "rich-text" + }, + "value": { + "default": "

Dear {{ user.full_name }}

Your co-author has requested your ORCID iD. You can add it by through your profile {{ user_profile_url }}.

" + }, + "editable_by": [ + "editor", + "journal-manager" + ] + }, + { + "group": { + "name": "email" + }, + "setting": { + "description": "Email sent to co-authors that do not have accounts to create an account and add ORCID iD.", + "is_translatable": true, + "name": "orcid_activate_request", + "pretty_name": "ORCID iD Create Request", + "type": "rich-text" + }, + "value": { + "default": "

Dear {{ user.full_name }}

Your co-author has requested your ORCID iD. You can add it by through your profile {{ user_profile_url }}.

" + }, + "editable_by": [ + "editor", + "journal-manager" + ] + }, { "group": { "name": "general" @@ -3607,6 +3645,44 @@ "journal-manager" ] }, + { + "value": { + "default": "ORCID iD Request" + }, + "setting": { + "type": "char", + "pretty_name": "ORCID iD Request", + "is_translatable": true, + "description": "Subject for when a submitter requests a co-author's ORCID iD", + "name": "subject_orcid_request" + }, + "group": { + "name": "email_subject" + }, + "editable_by": [ + "editor", + "journal-manager" + ] + }, + { + "value": { + "default": "ORCID iD Request" + }, + "setting": { + "type": "char", + "pretty_name": "ORCID iD Request", + "is_translatable": true, + "description": "Subject for when a submitter requests a co-author's ORCID iD and their account is inactive", + "name": "subject_orcid_activate_request" + }, + "group": { + "name": "email_subject" + }, + "editable_by": [ + "editor", + "journal-manager" + ] + }, { "group": { "name": "email" diff --git a/src/utils/orcid.py b/src/utils/orcid.py index d2d193bd94..63ae02ac28 100755 --- a/src/utils/orcid.py +++ b/src/utils/orcid.py @@ -14,6 +14,7 @@ from django.http import QueryDict import requests from requests.exceptions import HTTPError +import datetime from utils import logic from utils.logger import get_logger @@ -48,12 +49,44 @@ def retrieve_tokens(authorization_code, site): r.raise_for_status() except HTTPError as e: logger.error("ORCID request failed: %s" % str(e)) - orcid_id = None + # after logging failure continue with an empty response + # to avoid additional errors + orcid_response = {} else: logger.info("OK response from ORCID") - orcid_id = json.loads(r.text).get("orcid") + orcid_response = json.loads(r.text) - return orcid_id + access_token = orcid_response.get("access_token", None) + orcid_id = orcid_response.get("orcid", None) + + if "expires_in" in orcid_response: + expires = orcid_response.get("expires_in") + expiration_date = datetime.datetime.now() + datetime.timedelta(seconds=expires) + else: + expiration_date = None + + return access_token, expiration_date, orcid_id + + +def is_token_valid(orcid_id, token): + api_client = OrcidAPI( + settings.ORCID_CLIENT_ID, settings.ORCID_CLIENT_SECRET, sandbox=True + ) + r = api_client._get_public_info( + orcid_id, "record", token, None, "application/orcid+json" + ) + return r.status_code == 200 + + +def revoke_token(token): + url = settings.ORCID_TOKEN_URL.replace("token", "revoke") + data = { + "client_id": settings.ORCID_CLIENT_ID, + "client_secret": settings.ORCID_CLIENT_SECRET, + "token": token, + } + r = requests.post(url, data=data) + return r.status_code == 200 def build_redirect_uri(site): @@ -67,7 +100,9 @@ def build_redirect_uri(site): def get_orcid_record(orcid): try: logger.info("Retrieving ORCID profile for %s", orcid) - api_client = OrcidAPI(settings.ORCID_CLIENT_ID, settings.ORCID_CLIENT_SECRET) + api_client = OrcidAPI( + settings.ORCID_CLIENT_ID, settings.ORCID_CLIENT_SECRET, sandbox=True + ) search_token = api_client.get_search_token_from_orcid() return api_client.read_record_public( orcid,