Skip to content

Author interface for ROR, CRediT, and ORCiD in submission workflow #4697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

joemull
Copy link
Member

@joemull joemull commented Apr 7, 2025

Closes #4519.
Closes #3111.
Fixes #1485.

This follows the proposed design of #4519 as closely as possible. The ORCiD-based flow works really well, CRediT roles are easy to add, it's easy to change the correspondence author, and authors are easy to re-order.

However, there's a bit more work to do around editing co-author affiliations. We may need to move straight on to #145, which was out of scope for this PR but is a natural way to solve the problem of allowing maximum control over affiliation data, while not introducing security risks where bad actors could edit any user's affiliations.

image
image
image

@joemull joemull requested a review from ajrbyers April 7, 2025 15:54
@ajrbyers
Copy link
Member

ajrbyers commented Apr 7, 2025

@joemull can you check you've pushed everything? I think there are some missing imports on submission.logic.

@ajrbyers ajrbyers assigned joemull and unassigned ajrbyers Apr 7, 2025
@joemull
Copy link
Member Author

joemull commented Apr 8, 2025

I don't get any import errors @ajrbyers--was it on server start or on loading a page?

@joemull joemull assigned ajrbyers and unassigned joemull Apr 8, 2025
@ajrbyers
Copy link
Member

ajrbyers commented Apr 8, 2025

I don't get any import errors @ajrbyers--was it on server start or on loading a page?

Check submission.logic there are a bunch of function calls for things that are not imported:

orcid.get_orcid_record_details(cleaned_orcid)
generate_dummy_email(orcid_details)
utils_shared.generate_password()

@ajrbyers ajrbyers assigned joemull and unassigned ajrbyers Apr 8, 2025
@joemull
Copy link
Member Author

joemull commented Apr 8, 2025

Ah, right you are. I put the author search functionality in that file at the last minute. To guard against simple things like this, I should probably write quick view tests for each branch of the view's POST logic, eh?

@joemull joemull assigned mauromsl and unassigned joemull Apr 8, 2025
@joemull joemull requested a review from mauromsl April 8, 2025 10:12
@joemull
Copy link
Member Author

joemull commented Apr 14, 2025

We did a demo of this in the design meeting last week. Here are notes from that.

  • Maybe the help text for inviting a co-author to update their affiliations should explain that an account exists, and the co-author should try to log in and will be guided through the process. Something like: “This account may be inactive, and if so the user will be guided through the account activation process.”
  • For adding credit roles, use the same hide/display interaction as for correspondence authors, rather than displaying them all in a horizontal spread of buttons.
  • Avoid CSS reflow of Edit and Delete buttons on affiliation when a second author is added.
  • Consider changing the “Add affiliation manually” to “Add my affiliation” or similar to avoid the user expecting this button to appear as well for co-authors.
  • On other buttons, add the object of the action too, like “Remove author”, “Edit affiliation”, etc.

@joemull
Copy link
Member Author

joemull commented Apr 14, 2025

My other todos when this comes back from review:

  • Add translation tags?
  • Check accessibility of the “Change” dropdown - aria roles and states etc.
  • Add tests for each branch of the view logic
  • Author names should be h3s
  • Check the parsing of ORCiD data using Andy's ORCiD

@joemull joemull removed their assignment Apr 23, 2025
@ajrbyers ajrbyers assigned joemull and unassigned ajrbyers Apr 23, 2025
@joemull
Copy link
Member Author

joemull commented Apr 24, 2025

@ajrbyers the change to the credit role display you requested is done now.

Also forgot to mention yesterday that I could not reproduce the error you found with updating your affiliations from ORCID. It works by querying the public ORCID API using a cleaned version of the ORCID of the request user. Anything you were doing that would trip that up?

Obviously in the editor UI I'll have to make it possible for an editor to bulk update someone else's affils, and to do so for a frozen author.

@joemull joemull assigned ajrbyers and unassigned joemull Apr 24, 2025
Copy link
Member

@ajrbyers ajrbyers left a comment

Choose a reason for hiding this comment

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

Thanks for this - looks good. Just one final thing from me: when you edit your own details although the next querystring is set there is no way to get back from the edit profile page.

@ajrbyers ajrbyers assigned joemull and unassigned ajrbyers Apr 24, 2025
@joemull joemull requested a review from ajrbyers April 24, 2025 08:52
@joemull joemull assigned ajrbyers and unassigned joemull Apr 24, 2025
@ajrbyers ajrbyers requested a review from mauromsl April 24, 2025 09:07
@ajrbyers ajrbyers assigned mauromsl and unassigned ajrbyers Apr 24, 2025
Copy link
Member

@mauromsl mauromsl left a comment

Choose a reason for hiding this comment

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

Added a few comments around the changes to the orcid integration and potential impact over authentication. Happy to follow your leads in regards to interface and flow

Comment on lines +1061 to +1063
int((orcid_end.get('year', {}) or {}).get('value', 1)),
int((orcid_end.get('month', {}) or {}).get('value', 1)),
int((orcid_end.get('day', {}) or {}).get('value', 1)),
Copy link
Member

Choose a reason for hiding this comment

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

do we need to handle the case of explicit None like for the other cases? (e.g {'year': {'value': None}})

# that matches an email address in a public ORCiD record.
if not new_author and emails:
for email in emails:
candidates = core_models.Account.objects.filter(email=email)
Copy link
Member

