Skip to content

Commit

Permalink
Merge branch 'main' into lmendoza/9623-prod-professional-personal-ema…
Browse files Browse the repository at this point in the history
…il-detection-is-case-sensitive
  • Loading branch information
leomendoza123 authored Feb 11, 2025
2 parents 6eb6f0d + 57d9128 commit 8b494d9
Show file tree
Hide file tree
Showing 25 changed files with 459 additions and 60 deletions.
6 changes: 6 additions & 0 deletions .tx/config
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,9 @@ file_filter = orcid-core/src/main/resources/i18n/email_add_works_to_record_<lang
source_file = orcid-core/src/main/resources/i18n/email_add_works_to_record_en.properties
source_lang = en
type = UNICODEPROPERTIES

[o:orcid-inc-1:p:registry:r:papi_rate_limit_email]
file_filter = orcid-core/src/main/resources/i18n/papi_rate_limit_email_<lang>.properties
source_file = orcid-core/src/main/resources/i18n/papi_rate_limit_email_en.properties
source_lang = en
type = UNICODEPROPERTIES
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
## v2.71.9 - 2025-02-10

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.8...v2.71.9)

## v2.71.8 - 2025-02-10

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.7...v2.71.8)

## v2.71.7 - 2025-02-10

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.6...v2.71.7)

## v2.71.6 - 2025-02-06

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.5...v2.71.6)

## v2.71.5 - 2025-02-06

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.4...v2.71.5)

## v2.71.4 - 2025-02-06

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.3...v2.71.4)

- [#7206](https://github.com/ORCID/ORCID-Source/pull/7206): Revert "Revert "Update the cached token info so it indicate if the token is an OBO one""

## v2.71.3 - 2025-02-06

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.2...v2.71.3)

- [#7205](https://github.com/ORCID/ORCID-Source/pull/7205): Revert "Update the cached token info so it indicate if the token is an OBO one"

## v2.71.2 - 2025-02-05

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.1...v2.71.2)

## v2.71.1 - 2025-02-05

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.71.0...v2.71.1)

## v2.71.0 - 2025-02-05

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.70.16...v2.71.0)

## v2.70.16 - 2025-02-05

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.70.15...v2.70.16)

## v2.70.15 - 2025-01-31

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.70.14...v2.70.15)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
package org.orcid.api.common.filter;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;

import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;

import org.apache.commons.lang3.StringUtils;
import org.orcid.core.constants.OrcidOauth2Constants;
import org.orcid.core.exception.OboNotValidForApiVersionException;
import org.orcid.core.oauth.OrcidOauth2TokenDetailService;
import org.orcid.core.utils.JsonUtils;
import org.orcid.core.utils.cache.redis.RedisClient;
import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;


