From 8c02f6098ca2a4e4c19c7e0f744cef66b6e5fbaf Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Mon, 26 Feb 2024 10:29:37 +0200 Subject: [PATCH 1/2] Add AccessKeyLoginOptions (including customClaims) for ExchangeAccessKey API --- .../model/auth/AccessKeyLoginOptions.java | 15 +++++++++++++++ .../descope/sdk/auth/AuthenticationService.java | 4 +++- .../sdk/auth/impl/AuthenticationServiceImpl.java | 5 +++-- .../auth/impl/AuthenticationServiceImplTest.java | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/descope/model/auth/AccessKeyLoginOptions.java diff --git a/src/main/java/com/descope/model/auth/AccessKeyLoginOptions.java b/src/main/java/com/descope/model/auth/AccessKeyLoginOptions.java new file mode 100644 index 00000000..6efcc5bb --- /dev/null +++ b/src/main/java/com/descope/model/auth/AccessKeyLoginOptions.java @@ -0,0 +1,15 @@ +package com.descope.model.auth; + +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AccessKeyLoginOptions { + private Map customClaims; +} diff --git a/src/main/java/com/descope/sdk/auth/AuthenticationService.java b/src/main/java/com/descope/sdk/auth/AuthenticationService.java index cd74d244..d1475cdf 100644 --- a/src/main/java/com/descope/sdk/auth/AuthenticationService.java +++ b/src/main/java/com/descope/sdk/auth/AuthenticationService.java @@ -5,6 +5,7 @@ import com.descope.model.user.response.UserHistoryResponse; import com.descope.model.user.response.UserResponse; import java.util.List; +import com.descope.model.auth.AccessKeyLoginOptions; public interface AuthenticationService { @@ -45,10 +46,11 @@ Token validateAndRefreshSessionWithTokens(String sessionToken, String refreshTok * Use to exchange an access key for a session token. * * @param accessKey - Access Key + * @param loginOptions - {@link AccessKeyLoginOptions loginOptions} * @return {@link Token Token} * @throws DescopeException if there is an error */ - Token exchangeAccessKey(String accessKey) throws DescopeException; + Token exchangeAccessKey(String accessKey, AccessKeyLoginOptions loginOptions) throws DescopeException; /** * Use to ensure that a validated session token has been granted the specified permissions. This diff --git a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java index 670d2b22..436cfd6f 100644 --- a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import com.descope.model.auth.AccessKeyLoginOptions; class AuthenticationServiceImpl extends AuthenticationsBase { @@ -70,11 +71,11 @@ public Token validateAndRefreshSessionWithTokens(String sessionToken, String ref } @Override - public Token exchangeAccessKey(String accessKey) throws DescopeException { + public Token exchangeAccessKey(String accessKey, AccessKeyLoginOptions loginOptions) throws DescopeException { ApiProxy apiProxy = getApiProxy(accessKey); URI exchangeAccessKeyLinkURL = composeExchangeAccessKeyLinkURL(); - JWTResponse jwtResponse = apiProxy.post(exchangeAccessKeyLinkURL, null, JWTResponse.class); + JWTResponse jwtResponse = apiProxy.post(exchangeAccessKeyLinkURL, loginOptions, JWTResponse.class); AuthenticationInfo authenticationInfo = getAuthenticationInfo(jwtResponse); return authenticationInfo.getToken(); } diff --git a/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java b/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java index c7eadddb..da76e015 100644 --- a/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java +++ b/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java @@ -28,6 +28,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RetryingTest; +import com.descope.model.auth.AccessKeyLoginOptions; +import static com.descope.utils.CollectionUtils.mapOf; public class AuthenticationServiceImplTest { @@ -122,4 +124,18 @@ void testFunctionalFullCycle() throws Exception { authenticationService.logout(authInfo.getRefreshToken().getJwt()); userService.delete(loginId); } + + @Test + void exchangeAccessKey() { + ApiProxy apiProxy = mock(ApiProxy.class); + doReturn(MOCK_JWT_RESPONSE).when(apiProxy).post(any(), any(), any()); + try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { + mockedApiProxyBuilder.when( + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + + Map customClaims = mapOf("k1", "v1"); + loginOptions = new AccessKeyLoginOptions(customClaims) + token = this.authenticationService.exchangeAccessKey("dummyKey", loginOptions) + } + } } From fd9083f70bbd456084ecf72a9bd95dd025561deb Mon Sep 17 00:00:00 2001 From: Slavik Markovich Date: Mon, 26 Feb 2024 09:54:30 -0800 Subject: [PATCH 2/2] add func test --- .../sdk/auth/AuthenticationService.java | 11 ++++++- .../auth/impl/AuthenticationServiceImpl.java | 14 ++++++-- .../impl/AuthenticationServiceImplTest.java | 32 +++++++++++-------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/descope/sdk/auth/AuthenticationService.java b/src/main/java/com/descope/sdk/auth/AuthenticationService.java index d1475cdf..b9a2eef3 100644 --- a/src/main/java/com/descope/sdk/auth/AuthenticationService.java +++ b/src/main/java/com/descope/sdk/auth/AuthenticationService.java @@ -1,11 +1,11 @@ package com.descope.sdk.auth; import com.descope.exception.DescopeException; +import com.descope.model.auth.AccessKeyLoginOptions; import com.descope.model.jwt.Token; import com.descope.model.user.response.UserHistoryResponse; import com.descope.model.user.response.UserResponse; import java.util.List; -import com.descope.model.auth.AccessKeyLoginOptions; public interface AuthenticationService { @@ -46,6 +46,15 @@ Token validateAndRefreshSessionWithTokens(String sessionToken, String refreshTok * Use to exchange an access key for a session token. * * @param accessKey - Access Key + * @return {@link Token Token} + * @throws DescopeException if there is an error + */ + Token exchangeAccessKey(String accessKey) throws DescopeException; + + /** + * Use to exchange an access key for a session token with login options. + * + * @param accessKey - Access Key * @param loginOptions - {@link AccessKeyLoginOptions loginOptions} * @return {@link Token Token} * @throws DescopeException if there is an error diff --git a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java index 436cfd6f..f60539ec 100644 --- a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java @@ -7,9 +7,11 @@ import static com.descope.literals.Routes.AuthEndPoints.LOG_OUT_ALL_LINK; import static com.descope.literals.Routes.AuthEndPoints.LOG_OUT_LINK; import static com.descope.literals.Routes.AuthEndPoints.ME_LINK; +import static com.descope.utils.CollectionUtils.mapOf; import com.descope.exception.DescopeException; import com.descope.exception.ServerCommonException; +import com.descope.model.auth.AccessKeyLoginOptions; import com.descope.model.auth.AuthenticationInfo; import com.descope.model.auth.ExchangeTokenRequest; import com.descope.model.client.Client; @@ -26,7 +28,6 @@ import java.util.List; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import com.descope.model.auth.AccessKeyLoginOptions; class AuthenticationServiceImpl extends AuthenticationsBase { @@ -70,12 +71,21 @@ public Token validateAndRefreshSessionWithTokens(String sessionToken, String ref } } + @Override + public Token exchangeAccessKey(String accessKey) throws DescopeException { + return exchangeAccessKey(accessKey, null); + } + @Override public Token exchangeAccessKey(String accessKey, AccessKeyLoginOptions loginOptions) throws DescopeException { + if (StringUtils.isBlank(accessKey)) { + throw ServerCommonException.invalidArgument("accessKey"); + } ApiProxy apiProxy = getApiProxy(accessKey); URI exchangeAccessKeyLinkURL = composeExchangeAccessKeyLinkURL(); - JWTResponse jwtResponse = apiProxy.post(exchangeAccessKeyLinkURL, loginOptions, JWTResponse.class); + JWTResponse jwtResponse = apiProxy.post(exchangeAccessKeyLinkURL, + mapOf("loginOptions", loginOptions), JWTResponse.class); AuthenticationInfo authenticationInfo = getAuthenticationInfo(jwtResponse); return authenticationInfo.getToken(); } diff --git a/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java b/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java index da76e015..f2c9f21d 100644 --- a/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java +++ b/src/test/java/com/descope/sdk/auth/impl/AuthenticationServiceImplTest.java @@ -1,6 +1,7 @@ package com.descope.sdk.auth.impl; import static com.descope.sdk.TestUtils.MOCK_TOKEN; +import static com.descope.utils.CollectionUtils.mapOf; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -9,10 +10,12 @@ import com.descope.enums.DeliveryMethod; import com.descope.exception.RateLimitExceededException; +import com.descope.model.auth.AccessKeyLoginOptions; import com.descope.model.auth.AuthenticationInfo; import com.descope.model.auth.AuthenticationServices; import com.descope.model.client.Client; import com.descope.model.jwt.Token; +import com.descope.model.mgmt.AccessKeyResponse; import com.descope.model.mgmt.ManagementServices; import com.descope.model.user.request.UserRequest; import com.descope.model.user.response.OTPTestUserResponse; @@ -20,16 +23,16 @@ import com.descope.sdk.TestUtils; import com.descope.sdk.auth.AuthenticationService; import com.descope.sdk.auth.OTPService; +import com.descope.sdk.mgmt.AccessKeyService; import com.descope.sdk.mgmt.RolesService; import com.descope.sdk.mgmt.TenantService; import com.descope.sdk.mgmt.UserService; import com.descope.sdk.mgmt.impl.ManagementServiceBuilder; import java.util.Arrays; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RetryingTest; -import com.descope.model.auth.AccessKeyLoginOptions; -import static com.descope.utils.CollectionUtils.mapOf; public class AuthenticationServiceImplTest { @@ -38,6 +41,7 @@ public class AuthenticationServiceImplTest { private OTPService otpService; private RolesService roleService; private TenantService tenantService; + private AccessKeyService accessKeyService; @BeforeEach void setUp() { @@ -49,6 +53,7 @@ void setUp() { this.userService = mgmtServices.getUserService(); this.roleService = mgmtServices.getRolesService(); this.tenantService = mgmtServices.getTenantService(); + this.accessKeyService = mgmtServices.getAccessKeyService(); } @Test @@ -125,17 +130,16 @@ void testFunctionalFullCycle() throws Exception { userService.delete(loginId); } - @Test - void exchangeAccessKey() { - ApiProxy apiProxy = mock(ApiProxy.class); - doReturn(MOCK_JWT_RESPONSE).when(apiProxy).post(any(), any(), any()); - try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { - mockedApiProxyBuilder.when( - () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); - - Map customClaims = mapOf("k1", "v1"); - loginOptions = new AccessKeyLoginOptions(customClaims) - token = this.authenticationService.exchangeAccessKey("dummyKey", loginOptions) - } + @RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class) + void testFunctionalExchangeToken() throws Exception { + String name = TestUtils.getRandomName("ak-"); + AccessKeyResponse resp = accessKeyService.create(name, 0, null, null); + Token token = authenticationService.exchangeAccessKey(resp.getCleartext(), + new AccessKeyLoginOptions(mapOf("kuku", "kiki"))); + // temporary + @SuppressWarnings("unchecked") + Map nsecClaims = Map.class.cast(token.getClaims().get("nsec")); + assertEquals("kiki", nsecClaims.get("kuku")); + accessKeyService.delete(resp.getKey().getId()); } }