Choose a reason for hiding this comment

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

emails are unique, why do we avoid a .get() call

for email in emails:
candidates = core_models.Account.objects.filter(email=email)
if candidates.exists():
candidates.update(orcid=cleaned_orcid)
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to do this? A user could have multiple accounts in Janeway. A broad update like this could lead to multiple of the user accounts to be associated to the same ORCID, which can cause problems during authentication. We should either

  1. Avoid tampering accounts of co-authors
  2. Make orcid unique
  3. Tweak our authentication so that it can gracefully handle multiple accounts with the same ORCID

I suspect 3. is the ideal solution whilst 1. might be what we want now given time constraints.

There is also the fact that we are trusting ORCID to confirm and validate public email addresses. An attacker that can list a third party email address on their public orcid profile would be able to steal the same account in Janeway using this method.

Copy link
Member Author

@joemull joemull Apr 24, 2025

Choose a reason for hiding this comment

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

Yeah, it's a good question.

I think the best option is to check for an account that already has that ORCID in JW. If it exists, use it. This function should be doing that anyway. It would avoid a situation where multiple accounts are affiliated with the same ORCID.

If there is no match, then I still do think we should check the ORCID record's email addresses against email addresses in Janeway, and update the first account found with the ORCID.

The reason is: this is what we do during ORCID login--I was replicating the behavior we have already used for a few years there:

janeway/src/core/views.py

Lines 180 to 185 in e73827d

for email in orcid_details.get("emails"):
candidates = models.Account.objects.filter(email=email)
if candidates.exists():
# Store ORCID for future authentication requests
candidates.update(orcid=orcid_id)
login(request, candidates.first())

If we don't link existing accounts, it increases the likelihood of duplicate accounts, which is a big headache for usability. The alternative I see is to use frozen authors only for co-authors during this step, as you suggest below, but that opens a few cans of worms. See my response below on that.

As for the security risk you mention, yes, we are trusting ORCID. They say: "Unverified emails do not display in the public view of the record and only email addresses that have been verified will appear in API results."

If we don't want to trust them, we need to change the login with ORCID flow as well, because the same risk applies there. Do you want to do that?

Comment on lines +333 to +334
def add_author_from_search(search_term, request, article):
query = Q(email=search_term)
Copy link
Member

Choose a reason for hiding this comment

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

for my own sanity, can we split this function? There are just too many branches. At a first step we want to check if the search term is either an Orcid, an email or something else. Then we delegate to add_author_via_email and add_author_via_orcid

Also try to keep all updates to a record in the same area of the codebase.

form.cleaned_data.get('orcid'),
'0000-0003-2126-266X',
)
clean_orcid_id('Mauro-sfak-orci-dtst')
Copy link
Member

Choose a reason for hiding this comment

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

🕺

Comment on lines +95 to +98
if not affils:
affils.extend(
[affil for affil in summary["educations"]["education-summary"]]
)
Copy link
Member

Choose a reason for hiding this comment

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

It makes sense for get_affiliations to return these, but I think we are adding them automatically to the user when they are added via orcid, do we want that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I was replicating the logic we used to use for a single affiliation. Do you think it would be unexpected for all your education affils to be pulled through if you are a student? Too much data? Get just the last one?

Copy link
Member

Choose a reason for hiding this comment

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

@ajrbyers thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I think grabbing the latest one is okay. From the if not affils I presume it only runs when there are no other affiliations to get.

if emails:
email = emails[0]
else:
email = generate_dummy_email(orcid_details)
Copy link
Member

@mauromsl mauromsl Apr 24, 2025

Choose a reason for hiding this comment

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

We are not flagging the account as active, so they won't be able to login with orcid.
And the email address is fake, so they won't be able to activate it either. I reckon for these authors (no email address) we should NOT create an account, only a FrozenAuthor record.

Copy link
Member

@mauromsl mauromsl left a comment

Choose a reason for hiding this comment

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

.

@mauromsl mauromsl assigned joemull and unassigned mauromsl Apr 24, 2025
@joemull
Copy link
Member Author

joemull commented Apr 24, 2025

Thanks @mauromsl, see my comments above.

GitHub didn't give me a "Reply" text box on your last comment for some reason, so I'll reply here:

We are not flagging the account as active, so they won't be able to login with orcid.
And the email address is fake, so they won't be able to login either.

Yeah, that is a good point. I had missed that it's the ORCID registration view in core, not the login one, that makes them active:

janeway/src/core/views.py

Lines 336 to 340 in e73827d

# If the email matches the user email on ORCID, log them in
if new_user.email == initial.get("email"):
new_user.is_active = True
new_user.save()
login(request, new_user)

I reckon for these authors (no email address) we should NOT create an account, only a FrozenAuthor record.

As for whether to create frozen authors here, I originally was headed in that direction, because I fundamentally don't like the feature of Janeway where a non-trusted party can create an account for someone else. But if we were to use frozen authors here, that raises all kinds of questions about how you'd re-link records to authors, and the view during author submission would have to handle the user's expectations about their profile data matching their frozen author data. Let's discuss offline.

@joemull joemull requested a review from mauromsl April 24, 2025 11:30
@joemull joemull assigned mauromsl and unassigned joemull Apr 24, 2025
@joemull
Copy link
Member Author

joemull commented Apr 24, 2025

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.

Update the author submission screen Allow adding authors by ORCID (alone) Wiki update ORCID Information
3 participants