@Component
@Provider
public class OboApiVersionCheckFilter implements ContainerRequestFilter {

@Autowired
private OrcidOauth2TokenDetailService orcidOauth2TokenService;


@Autowired
private RedisClient redisClient;

@Value("${org.orcid.core.utils.cache.redis.enabled:true}")
private boolean isTokenCacheEnabled;

@Override
public void filter(ContainerRequestContext request) {
String version = getApiVersion(request);
Expand All @@ -44,9 +57,19 @@ private boolean isOboRequest() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (OAuth2Authentication.class.isAssignableFrom(authentication.getClass())) {
OAuth2AuthenticationDetails authDetails = (OAuth2AuthenticationDetails) ((OAuth2Authentication) authentication).getDetails();
if (authDetails != null && authDetails.getTokenValue() != null) {
OrcidOauth2TokenDetail tokenDetail = orcidOauth2TokenService.findIgnoringDisabledByTokenValue(authDetails.getTokenValue());
return tokenDetail.getOboClientDetailsId() != null;
if (authDetails != null && authDetails.getTokenValue() != null) {
Map<String, String> cachedAccessToken = getTokenFromCache(authDetails.getTokenValue());
if(cachedAccessToken != null) {
if(cachedAccessToken.containsKey(OrcidOauth2Constants.IS_OBO_TOKEN)) {
return true;
}
} else {
// Fallback to database if it is not in the cache
OrcidOauth2TokenDetail tokenDetail = orcidOauth2TokenService.findIgnoringDisabledByTokenValue(authDetails.getTokenValue());
if(tokenDetail != null) {
return tokenDetail.getOboClientDetailsId() != null;
}
}
}
}
}
Expand All @@ -62,4 +85,14 @@ private String getApiVersion(ContainerRequestContext request) {
return null;
}

private Map<String, String> getTokenFromCache(String accessTokenValue) {
if(isTokenCacheEnabled) {
String tokenJsonInfo = redisClient.get(accessTokenValue);
if(StringUtils.isNotBlank(tokenJsonInfo)) {
return JsonUtils.readObjectFromJsonString(tokenJsonInfo, HashMap.class);
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.orcid.persistence.dao.ProfileLastModifiedDao;
import org.orcid.persistence.jpa.entities.IndexingStatus;
import org.orcid.persistence.jpa.entities.OrcidOauth2AuthoriziationCodeDetail;
import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail;
import org.orcid.pojo.ajaxForm.PojoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -191,6 +192,9 @@ protected void setToCache(String clientId, OAuth2AccessToken accessToken) {
tokenData.put(OrcidOauth2Constants.CLIENT_ID, clientId);
tokenData.put(OrcidOauth2Constants.RESOURCE_IDS, OrcidOauth2Constants.ORCID);
tokenData.put(OrcidOauth2Constants.APPROVED, Boolean.TRUE.toString());
if(accessToken.getAdditionalInformation().containsKey(OrcidOauth2Constants.IS_OBO_TOKEN)) {
tokenData.put(OrcidOauth2Constants.IS_OBO_TOKEN, Boolean.TRUE.toString());
}
redisClient.set(tokenValue, JsonUtils.convertToJsonString(tokenData));
} catch(Exception e) {
LOGGER.info("Unable to set token in Redis cache", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class OrcidOauth2Constants {
public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials";
public static final String DATE_CREATED = "date_created";
public static final String CLIENT_ID = "client_id";
public static final String IS_OBO_TOKEN = "is_obo_token";
public static final String ORCID = "orcid";
public static final String NAME = "name";
public static final String CLIENT_ID_PARAM = "client_id";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@
public interface ProfileEmailDomainManager extends ProfileEmailDomainManagerReadOnly {
void updateEmailDomains(String orcid, org.orcid.pojo.ajaxForm.Emails emails);
void processDomain(String orcid, String email);
void removeAllEmailDomains(String orcid);
void moveEmailDomainToAnotherAccount(String emailDomain, String deprecatedOrcid, String primaryOrcid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void updateEmailDomains(String orcid, org.orcid.pojo.ajaxForm.Emails newE
if (orcid == null || orcid.isBlank()) {
throw new IllegalArgumentException("ORCID must not be empty");
}
List<ProfileEmailDomainEntity> existingEmailDomains = profileEmailDomainDao.findByOrcid(orcid);
List<ProfileEmailDomainEntity> existingEmailDomains = profileEmailDomainDaoReadOnly.findByOrcid(orcid);

if (existingEmailDomains != null) {
// VISIBILITY UPDATE FOR EXISTING DOMAINS
Expand Down Expand Up @@ -98,7 +98,7 @@ public void processDomain(String orcid, String email) {
}
}
if(StringUtils.equalsIgnoreCase(category, EmailDomainEntity.DomainCategory.PROFESSIONAL.name())) {
ProfileEmailDomainEntity existingDomain = profileEmailDomainDao.findByEmailDomain(orcid, domain);
ProfileEmailDomainEntity existingDomain = profileEmailDomainDaoReadOnly.findByEmailDomain(orcid, domain);
// ADD NEW DOMAIN IF ONE DOESN'T EXIST
if (existingDomain == null) {
// Verify the user doesn't have more emails with that domain
Expand All @@ -109,4 +109,19 @@ public void processDomain(String orcid, String email) {
}
}
}

public void removeAllEmailDomains(String orcid) {
if (orcid == null || orcid.isBlank()) {
throw new IllegalArgumentException("ORCID must not be empty");
}
profileEmailDomainDao.removeAllEmailDomains(orcid);
}

@Transactional
public void moveEmailDomainToAnotherAccount(String emailDomain, String deprecatedOrcid, String primaryOrcid) {
ProfileEmailDomainEntity existingEmailDomain = getEmailDomain(primaryOrcid, emailDomain);
if (existingEmailDomain == null) {
profileEmailDomainDao.moveEmailDomainToAnotherAccount(emailDomain, deprecatedOrcid, primaryOrcid);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@
import org.orcid.persistence.dao.BackupCodeDao;
import org.orcid.persistence.dao.ProfileLastModifiedDao;
import org.orcid.persistence.dao.UserConnectionDao;
import org.orcid.persistence.jpa.entities.ClientDetailsEntity;
import org.orcid.persistence.jpa.entities.IndexingStatus;
import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail;
import org.orcid.persistence.jpa.entities.ProfileEntity;
import org.orcid.persistence.jpa.entities.*;
import org.orcid.pojo.ApplicationSummary;
import org.orcid.pojo.ajaxForm.Claim;
import org.orcid.pojo.ajaxForm.PojoUtil;
Expand Down Expand Up @@ -190,7 +187,6 @@ public Boolean doInTransaction(TransactionStatus status) {
// If it was successfully deprecated
if (wasDeprecated) {
LOGGER.info("Account {} was deprecated to primary account: {}", deprecatedOrcid, primaryOrcid);
clearRecord(deprecatedOrcid, false);
// Move all email's to the primary record
Emails deprecatedAccountEmails = emailManager.getEmails(deprecatedOrcid);
if (deprecatedAccountEmails != null) {
Expand All @@ -202,6 +198,13 @@ public Boolean doInTransaction(TransactionStatus status) {
emailManager.moveEmailToOtherAccount(email.getEmail(), deprecatedOrcid, primaryOrcid);
}
}
List<ProfileEmailDomainEntity> deprecatedEmailDomains = profileEmailDomainManager.getEmailDomains(deprecatedOrcid);
if (deprecatedEmailDomains != null && !deprecatedEmailDomains.isEmpty()) {
for (ProfileEmailDomainEntity emailDomain : deprecatedEmailDomains) {
profileEmailDomainManager.moveEmailDomainToAnotherAccount(emailDomain.getEmailDomain(), deprecatedOrcid, primaryOrcid);
}
}
clearRecord(deprecatedOrcid, false);

profileLastModifiedDao.updateLastModifiedDateAndIndexingStatus(deprecatedOrcid, IndexingStatus.REINDEX);
return true;
Expand Down Expand Up @@ -640,6 +643,9 @@ private void clearRecord(String orcid, Boolean disableTokens) {
// remove trusted individuals
givenPermissionToManager.removeAllForProfile(orcid);

// remove email domains
profileEmailDomainManager.removeAllEmailDomains(orcid);

// Remove biography
if (biographyManager.exists(orcid)) {
Biography deprecatedBio = new Biography();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
*/
public interface ProfileEmailDomainManagerReadOnly {
List<ProfileEmailDomainEntity> getEmailDomains(String orcid);
ProfileEmailDomainEntity getEmailDomain(String orcid, String emailDomain);
List<ProfileEmailDomainEntity> getPublicEmailDomains(String orcid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public List<ProfileEmailDomainEntity> getEmailDomains(String orcid) {
return profileEmailDomainDaoReadOnly.findByOrcid(orcid);
};

public ProfileEmailDomainEntity getEmailDomain(String orcid, String emailDomain) {
return profileEmailDomainDaoReadOnly.findByEmailDomain(orcid, emailDomain);
}

public List<ProfileEmailDomainEntity> getPublicEmailDomains(String orcid) {
return profileEmailDomainDaoReadOnly.findPublicEmailDomains(orcid);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.nimbusds.jwt.SignedJWT;

import static org.orcid.core.constants.OrcidOauth2Constants.TOKEN_DISABLED;
import static org.orcid.core.constants.OrcidOauth2Constants.IS_OBO_TOKEN;

public class IETFTokenExchangeResponse implements OAuth2AccessToken {

Expand Down Expand Up @@ -52,6 +53,10 @@ public static IETFTokenExchangeResponse accessToken(OAuth2AccessToken accessTok
if(accessToken.getAdditionalInformation().containsKey(TOKEN_DISABLED)) {
token.additionalInformation.put(TOKEN_DISABLED, "true");
}
if(accessToken.getAdditionalInformation().containsKey(IS_OBO_TOKEN)) {
token.additionalInformation.put(IS_OBO_TOKEN, "true");
}

return token;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authe
orcidOauthTokenDetailService.createNew(detail);
// Set the token id in the additional details
token.getAdditionalInformation().put(OrcidOauth2Constants.TOKEN_ID, detail.getId());
if(detail.getOboClientDetailsId() != null) {
token.getAdditionalInformation().put(OrcidOauth2Constants.IS_OBO_TOKEN, "true");
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public List<OrgDisambiguatedSolrDocument> getOrgs(String searchTerm, int firstRe

// Set the preserveMulti parameter
query.setParam("preserveMulti", "true");
query.setParam("lowercaseOperators", "true");

if(fundersOnly) {
query.addFilterQuery(String.format("(%s:(%s OR %s))", SolrConstants.ORG_DISAMBIGUATED_ID_SOURCE_TYPE, "ROR", "FUNDREF"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ public class PasswordResetToken {
// public static final String RESET_TOKEN_DATE_FORMAT = "dd/MM/yyyy HH:mm:ss";
private static final String EMAIL_PARAM_KEY = "email";
private static final String ISSUE_DATE_PARAM_KEY = "issueDate";
private static final String HOURS_PARAM_KEY = "h";
private static final String EQUALS = "=";
private static final String SEPARATOR = "&";

private String email;
private XMLGregorianCalendar issueDate;
private int durationInHours = 4;

public PasswordResetToken() {

Expand All @@ -38,19 +40,28 @@ public PasswordResetToken(String paramsString) {
if (keyValue.length == 2) {
params.put(keyValue[0], keyValue[1]);
}

}
email = params.get(EMAIL_PARAM_KEY);
issueDate = DateUtils.convertToXMLGregorianCalendar(params.get(ISSUE_DATE_PARAM_KEY));
if(StringUtils.isNotBlank(params.get(HOURS_PARAM_KEY))) {
durationInHours = Integer.valueOf(params.get(HOURS_PARAM_KEY));
}

}

public String getEmail() {
return email;
}

public int getDurationInHours() {
return durationInHours;
}

public Date getIssueDate() {
return issueDate.toGregorianCalendar().getTime();
}


/**
*
Expand All @@ -61,6 +72,7 @@ public String toParamsString() {
List<Pair<String, String>> pairs = new ArrayList<Pair<String, String>>();
pairs.add(new ImmutablePair<String, String>(EMAIL_PARAM_KEY, email));
pairs.add(new ImmutablePair<String, String>(ISSUE_DATE_PARAM_KEY, String.valueOf(issueDate)));
pairs.add(new ImmutablePair<String, String>(HOURS_PARAM_KEY, String.valueOf(durationInHours)));

List<String> items = new ArrayList<String>(pairs.size());
for (Pair<String, String> pair : pairs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ public String createResetEmail(String userEmail, String baseUri) {
String resetParams = MessageFormat.format("email={0}&issueDate={1}", new Object[] { userEmail, date.toXMLFormat() });
return createEmailBaseUrl(resetParams, baseUri, "reset-password-email");
}

public String createResetLinkForAdmin(String userEmail, String baseUri) {
XMLGregorianCalendar date = DateUtils.convertToXMLGregorianCalendarNoTimeZoneNoMillis(new Date());
String resetParams = MessageFormat.format("email={0}&issueDate={1}&h=24", new Object[] { userEmail, date.toXMLFormat() });
return createEmailBaseUrl(resetParams, baseUri, "reset-password-email");
}

public String createReactivationUrl(String userEmail, String baseUri) {
XMLGregorianCalendar date = DateUtils.convertToXMLGregorianCalendarNoTimeZoneNoMillis(new Date());
Expand Down
Loading

0 comments on commit 8b494d9

Please sign in to comment.