Skip to content

Commit 366d7ac

Browse files
authored
Merge pull request #11222 from GlobalDataverseCommunityConsortium/ORCID_1
ORCID: Support authenticated ORCIDs in account profile
2 parents 031bf7c + 8d64462 commit 366d7ac

30 files changed

+478
-157
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Dataverse now includes improved integration with ORCID, supported through a grant to GDCC from the ([ORCID Global Participation Fund](https://info.orcid.org/global-participation-fund-announces-fourth-round-of-awardees/).)[https://info.orcid.org/global-participation-fund-announces-fourth-round-of-awardees/].
2+
3+
Specifically, Dataverse users can now link their Dataverse account with their ORCID profile which then allows Dataverse to use automatically add their ORCID to their author metadata when they create a dataset.
4+
5+
This functionality leverages Dataverse's existing support for login via ORCID, but can be turned on independently of it. If ORCID login is enabled, the user's ORCID will automatically be added to their profile. If the user has logged in via some other mechanism, they are able to click a button to initiate a similar authentication process in which the user must login to their ORCID account and approve the connection.
6+
7+
Feedback from installations that enable this functionality is requested and we expect that updates can be made in the next Dataverse release.
8+
9+
See also [the](https://dataverse-guide--11222.org.readthedocs.build/en/11222/user/account.html#linking-orcid-with-your-account-profile) [guides](https://dataverse-guide--11222.org.readthedocs.build/en/11222/installation/orcid.html), #7284, and #11222.

doc/sphinx-guides/source/installation/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ Installation Guide
2020
shibboleth
2121
oauth2
2222
oidc
23+
orcid
2324
external-tools
2425
advanced

doc/sphinx-guides/source/installation/oauth2.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ URLs to help you request a Client ID and Client Secret from the providers suppor
3838
Each of these providers will require the following information from you:
3939

4040
- Basic information about your Dataverse installation such as a name, description, URL, logo, privacy policy, etc.
41-
- OAuth2 Redirect URI (ORCID) or Redirect URI (Microsoft Azure AD) or Authorization Callback URL (GitHub) or Authorized Redirect URIs (Google): This is the URL on the Dataverse installation side to which the user will be sent after successfully authenticating with the identity provider. This should be the advertised URL of your Dataverse installation (the protocol, fully qualified domain name, and optional port configured via the ``dataverse.siteUrl`` JVM option mentioned in the :doc:`config` section) appended with ``/oauth2/callback.xhtml`` such as ``https://dataverse.example.edu/oauth2/callback.xhtml``.
41+
- OAuth2 Redirect URI(s) (ORCID) or Redirect URI (Microsoft Azure AD) or Authorization Callback URL (GitHub) or Authorized Redirect URIs (Google): This is the URL on the Dataverse installation side to which the user will be sent after successfully authenticating with the identity provider. This should be the advertised URL of your Dataverse installation (the protocol, fully qualified domain name, and optional port configured via the ``dataverse.siteUrl`` JVM option mentioned in the :doc:`config` section) appended with ``/oauth2/callback.xhtml`` such as ``https://dataverse.example.edu/oauth2/callback.xhtml``.
4242

43+
For ORCID, if you also want to enable the ability to associate ORCIDs with user accounts (when users did not login via ORCID) as discussed in :doc:`orcid`, you must add a second redirect URL: to ``/oauth2/orcidConfirm.xhtml`` such as ``https://dataverse.example.edu/oauth2/orcidConfirm.xhtml``.
44+
4345
When you are finished you should have a Client ID and Client Secret from the provider. Keep them safe and secret.
4446

4547
Dataverse Installation Side
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
ORCID Integration
2+
=================
3+
4+
.. contents:: |toctitle|
5+
:local:
6+
7+
Introduction
8+
------------
9+
10+
Dataverse leverages ORCIDs (and other types of persistent identifiers (PIDs)) to improve the findability of data and to simplify the process of adding metadata.
11+
When ORCIDs are included as metadata about authors, Dataverse includes them in metadata exports, advertises them through :ref:`discovery-sign-posting` and via metadata embedded in dataset pages, and includes them in the metadata associated with dataset DOIs.
12+
13+
Dataverse can be configured to make it easier to include ORCIDs
14+
- via use of an ORCID "External Vocabulary Script" that allows users to lookup authors, depositors, etc. based on their ORCID profile metadata and then records these ORCIDs automatically and adds links to ORCID profiles in metadata displays. With this configured, there is no need enter ORCIDs directly. See :ref:`using-external-vocabulary-services` in the Admin Guide.
15+
- via association of ORCIDs with Dataverse user accounts, through the use of ORCID logins or, in addition or instead, a separate authenticated ORCID linking mechanism. When an ORCID is associated with a Dataverse account, it will automatically be added to the dataset metadata when a user creates a dataset and is added as an initial author.
16+
17+
See also :ref:`orcid-integration` in the User Guide.
18+
19+
Configuration
20+
--------------
21+
22+
The steps needed to configure Dataverse to support lookup of ORCIDs for the author metadata field (and ROR identifiers for organizations as author affiliations) is described in the `Dataverse Author Field Example page <https://github.com/gdcc/dataverse-external-vocab-support/blob/main/examples/authorIDandAffilationUsingORCIDandROR.md>`_ in the `Dataverse External Vocabulary Suport Github Repository <https://github.com/gdcc/dataverse-external-vocab-support>`_. Briefly, this involves changing the :ref:`:CVocConf` setting and potentially creating local web-acessible copies of the relevant scripts.
23+
24+
To configure Dataverse to support adding ORCIDs to user profiles, one must configure ORCID as an OAuth2 provider as described in :doc:`oauth2`. The ability to link ORCIDs to user accounts is automatically enabled if an ORCID provider is configured. To avoid also enabling ORCID login, the provider can be registered with "enabled":false.
25+
26+

doc/sphinx-guides/source/user/account.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,20 @@ Microsoft Azure AD, GitHub, and Google Log In
165165

166166
You can also convert your Dataverse installation account to use authentication provided by GitHub, Microsoft, or Google. These options may be found in the "Other options" section of the log in page, and function similarly to how ORCID is outlined above. If you would like to convert your account away from using one of these services for log in, then you can follow the same steps as listed above for converting away from the ORCID log in.
167167

168+
169+
.. _orcid-integration:
170+
171+
Linking ORCID with Your Account Profile
172+
---------------------------------------
173+
174+
If you login using ORCID, Dataverse will add the link to your ORCID account in your account profile and, when you create datasets, will automatically add you, with your ORCID, as an author.
175+
176+
If you login via other methods, you can add a link to your ORCID account as you create an account or later via the "Account Information" page.
177+
As when using ORCID login, you will be redirected to the ORCID website to log in there and allow the connection with Dataverse.
178+
Once you've done that, the link to your ORCID will be shown in the Account Information page and your ORCID will be added as your identifier when you create datasets (exactly the same as if you had logged in via ORCID).
179+
180+
Note that the ability to login via ORCID (or other providers) and the ability to link to your ORCID profile are separate configuration options :doc:`available </installation/orcid>` to Dataverse administrators.
181+
168182
.. _my-data:
169183

170184
My Data

src/main/java/edu/harvard/iq/dataverse/DatasetPage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2612,7 +2612,7 @@ private void resetVersionUI() {
26122612
}
26132613
}
26142614

2615-
String creatorOrcidId = au.getOrcidId();
2615+
String creatorOrcidId = au.getAuthenticatedOrcid();
26162616
if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.author) && dsf.isEmpty()) {
26172617
for (DatasetFieldCompoundValue authorValue : dsf.getDatasetFieldCompoundValues()) {
26182618
for (DatasetField subField : authorValue.getChildDatasetFields()) {

src/main/java/edu/harvard/iq/dataverse/authorization/AuthUtil.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
import edu.harvard.iq.dataverse.authorization.providers.builtin.DataverseUserPage;
44
import edu.harvard.iq.dataverse.authorization.providers.oauth2.AbstractOAuth2AuthenticationProvider;
55
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider;
6+
import edu.harvard.iq.dataverse.util.SystemConfig;
7+
68
import java.util.Collection;
79
import java.util.logging.Logger;
810

911
public class AuthUtil {
1012

1113
private static final Logger logger = Logger.getLogger(DataverseUserPage.class.getCanonicalName());
1214

13-
public static boolean isNonLocalLoginEnabled(Collection<AuthenticationProvider> providers) {
15+
public static boolean isNonLocalSignupEnabled(Collection<AuthenticationProvider> providers, SystemConfig systemConfig) {
1416
if (providers != null) {
17+
1518
for (AuthenticationProvider provider : providers) {
1619
if (provider instanceof AbstractOAuth2AuthenticationProvider || provider instanceof ShibAuthenticationProvider) {
1720
logger.fine("found an remote auth provider (returning true): " + provider.getId());
18-
return true;
21+
if(!systemConfig.isSignupDisabledForRemoteAuthProvider(provider.getId())) {
22+
return true;
23+
}
1924
} else {
2025
logger.fine("not a remote auth provider: " + provider.getId());
2126
}

src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticatedUserDisplayInfo.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,29 @@ public class AuthenticatedUserDisplayInfo extends RoleAssigneeDisplayInfo {
1414
@NotBlank(message = "{user.firstName}")
1515
private String firstName;
1616
private String position;
17+
private String orcid;
1718

1819
/*
1920
* @todo Shouldn't we persist the displayName too? It still exists on the
2021
* authenticateduser table.
2122
*/
2223
public AuthenticatedUserDisplayInfo(String firstName, String lastName, String emailAddress, String affiliation, String position) {
24+
this(firstName, lastName, emailAddress, affiliation, position, null);
25+
}
26+
public AuthenticatedUserDisplayInfo(String firstName, String lastName, String emailAddress, String affiliation, String position, String orcid) {
2327
super(firstName + " " + lastName,emailAddress,affiliation);
2428
this.firstName = firstName;
2529
this.lastName = lastName;
26-
this.position = position;
30+
this.position = position;
31+
this.orcid = orcid;
2732
}
2833

2934
public AuthenticatedUserDisplayInfo() {
3035
super("","","");
3136
firstName="";
3237
lastName="";
3338
position="";
39+
orcid=null;
3440
}
3541

3642

@@ -39,7 +45,7 @@ public AuthenticatedUserDisplayInfo() {
3945
* @param src the display info {@code this} will be a copy of.
4046
*/
4147
public AuthenticatedUserDisplayInfo( AuthenticatedUserDisplayInfo src ) {
42-
this( src.getFirstName(), src.getLastName(), src.getEmailAddress(), src.getAffiliation(), src.getPosition());
48+
this( src.getFirstName(), src.getLastName(), src.getEmailAddress(), src.getAffiliation(), src.getPosition(), src.getOrcid());
4349
}
4450

4551
public String getLastName() {
@@ -98,6 +104,27 @@ public boolean equals(Object obj) {
98104
}
99105
return Objects.equals(this.position, other.position) && super.equals(obj);
100106
}
107+
108+
public void setOrcid(String orcidUrl) {
109+
this.orcid=orcidUrl;
110+
}
111+
112+
public String getOrcid() {
113+
return orcid;
114+
}
115+
116+
public String getOrcidForDisplay() {
117+
String orcidUrl = getOrcid();
118+
if(orcidUrl == null) {
119+
return null;
120+
}
121+
int index = orcidUrl.lastIndexOf('/');
122+
if (index > 0) {
123+
return orcidUrl.substring(index + 1);
124+
} else {
125+
return orcidUrl;
126+
}
127+
}
101128

102129
}
103130

src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationProvider.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,6 @@ public interface AuthenticationProvider {
3333
default boolean isUserInfoUpdateAllowed() { return false; };
3434
default boolean isUserDeletionAllowed() { return false; };
3535
default boolean isOAuthProvider() { return false; };
36-
/** @todo Consider moving some or all of these to AuthenticationProviderDisplayInfo.*/
37-
/** The identifier is only displayed in the UI if it's meaningful, such as an ORCID iD.*/
38-
default boolean isDisplayIdentifier() { return false; };
39-
/** ORCID calls their persistent id an "ORCID iD".*/
40-
default String getPersistentIdName() { return null; };
41-
/** ORCID has special language to describe their ID: http://members.orcid.org/logos-web-graphics */
42-
default String getPersistentIdDescription() { return null; };
43-
/** An ORCID example would be the "http://orcid.org/" part of http://orcid.org/0000-0002-7874-374X*/
44-
default String getPersistentIdUrlPrefix() { return null; };
45-
default String getLogo() { return null; };
4636

4737

4838

src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationProvidersRegistrationServiceBean.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
1616
import edu.harvard.iq.dataverse.authorization.providers.oauth2.AbstractOAuth2AuthenticationProvider;
1717
import edu.harvard.iq.dataverse.authorization.providers.oauth2.OAuth2AuthenticationProviderFactory;
18+
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP;
1819
import edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.OIDCAuthenticationProviderFactory;
1920
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProviderFactory;
2021
import edu.harvard.iq.dataverse.settings.JvmSettings;
@@ -87,6 +88,8 @@ public class AuthenticationProvidersRegistrationServiceBean {
8788

8889
@PersistenceContext(unitName = "VDCNet-ejbPU")
8990
private EntityManager em;
91+
92+
private AuthenticationProvider orcidProvider;
9093

9194
// does this method also need an explicit @Lock(WRITE)?
9295
// - I'm assuming not; since it's guaranteed to only be called once,
@@ -110,8 +113,9 @@ public void startup() {
110113
}
111114

112115
// Now, load the providers.
113-
em.createNamedQuery("AuthenticationProviderRow.findAllEnabled", AuthenticationProviderRow.class)
116+
em.createNamedQuery("AuthenticationProviderRow.findAll", AuthenticationProviderRow.class)
114117
.getResultList().forEach((row) -> {
118+
if(row.isEnabled()) {
115119
try {
116120
registerProvider( loadProvider(row) );
117121

@@ -120,9 +124,28 @@ public void startup() {
120124

121125
} catch (AuthorizationSetupException ex) {
122126
logger.log(Level.SEVERE, "Exception setting up the authentication provider '" + row.getId() + "': " + ex.getMessage(), ex);
127+
}
128+
} else {
129+
// We still use an ORCID provider that is not enabled for login as a way to
130+
// authenticate ORCIDs being added to account profiles
131+
Map<String, String> data = OAuth2AuthenticationProviderFactory
132+
.parseFactoryData(row.getFactoryData());
133+
if ("orcid".equals(data.get("type"))) {
134+
try {
135+
setOrcidProvider(loadProvider(row));
136+
} catch (Exception e) {
137+
logger.log(Level.SEVERE, "Cannot register ORCID provider '" + row.getId());
138+
}
139+
}
123140
}
124-
});
125-
141+
});
142+
// If there is an enabled ORCID provider, we'll still use that in preference to a disabled one (there should only be one but this would handle a case where, for example, someone has a disabled sandbox ORCID provider and a real enabled ORCID provider)
143+
// Could be changed in the future if there's a need for two different clients for login and adding ORCIDs to profiles
144+
for (AuthenticationProvider provider : authenticationProviders.values()) {
145+
if (provider instanceof OrcidOAuth2AP) {
146+
setOrcidProvider(provider);
147+
}
148+
}
126149
// Add providers registered via MPCONFIG
127150
if (JvmSettings.OIDC_ENABLED.lookupOptional(Boolean.class).orElse(false)) {
128151
try {
@@ -133,6 +156,15 @@ public void startup() {
133156
}
134157
}
135158

159+
private void setOrcidProvider(AuthenticationProvider provider) {
160+
orcidProvider = provider;
161+
162+
}
163+
164+
public AuthenticationProvider getOrcidProvider() {
165+
return orcidProvider;
166+
}
167+
136168
private void registerProviderFactory(AuthenticationProviderFactory aFactory)
137169
throws AuthorizationSetupException
138170
{

0 commit comments

Comments
 (0)