From a02668003ddf32c1b16f3e5a213396e6bc9bbfe4 Mon Sep 17 00:00:00 2001 From: Filipe Dias Lewandowski Date: Wed, 17 Apr 2024 16:03:55 +0200 Subject: [PATCH 1/3] Closes #2425: Adding ORCID and affiliation ROR identifiers to the user profile (#2449) * Closes #2425: Adding ORCID and affiliation ROR identifiers to the user profile * Deduplicate validation/suggestion code * review comments --- .../persistence/user/AuthenticatedUser.java | 26 ++++++- .../user/AuthenticatedUserDisplayInfo.java | 34 ++++++-- .../user/RoleAssigneeDisplayInfo.java | 22 ++++-- .../src/main/resources/Bundle_en.properties | 10 +++ .../src/main/resources/Bundle_pl.properties | 10 +++ .../db/migration/V72__addUserORCIDAndROR.sql | 1 + .../user/AuthenticatedUserTest.java | 12 ++- dataverse-webapp/pom.xml | 12 +++ .../authorization/EditableAccountField.java | 4 +- .../EditableAccountFieldSets.java | 3 +- .../providers/builtin/DataverseUserPage.java | 3 +- .../providers/common/BaseUserPage.java | 64 +++++++++++++++ .../common/ExternalIdpFirstLoginPage.java | 6 +- .../dataverse/validation/OrcidValidator.java | 49 ++++++++++++ .../{field/validators => }/RorValidator.java | 30 +++----- .../validation/field/ValidationResult.java | 27 +++++-- .../field/validators/RorFieldValidator.java | 39 ++++++++++ .../src/main/webapp/dataverseuser.xhtml | 77 +++++++++++++++++++ .../src/main/webapp/firstLogin.xhtml | 48 ++++++++++++ .../AuthenticatedUserDisplayInfoTest.java | 9 ++- .../mocks/MockAuthenticatedUser.java | 4 +- .../validation/OrcidValidatorTest.java | 32 ++++++++ .../validation/RorValidatorTest.java | 39 ++++++++++ .../validators/RorFieldValidatorTest.java | 65 ++++++++++++++++ .../field/validators/RorValidatorTest.java | 47 ----------- 25 files changed, 575 insertions(+), 98 deletions(-) create mode 100644 dataverse-persistence/src/main/resources/db/migration/V72__addUserORCIDAndROR.sql create mode 100644 dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/BaseUserPage.java create mode 100644 dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/OrcidValidator.java rename dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/{field/validators => }/RorValidator.java (62%) create mode 100644 dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorFieldValidator.java create mode 100644 dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/validation/OrcidValidatorTest.java create mode 100644 dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/validation/RorValidatorTest.java create mode 100644 dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/validation/field/validators/RorFieldValidatorTest.java delete mode 100644 dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/validation/field/validators/RorValidatorTest.java diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUser.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUser.java index cbc82dbef0..94a7e63f70 100644 --- a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUser.java +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUser.java @@ -83,7 +83,9 @@ public class AuthenticatedUser implements User, Serializable, JpaEntity { @NotNull @Column(nullable = false, unique = true) private String email; + private String orcid; private String affiliation; + private String affiliationROR; private String position; @NotBlank(message = "{user.lastName}") @@ -148,10 +150,18 @@ public String getEmail() { return email; } + public String getOrcid() { + return orcid; + } + public String getAffiliation() { return affiliation; } + public String getAffiliationROR() { + return affiliationROR; + } + public String getPosition() { return position; } @@ -214,7 +224,7 @@ public String getIdentifier() { @Override public AuthenticatedUserDisplayInfo getDisplayInfo() { - return new AuthenticatedUserDisplayInfo(firstName, lastName, email, affiliation, position); + return new AuthenticatedUserDisplayInfo(firstName, lastName, email, orcid, affiliation, affiliationROR, position); } /** @@ -233,6 +243,12 @@ public void applyDisplayInfo(AuthenticatedUserDisplayInfo inf) { if (StringUtils.isNotBlank(inf.getPosition())) { setPosition(inf.getPosition()); } + if (StringUtils.isNotBlank(inf.getOrcid())) { + setOrcid(inf.getOrcid()); + } + if (StringUtils.isNotBlank(inf.getAffiliationROR())) { + setAffiliationROR(inf.getAffiliationROR()); + } } @Override @@ -277,10 +293,18 @@ public void setEmail(String email) { this.email = email.trim(); } + public void setOrcid(String orcid) { + this.orcid = orcid; + } + public void setAffiliation(String affiliation) { this.affiliation = affiliation; } + public void setAffiliationROR(String affiliationROR) { + this.affiliationROR = affiliationROR; + } + public void setPosition(String position) { this.position = position; } diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserDisplayInfo.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserDisplayInfo.java index 68cdf59432..8f4b65edef 100644 --- a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserDisplayInfo.java +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserDisplayInfo.java @@ -14,23 +14,35 @@ public class AuthenticatedUserDisplayInfo extends RoleAssigneeDisplayInfo { @NotBlank(message = "{user.firstName}") private String firstName; private String position; + private String orcid; /* * @todo Shouldn't we persist the displayName too? It still exists on the * authenticateduser table. */ - public AuthenticatedUserDisplayInfo(String firstName, String lastName, String emailAddress, String affiliation, String position) { - super(firstName + " " + lastName, emailAddress, affiliation); + public AuthenticatedUserDisplayInfo(String firstName, String lastName, String emailAddress, + String affiliation, String position) { + super(firstName + " " + lastName, emailAddress, affiliation, null); this.firstName = firstName; this.lastName = lastName; this.position = position; } + public AuthenticatedUserDisplayInfo(String firstName, String lastName, String emailAddress, String orcid, + String affiliation, String affiliationROR, String position) { + super(firstName + " " + lastName, emailAddress, affiliation, affiliationROR); + this.firstName = firstName; + this.lastName = lastName; + this.position = position; + this.orcid = orcid; + } + public AuthenticatedUserDisplayInfo() { - super("", "", ""); + super("", "", "", ""); firstName = ""; lastName = ""; position = ""; + orcid = ""; } @@ -40,7 +52,8 @@ public AuthenticatedUserDisplayInfo() { * @param src the display info {@code this} will be a copy of. */ public AuthenticatedUserDisplayInfo(AuthenticatedUserDisplayInfo src) { - this(src.getFirstName(), src.getLastName(), src.getEmailAddress(), src.getAffiliation(), src.getPosition()); + this(src.getFirstName(), src.getLastName(), src.getEmailAddress(), src.getOrcid(), src.getAffiliation(), + src.getAffiliationROR(), src.getPosition()); } public String getLastName() { @@ -67,9 +80,18 @@ public void setPosition(String position) { this.position = position; } + public String getOrcid() { + return orcid; + } + + public void setOrcid(String orcid) { + this.orcid = orcid; + } + @Override public String toString() { - return "AuthenticatedUserDisplayInfo{firstName=" + firstName + ", lastName=" + lastName + ", position=" + position + ", email=" + getEmailAddress() + '}'; + return "AuthenticatedUserDisplayInfo{firstName=" + firstName + ", lastName=" + lastName + + ", position=" + position + ", email=" + getEmailAddress() + ", orcid=" + getOrcid() + '}'; } @Override @@ -97,7 +119,7 @@ public boolean equals(Object obj) { if (!Objects.equals(this.firstName, other.firstName)) { return false; } - return Objects.equals(this.position, other.position) && super.equals(obj); + return Objects.equals(this.position, other.position) && Objects.equals(this.orcid, other.orcid) && super.equals(obj); } } diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/RoleAssigneeDisplayInfo.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/RoleAssigneeDisplayInfo.java index 8975b57796..4177fb195b 100644 --- a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/RoleAssigneeDisplayInfo.java +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/RoleAssigneeDisplayInfo.java @@ -13,15 +13,17 @@ public class RoleAssigneeDisplayInfo implements java.io.Serializable { private String title; private String emailAddress; private String affiliation; + private String affiliationROR; public RoleAssigneeDisplayInfo(String title, String emailAddress) { - this(title, emailAddress, null); + this(title, emailAddress, null, null); } - public RoleAssigneeDisplayInfo(String title, String emailAddress, String anAffiliation) { + public RoleAssigneeDisplayInfo(String title, String emailAddress, String anAffiliation, String affiliationROR) { this.title = title; this.emailAddress = emailAddress; - affiliation = anAffiliation; + this.affiliation = anAffiliation; + this.affiliationROR = affiliationROR; } public String getTitle() { @@ -36,6 +38,10 @@ public String getAffiliation() { return affiliation; } + public String getAffiliationROR() { + return affiliationROR; + } + public void setTitle(String title) { this.title = title; } @@ -48,9 +54,14 @@ public void setAffiliation(String affiliation) { this.affiliation = affiliation; } + public void setAffiliationROR(String affiliationROR) { + this.affiliationROR = affiliationROR; + } + @Override public String toString() { - return "RoleAssigneeDisplayInfo{" + "title=" + title + ", emailAddress=" + emailAddress + ", affiliation=" + affiliation + '}'; + return "RoleAssigneeDisplayInfo{" + "title=" + title + ", emailAddress=" + emailAddress + + ", affiliation=" + affiliation + ", affiliationROR=" + affiliationROR + '}'; } @Override @@ -78,7 +89,8 @@ public boolean equals(Object obj) { if (!Objects.equals(this.emailAddress, other.emailAddress)) { return false; } - return Objects.equals(this.affiliation, other.affiliation); + return Objects.equals(this.affiliation, other.affiliation) + && Objects.equals(this.affiliationROR, other.affiliationROR); } } diff --git a/dataverse-persistence/src/main/resources/Bundle_en.properties b/dataverse-persistence/src/main/resources/Bundle_en.properties index f095fb13ab..98ca5c4b87 100755 --- a/dataverse-persistence/src/main/resources/Bundle_en.properties +++ b/dataverse-persistence/src/main/resources/Bundle_en.properties @@ -331,7 +331,17 @@ user.lastName=Family Name user.lastName.tip=The last name you would like to use for this account. user.email.tip=A valid email address you have access to in order to be contacted. user.email.taken=This email address is already taken. +user.orcid=ORCID +user.orcid.tip=ORCID is an open digital identifier allowing distinction between researchers having the same or similar name and surname. +user.orcid.invalid.format=Invalid ORCID format. +user.orcid.invalid.checksum=Invalid ORCID checksum. user.affiliation.tip=The organization with which you are affiliated. +user.affiliationror=Affiliation ROR +user.affiliationror.tip=ROR identifier of the affiliating institution. The identifier must be provided in its URL form, for instance "https://ror.org/039bjqg32" for the University of Warsaw. You can also enter the name of the institution and select its ROR ID from the list. +user.affiliationror.suggestionDisplay.valueHeader=ROR +user.affiliationror.suggestionDisplay.detailsHeader=Institution +user.affiliationror.ror.invalid.format=Invalid ROR format. +user.affiliationror.ror.invalid.checksum=Invalid ROR checksum. user.position=Position user.position.tip=Your role or title at the organization you are affiliated with; such as staff, faculty, student, etc. user.notificationsLanguage=E-mail notifications language diff --git a/dataverse-persistence/src/main/resources/Bundle_pl.properties b/dataverse-persistence/src/main/resources/Bundle_pl.properties index 7d9482f13c..de0a92eb4f 100644 --- a/dataverse-persistence/src/main/resources/Bundle_pl.properties +++ b/dataverse-persistence/src/main/resources/Bundle_pl.properties @@ -331,7 +331,17 @@ user.lastName=Nazwisko user.lastName.tip=Nazwisko, kt\u00F3re chcesz u\u017Cy\u0107 dla tego konta. user.email.tip=Aktualny adres e-mail, do kt\u00F3rego posiadasz dost\u0119p i kt\u00F3rego mo\u017Cna u\u017Cy\u0107, by si\u0119 z Tob\u0105 skontaktowa\u0107. user.email.taken=Ten adres e-mail jest ju\u017C przypisany do innego konta. +user.orcid=ORCID +user.orcid.tip=ORCID to otwarty identyfikator cyfrowy umo\u017Cliwiaj\u0105cy rozr\u00F3\u017Cnienie naukowc\u00F3w o tym samym lub podobnym imieniu i nazwisku. +user.orcid.invalid.format=Nieprawid\u0142owo zbudowany identyfikator. +user.orcid.invalid.checksum=Nieprawid\u0142owy identyfikator. user.affiliation.tip=Instytucja przy kt\u00F3rej jeste\u015B afiliowana/afiliowany. +user.affiliationror=ROR instytucji afiliuj\u0105cej +user.affiliationror.tip=Identyfikator ROR instytucji afiliuj\u0105cej. Identyfikator musi zosta\u0107 podany w formie adresu URL, np. "https://ror.org/039bjqg32" dla Uniwersytetu Warszawskiego. W to pole mo\u017Cesz r\u00F3wnie\u017C wprowadzi\u0107 nazw\u0119 instytucji i wybra\u0107 jej identyfikator ROR z listy. +user.affiliationror.suggestionDisplay.valueHeader=ROR +user.affiliationror.suggestionDisplay.detailsHeader=Instytucja +user.affiliationror.ror.invalid.format=Nieprawid\u0142owo zbudowany identyfikator ROR. +user.affiliationror.ror.invalid.checksum=Nieprawid\u0142owy identyfikator ROR. user.position=Stanowisko user.position.tip=Stanowisko lub rola jak\u0105 pe\u0142nisz w instytucji, przy kt\u00F3rej jeste\u015B afiliowana/afiliowany, na przyk\u0142ad "pracownik naukowy", "student", "pracownik administracyjny" itp. user.notificationsLanguage=J\u0119zyk powiadomie\u0144 e-mailowych diff --git a/dataverse-persistence/src/main/resources/db/migration/V72__addUserORCIDAndROR.sql b/dataverse-persistence/src/main/resources/db/migration/V72__addUserORCIDAndROR.sql new file mode 100644 index 0000000000..4822df480e --- /dev/null +++ b/dataverse-persistence/src/main/resources/db/migration/V72__addUserORCIDAndROR.sql @@ -0,0 +1 @@ +ALTER TABLE authenticateduser ADD COLUMN orcid TEXT, ADD COLUMN affiliationror TEXT; diff --git a/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserTest.java b/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserTest.java index 87f45622d8..0ef2bd15d1 100644 --- a/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserTest.java +++ b/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/AuthenticatedUserTest.java @@ -45,7 +45,8 @@ public void testGetIdentifier() { @Test public void testApplyDisplayInfo() { System.out.println("applyDisplayInfo"); - AuthenticatedUserDisplayInfo inf = new AuthenticatedUserDisplayInfo("Homer", "Simpson", "Homer.Simpson@someU.edu", "UnitTester", "In-Memory user"); + AuthenticatedUserDisplayInfo inf = new AuthenticatedUserDisplayInfo("Homer", "Simpson", "Homer.Simpson@someU.edu", + "0000-0001-2345-6789", "UnitTester", "https://ror.org/04k0tth05", "In-Memory user"); testUser.applyDisplayInfo(inf); assertEquals(inf, testUser.getDisplayInfo()); } @@ -53,8 +54,15 @@ public void testApplyDisplayInfo() { @Test public void testGetDisplayInfo() { System.out.println("getDisplayInfo"); - AuthenticatedUserDisplayInfo expResult = new AuthenticatedUserDisplayInfo("Homer", "Simpson", "Homer.Simpson@someU.edu", "UnitTester", "In-Memory user"); + // given + testUser.setOrcid("0000-0001-2345-6789"); + testUser.setAffiliationROR("https://ror.org/04k0tth05"); + AuthenticatedUserDisplayInfo expResult = new AuthenticatedUserDisplayInfo("Homer", "Simpson", "Homer.Simpson@someU.edu", + "0000-0001-2345-6789", "UnitTester", "https://ror.org/04k0tth05", "In-Memory user"); + // when AuthenticatedUserDisplayInfo result = testUser.getDisplayInfo(); + + // then assertEquals(expResult, result); } diff --git a/dataverse-webapp/pom.xml b/dataverse-webapp/pom.xml index 362fed94ec..43dd125548 100644 --- a/dataverse-webapp/pom.xml +++ b/dataverse-webapp/pom.xml @@ -45,6 +45,12 @@ org.apache.httpcomponents fluent-hc + + + commons-logging + commons-logging + + org.apache.httpcomponents @@ -431,6 +437,12 @@ com.onelogin java-saml 2.8.0 + + + org.apache.santuario + xmlsec + + diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountField.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountField.java index 336df4eb9b..44fbb8e642 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountField.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountField.java @@ -9,6 +9,8 @@ public enum EditableAccountField { FAMILY_NAME, EMAIL, AFFILIATION, + AFFILIATION_ROR, POSITION, - NOTIFICATIONS_LANG + NOTIFICATIONS_LANG, + ORCID } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountFieldSets.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountFieldSets.java index 930b2e8bd9..780659d69f 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountFieldSets.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/EditableAccountFieldSets.java @@ -33,7 +33,8 @@ public static Set createAllFieldsSet() { public static Set createSecondaryFieldsSet() { return Collections.unmodifiableSet( - Stream.of(EditableAccountField.AFFILIATION, EditableAccountField.POSITION, EditableAccountField.NOTIFICATIONS_LANG) + Stream.of(EditableAccountField.AFFILIATION, EditableAccountField.POSITION, EditableAccountField.NOTIFICATIONS_LANG, + EditableAccountField.ORCID, EditableAccountField.AFFILIATION_ROR) .collect(Collectors.toSet())); } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index f90f39aaf0..546081781c 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -8,6 +8,7 @@ import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.EditableAccountField; import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier; +import edu.harvard.iq.dataverse.authorization.providers.common.BaseUserPage; import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider; import edu.harvard.iq.dataverse.common.BundleUtil; import edu.harvard.iq.dataverse.consent.ConsentDto; @@ -60,7 +61,7 @@ @ViewScoped @Named("DataverseUserPage") -public class DataverseUserPage implements java.io.Serializable { +public class DataverseUserPage extends BaseUserPage { private static final Logger logger = Logger.getLogger(DataverseUserPage.class.getCanonicalName()); diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/BaseUserPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/BaseUserPage.java new file mode 100644 index 0000000000..7d36658f67 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/BaseUserPage.java @@ -0,0 +1,64 @@ +package edu.harvard.iq.dataverse.authorization.providers.common; + +import edu.harvard.iq.dataverse.common.BundleUtil; +import edu.harvard.iq.dataverse.dataset.metadata.inputRenderer.Suggestion; +import edu.harvard.iq.dataverse.dataset.metadata.inputRenderer.suggestion.SuggestionHandler; +import edu.harvard.iq.dataverse.validation.OrcidValidator; +import edu.harvard.iq.dataverse.validation.RorValidator; +import edu.harvard.iq.dataverse.validation.field.ValidationResult; +import org.apache.commons.lang3.StringUtils; + +import javax.ejb.EJB; +import javax.faces.application.FacesMessage; +import javax.faces.component.UIComponent; +import javax.faces.component.UIInput; +import javax.faces.context.FacesContext; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +/** + * Provides common functionality for the user account details page. + */ +public abstract class BaseUserPage implements Serializable { + + @EJB + RorValidator rorValidator; + + @EJB + OrcidValidator orcidValidator; + + @EJB(beanName = "RorSuggestionHandler") + SuggestionHandler suggestionHandler; + + public void validateOrcid(FacesContext context, UIComponent toValidate, Object value) { + String orcid = (String) value; + if (StringUtils.isEmpty(orcid)) { + return; + } + ValidationResult result = orcidValidator.validate(orcid); + if (!result.isOk()) { + ((UIInput) toValidate).setValid(false); + context.addMessage(toValidate.getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, + BundleUtil.getStringFromBundle("user." + result.getErrorCode()), null)); + } + } + + public void validateAffiliationRor(FacesContext context, UIComponent toValidate, Object value) { + String ror = (String) value; + if (StringUtils.isEmpty(ror)) { + return; + } + + ValidationResult result = rorValidator.validate(ror); + if (!result.isOk()) { + ((UIInput) toValidate).setValid(false); + context.addMessage(toValidate.getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, + BundleUtil.getStringFromBundle("user.affiliationror." + result.getErrorCode()), null)); + } + } + + public List processAffiliationRorSuggestions(String query) { + return suggestionHandler.generateSuggestions(Collections.emptyMap(), query); + } +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/ExternalIdpFirstLoginPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/ExternalIdpFirstLoginPage.java index 9414024fee..31896f9f97 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/ExternalIdpFirstLoginPage.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/common/ExternalIdpFirstLoginPage.java @@ -43,7 +43,6 @@ import javax.inject.Named; import javax.servlet.http.HttpSession; import java.io.IOException; -import java.io.Serializable; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; @@ -65,7 +64,7 @@ */ @Named("ExternalIdpFirstLoginPage") @SessionScoped -public class ExternalIdpFirstLoginPage implements Serializable { +public class ExternalIdpFirstLoginPage extends BaseUserPage { private static final Logger logger = Logger.getLogger(ExternalIdpFirstLoginPage.class.getCanonicalName()); @@ -235,7 +234,8 @@ public String init() throws IOException { public String createNewAccount() { AuthenticatedUserDisplayInfo displayInfo = newUser.getDisplayInfo(); AuthenticatedUserDisplayInfo newAuthenticatedUserDisplayInfo = new AuthenticatedUserDisplayInfo( - displayInfo.getFirstName(), displayInfo.getLastName(), getSelectedEmail(), displayInfo.getAffiliation(), + displayInfo.getFirstName(), displayInfo.getLastName(), getSelectedEmail(), displayInfo.getOrcid(), + displayInfo.getAffiliation(), displayInfo.getAffiliationROR(), displayInfo.getPosition()); final AuthenticatedUser user = authenticationSvc.createAuthenticatedUser(newUser.toUserRecordIdentifier(), diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/OrcidValidator.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/OrcidValidator.java new file mode 100644 index 0000000000..f398e4c2cd --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/OrcidValidator.java @@ -0,0 +1,49 @@ +package edu.harvard.iq.dataverse.validation; + +import edu.harvard.iq.dataverse.validation.field.ValidationResult; +import org.apache.commons.lang3.StringUtils; + +import javax.ejb.Stateless; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Stateless +public class OrcidValidator { + + public static final String INVALID_FORMAT_ERROR_CODE = "orcid.invalid.format"; + public static final String INVALID_CHECKSUM_ERROR_CODE = "orcid.invalid.checksum"; + + private static final Pattern ORCID_FORMAT_PATTERN = Pattern.compile("([0-9]{4})-([0-9]{4})-([0-9]{4})-([0-9]{3})([0-9X])"); + + // -------------------- LOGIC -------------------- + + public ValidationResult validate(String orcid) { + if (StringUtils.isEmpty(orcid)) { + return ValidationResult.invalid(INVALID_FORMAT_ERROR_CODE); + } + + Matcher matcher = ORCID_FORMAT_PATTERN.matcher(orcid); + if (StringUtils.isBlank(orcid) || !matcher.matches()) { + return ValidationResult.invalid(INVALID_FORMAT_ERROR_CODE); + } + + String encoded = matcher.group(1) + matcher.group(2) + matcher.group(3) + matcher.group(4); + String checksum = matcher.group(5); + + return checksum.equals(computeChecksum(encoded)) + ? ValidationResult.ok() + : ValidationResult.invalid(INVALID_CHECKSUM_ERROR_CODE); + } + + // -------------------- PRIVATE -------------------- + + public String computeChecksum(String baseDigits) { + int total = Arrays.stream(baseDigits.split("")) + .mapToInt(Integer::parseInt) + .reduce(0, (accumulated, digit) -> (accumulated + digit) * 2); + + int result = (12 - total % 11) % 11; + return result == 10 ? "X" : String.valueOf(result); + } +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorValidator.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/RorValidator.java similarity index 62% rename from dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorValidator.java rename to dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/RorValidator.java index 7c64f6f7de..656eaa9855 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorValidator.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/RorValidator.java @@ -1,46 +1,34 @@ -package edu.harvard.iq.dataverse.validation.field.validators; +package edu.harvard.iq.dataverse.validation; -import edu.harvard.iq.dataverse.common.BundleUtil; -import edu.harvard.iq.dataverse.persistence.dataset.ValidatableField; import edu.harvard.iq.dataverse.validation.field.ValidationResult; import org.apache.commons.lang3.StringUtils; -import org.omnifaces.cdi.Eager; -import javax.enterprise.context.ApplicationScoped; +import javax.ejb.Stateless; import java.util.Arrays; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -@Eager -@ApplicationScoped -public class RorValidator extends MultiValueValidatorBase { - +@Stateless +public class RorValidator { + public static final String INVALID_FORMAT_ERROR_CODE = "ror.invalid.format"; + public static final String INVALID_CHECKSUM_ERROR_CODE = "ror.invalid.checksum"; private static final Map DECODE_SYMBOL_VALUES = Initializer.initializeDecodeSymbolValues(); // -------------------- LOGIC -------------------- - @Override - public String getName() { - return "ror_validator"; - } - - @Override - public ValidationResult validateValue(String fullRor, ValidatableField field, Map params, - Map> fieldIndex) { - String fieldName = field.getDatasetFieldType().getDisplayName(); + public ValidationResult validate(String fullRor) { if (StringUtils.isBlank(fullRor) || !fullRor.matches("https://ror\\.org/0[a-hjkmnp-tv-z0-9]{6}[0-9]{2}")) { - return ValidationResult.invalid(field, BundleUtil.getStringFromBundle("ror.invalid.format", fieldName)); + return ValidationResult.invalid(INVALID_FORMAT_ERROR_CODE); } String value = fullRor.substring(fullRor.lastIndexOf("/") + 1); String encoded = value.substring(0, 7); long checksum = Long.parseLong(value.substring(7)); return checksum == computeChecksum(encoded) ? ValidationResult.ok() - : ValidationResult.invalid(field, BundleUtil.getStringFromBundle("ror.invalid.checksum", fieldName)); + : ValidationResult.invalid(INVALID_CHECKSUM_ERROR_CODE); } // -------------------- PRIVATE -------------------- diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/ValidationResult.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/ValidationResult.java index f43102a15e..1285e54263 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/ValidationResult.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/ValidationResult.java @@ -4,16 +4,19 @@ import org.apache.commons.lang3.StringUtils; public class ValidationResult { - public static ValidationResult OK = new ValidationResult(true,null, StringUtils.EMPTY); - private boolean ok; - private ValidatableField field; - private String message; + public static ValidationResult OK = new ValidationResult(true, null, null, StringUtils.EMPTY); + + private final boolean ok; + private final String errorCode; + private final ValidatableField field; + private final String message; // -------------------- CONSTRUCTORS -------------------- - private ValidationResult(boolean ok, ValidatableField field, String validationMessage) { + private ValidationResult(boolean ok, String errorCode, ValidatableField field, String validationMessage) { this.ok = ok; + this.errorCode = errorCode; this.field = field; this.message = validationMessage; } @@ -28,14 +31,26 @@ public String getMessage() { return message; } + public String getErrorCode() { + return errorCode; + } + // -------------------- LOGIC -------------------- + public ValidationResult withInfo(ValidatableField field, String validationMessage) { + return new ValidationResult(ok, errorCode, field, validationMessage); + } + public static ValidationResult ok() { return OK; } public static ValidationResult invalid(ValidatableField field, String message) { - return new ValidationResult(false, field, message); + return new ValidationResult(false, null, field, message); + } + + public static ValidationResult invalid(String errorCode) { + return new ValidationResult(false, errorCode, null, StringUtils.EMPTY); } public boolean isOk() { diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorFieldValidator.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorFieldValidator.java new file mode 100644 index 0000000000..8c9dc36546 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/validation/field/validators/RorFieldValidator.java @@ -0,0 +1,39 @@ +package edu.harvard.iq.dataverse.validation.field.validators; + +import edu.harvard.iq.dataverse.common.BundleUtil; +import edu.harvard.iq.dataverse.persistence.dataset.ValidatableField; +import edu.harvard.iq.dataverse.validation.RorValidator; +import edu.harvard.iq.dataverse.validation.field.ValidationResult; +import org.omnifaces.cdi.Eager; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.util.List; +import java.util.Map; + +@Eager +@ApplicationScoped +public class RorFieldValidator extends MultiValueValidatorBase { + + @Inject + private RorValidator rorValidator; + + // -------------------- LOGIC -------------------- + + @Override + public String getName() { + return "ror_validator"; + } + + @Override + public ValidationResult validateValue(String fullRor, ValidatableField field, Map params, + Map> fieldIndex) { + String fieldName = field.getDatasetFieldType().getDisplayName(); + ValidationResult result = rorValidator.validate(fullRor); + if (result.isOk()) { + return result; + } else { + return result.withInfo(field, BundleUtil.getStringFromBundle(result.getErrorCode(), fieldName)); + } + } +} diff --git a/dataverse-webapp/src/main/webapp/dataverseuser.xhtml b/dataverse-webapp/src/main/webapp/dataverseuser.xhtml index 6da1236322..fbbc2e8f70 100644 --- a/dataverse-webapp/src/main/webapp/dataverseuser.xhtml +++ b/dataverse-webapp/src/main/webapp/dataverseuser.xhtml @@ -475,6 +475,19 @@

+ +
+ +
+

+ + + +

+
+
+ +
+ +
+

+ + + +

+
+
+
+ + +
+ + +
+
+
+ + +
+ + + + + + + + + + + + +
+
+
+ +
+ + +
+
+
+ + +
+ + + + + + + + + + + + +
+