diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dbc91d7d0f..3b8328dee49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## v2.63.3 - 2024-08-06 + +[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.63.2...v2.63.3) + +## v2.63.2 - 2024-08-05 + +[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.63.1...v2.63.2) + +## v2.63.1 - 2024-08-05 + +[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.63.0...v2.63.1) + +## v2.63.0 - 2024-08-04 + +[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.62.6...v2.63.0) + +## v2.62.6 - 2024-08-04 + +[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.62.5...v2.62.6) + ## v2.62.5 - 2024-07-24 [Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v2.62.4...v2.62.5) diff --git a/orcid-api-common/src/main/java/org/orcid/api/common/jaxb/OrcidExceptionMapper.java b/orcid-api-common/src/main/java/org/orcid/api/common/jaxb/OrcidExceptionMapper.java index 418eceda83f..8819b3422f5 100644 --- a/orcid-api-common/src/main/java/org/orcid/api/common/jaxb/OrcidExceptionMapper.java +++ b/orcid-api-common/src/main/java/org/orcid/api/common/jaxb/OrcidExceptionMapper.java @@ -130,6 +130,8 @@ public Response toResponse(Throwable t) { logShortError(t, clientId); } else if (t instanceof InvalidPutCodeException) { logShortError(t, clientId); + } else if (t instanceof MismatchedPutCodeException) { + logShortError(t, clientId); } else { LOGGER.error("An exception has occured processing request from client " + clientId, t); } diff --git a/orcid-api-common/src/main/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegatorImpl.java b/orcid-api-common/src/main/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegatorImpl.java index 6d15fb7a930..db1c8e7ea83 100644 --- a/orcid-api-common/src/main/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegatorImpl.java +++ b/orcid-api-common/src/main/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegatorImpl.java @@ -162,8 +162,11 @@ public Response obtainOauth2Token(String authorization, MultivaluedMap doNotAllowDeleteOnTheseRevokeReasons = List.of(RevokeReason.CLIENT_REVOKED.name(), RevokeReason.STAFF_REVOKED.name()); + private final List doNotAllowDeleteOnTheseRevokeReasons = List.of(RevokeReason.CLIENT_REVOKED, RevokeReason.STAFF_REVOKED, RevokeReason.RECORD_DEACTIVATED, RevokeReason.AUTH_CODE_REUSED); public IETFExchangeTokenGranter(AuthorizationServerTokenServices tokenServices) { this.tokenServices = tokenServices; @@ -239,10 +238,12 @@ private OAuth2AccessToken generateAccessToken(TokenRequest tokenRequest, String Set inactiveScopesOBO = Sets.newHashSet(); boolean issueRevokedToken = false; RevokeReason revokeReason = null; + // Lets consider token expiration time anything that goes beyond this date + Date now = new Date(); for (OrcidOauth2TokenDetail d : details) { Set scopesInToken = ScopePathType.getScopesFromSpaceSeparatedString(d.getScope()); // If token is expired, we should ignore it - if (d.getTokenExpiration().after(new Date())) { + if (d.getTokenExpiration().after(now)) { // If token is disabled, we should know if it have the /activities/update scope on it if(d.getTokenDisabled() == null || !d.getTokenDisabled()) { activeScopesOBO.addAll(scopesInToken); @@ -257,8 +258,12 @@ private OAuth2AccessToken generateAccessToken(TokenRequest tokenRequest, String // Keep only the /activities/update scope if the token was not revoked by a client or staff member if(revokeReason == null || !doNotAllowDeleteOnTheseRevokeReasons.contains(revokeReason)) { inactiveScopesOBO.add(ScopePathType.ACTIVITIES_UPDATE); + } else { + throw new OrcidInvalidScopeException("The id_token is disabled and does not contain any valid scope"); } - } + } else { + throw new OrcidInvalidScopeException("The id_token is disabled and does not contain any valid scope"); + } } } } diff --git a/orcid-core/src/main/java/org/orcid/core/oauth/IETFTokenExchangeResponse.java b/orcid-core/src/main/java/org/orcid/core/oauth/IETFTokenExchangeResponse.java index 8109c83a013..1173ba0aa7c 100644 --- a/orcid-core/src/main/java/org/orcid/core/oauth/IETFTokenExchangeResponse.java +++ b/orcid-core/src/main/java/org/orcid/core/oauth/IETFTokenExchangeResponse.java @@ -13,6 +13,8 @@ import com.nimbusds.jwt.SignedJWT; +import static org.orcid.core.constants.OrcidOauth2Constants.TOKEN_DISABLED; + public class IETFTokenExchangeResponse implements OAuth2AccessToken { private Map additionalInformation = new HashMap(); @@ -47,6 +49,9 @@ public static IETFTokenExchangeResponse accessToken(OAuth2AccessToken accessTok if (accessToken.getAdditionalInformation().containsKey("name")) { token.additionalInformation.put("name",accessToken.getAdditionalInformation().get("name")); } + if(accessToken.getAdditionalInformation().containsKey(TOKEN_DISABLED)) { + token.additionalInformation.put(TOKEN_DISABLED, "true"); + } return token; } diff --git a/orcid-core/src/main/java/org/orcid/core/oauth/service/OrcidRandomValueTokenServicesImpl.java b/orcid-core/src/main/java/org/orcid/core/oauth/service/OrcidRandomValueTokenServicesImpl.java index 2f32a62ab3f..f9ec2d50b48 100644 --- a/orcid-core/src/main/java/org/orcid/core/oauth/service/OrcidRandomValueTokenServicesImpl.java +++ b/orcid-core/src/main/java/org/orcid/core/oauth/service/OrcidRandomValueTokenServicesImpl.java @@ -1,12 +1,6 @@ package org.orcid.core.oauth.service; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import javax.annotation.Resource; import javax.persistence.PersistenceException; @@ -465,6 +459,10 @@ public OAuth2AccessToken createRevokedAccessToken(OAuth2Authentication authentic // create the regular token DefaultOAuth2AccessToken accessToken = generateAccessToken(authentication); try { + if(accessToken.getAdditionalInformation() == null) { + accessToken.setAdditionalInformation(Collections.emptyMap()); + } + accessToken.getAdditionalInformation().put(OrcidOauth2Constants.TOKEN_DISABLED, true); orcidTokenStore.storeRevokedAccessToken(accessToken, authentication, revokeReason); } catch (PersistenceException e) { // In the unlikely case that there is a constraint violation, lets diff --git a/orcid-core/src/test/java/org/orcid/core/oauth/IETFExchangeTokenGranterTest.java b/orcid-core/src/test/java/org/orcid/core/oauth/IETFExchangeTokenGranterTest.java index 58308246b57..a277afcaa6f 100644 --- a/orcid-core/src/test/java/org/orcid/core/oauth/IETFExchangeTokenGranterTest.java +++ b/orcid-core/src/test/java/org/orcid/core/oauth/IETFExchangeTokenGranterTest.java @@ -289,14 +289,14 @@ public void grantDisabledTokenDoesntWorkTest() throws NoSuchAlgorithmException, tokenGranter.grant(GRANT_TYPE, getTokenRequest(ACTIVE_CLIENT_ID, List.of("/read-limited"))); fail(); } catch (OrcidInvalidScopeException oise) { - assertEquals("The id_token is not associated with a valid scope", oise.getMessage()); + assertEquals("The id_token is disabled and does not contain any valid scope", oise.getMessage()); } catch (Exception e) { fail(); } } @Test - public void grantDisabledTokenWithActivitiesReadLimitedGenerateDeactivatedTokenTest() + public void grantUserDisabledTokenWithActivitiesReadLimitedGenerateDeactivatedTokenTest() throws NoSuchAlgorithmException, IOException, ParseException, URISyntaxException, JOSEException { OrcidOauth2TokenDetail token1 = getOrcidOauth2TokenDetail(true, "/activities/update", System.currentTimeMillis() + 60000, true); token1.setRevokeReason(RevokeReason.USER_REVOKED.name()); @@ -309,6 +309,38 @@ public void grantDisabledTokenWithActivitiesReadLimitedGenerateDeactivatedTokenT verify(tokenServicesMock, never()).createAccessToken(any()); } + @Test + public void grantClientDisabledTokenWithActivitiesReadLimitedThrowExceptionTest() + throws NoSuchAlgorithmException, IOException, ParseException, URISyntaxException, JOSEException { + OrcidOauth2TokenDetail token1 = getOrcidOauth2TokenDetail(true, "/activities/update", System.currentTimeMillis() + 60000, true); + token1.setRevokeReason(RevokeReason.CLIENT_REVOKED.name()); + + when(orcidOauthTokenDetailServiceMock.findByClientIdAndUserName(any(), any())).thenReturn(List.of(token1)); + try { + tokenGranter.grant(GRANT_TYPE, getTokenRequest(ACTIVE_CLIENT_ID, List.of("/activities/update"))); + } catch(OrcidInvalidScopeException e) { + assertEquals("The id_token is disabled and does not contain any valid scope", e.getMessage()); + } catch(Exception e) { + fail("Unhandled exception:" + e.getMessage()); + } + } + + @Test + public void grantStaffDisabledTokenWithActivitiesReadLimitedThrowExceptionTest() + throws NoSuchAlgorithmException, IOException, ParseException, URISyntaxException, JOSEException { + OrcidOauth2TokenDetail token1 = getOrcidOauth2TokenDetail(true, "/activities/update", System.currentTimeMillis() + 60000, true); + token1.setRevokeReason(RevokeReason.STAFF_REVOKED.name()); + + when(orcidOauthTokenDetailServiceMock.findByClientIdAndUserName(any(), any())).thenReturn(List.of(token1)); + try { + tokenGranter.grant(GRANT_TYPE, getTokenRequest(ACTIVE_CLIENT_ID, List.of("/activities/update"))); + } catch(OrcidInvalidScopeException e) { + assertEquals("The id_token is disabled and does not contain any valid scope", e.getMessage()); + } catch(Exception e) { + fail("Unhandled exception:" + e.getMessage()); + } + } + @Test public void grantDisabledTokenWithActivitiesUpdateAndOtherActiveTokenWithOtherScopesGenerateDeactivatedTokenTest() throws NoSuchAlgorithmException, IOException, ParseException, URISyntaxException, JOSEException { diff --git a/orcid-persistence/src/main/resources/db-master.xml b/orcid-persistence/src/main/resources/db-master.xml index 79ccfe96ca3..c2e1fa5b644 100644 --- a/orcid-persistence/src/main/resources/db-master.xml +++ b/orcid-persistence/src/main/resources/db-master.xml @@ -1,324 +1,332 @@ + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -345,22 +353,22 @@ - - + + - - + + - - - - - + + + + + - - + + @@ -375,14 +383,14 @@ - + - + @@ -393,4 +401,5 @@ - + + \ No newline at end of file diff --git a/orcid-persistence/src/main/resources/db/updates/create_dw_notification.xml b/orcid-persistence/src/main/resources/db/updates/create_dw_notification.xml new file mode 100644 index 00000000000..0af7616159d --- /dev/null +++ b/orcid-persistence/src/main/resources/db/updates/create_dw_notification.xml @@ -0,0 +1,20 @@ + + + + + + select notification_type, orcid, client_source_id, date_created, sent_date, read_date, actioned_date, archived_date, last_modified + from notification where notification_type='PERMISSION' and client_source_id is not null and last_modified > date_trunc('day',(now() - interval '12 months')) + + + + + + SELECT 1 FROM pg_roles WHERE rolname='dw_user' + + GRANT SELECT ON TABLE dw_notification to dw_user; + + + \ No newline at end of file