diff --git a/src/main/java/com/descope/model/auth/AuthenticationInfo.java b/src/main/java/com/descope/model/auth/AuthenticationInfo.java index 5c14886b..b4a5666d 100644 --- a/src/main/java/com/descope/model/auth/AuthenticationInfo.java +++ b/src/main/java/com/descope/model/auth/AuthenticationInfo.java @@ -12,4 +12,9 @@ public class AuthenticationInfo { private Token refreshToken; private UserResponse user; private Boolean firstSeen; + private IDPResponse idpResponse; + + public AuthenticationInfo(Token token, Token refreshToken, UserResponse user, Boolean firstSeen) { + this(token, refreshToken, user, firstSeen, null); + } } diff --git a/src/main/java/com/descope/model/auth/IDPResponse.java b/src/main/java/com/descope/model/auth/IDPResponse.java new file mode 100644 index 00000000..60bf41e8 --- /dev/null +++ b/src/main/java/com/descope/model/auth/IDPResponse.java @@ -0,0 +1,16 @@ +package com.descope.model.auth; + +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IDPResponse { + private List idpGroups; + private Map idpSAMLAttributes; + private Map idpOIDCClaims; +} diff --git a/src/main/java/com/descope/model/jwt/response/JWTResponse.java b/src/main/java/com/descope/model/jwt/response/JWTResponse.java index 00e87966..763fa8c0 100644 --- a/src/main/java/com/descope/model/jwt/response/JWTResponse.java +++ b/src/main/java/com/descope/model/jwt/response/JWTResponse.java @@ -1,5 +1,6 @@ package com.descope.model.jwt.response; +import com.descope.model.auth.IDPResponse; import com.descope.model.user.response.UserResponse; import lombok.AllArgsConstructor; import lombok.Data; @@ -17,4 +18,11 @@ public class JWTResponse { private Integer cookieExpiration; private UserResponse user; private Boolean firstSeen; + private IDPResponse idpResponse; + + public JWTResponse(String sessionJwt, String refreshJwt, String cookieDomain, String cookiePath, + Integer cookieMaxAge, Integer cookieExpiration, UserResponse user, Boolean firstSeen) { + this(sessionJwt, refreshJwt, cookieDomain, cookiePath, cookieMaxAge, cookieExpiration, user, + firstSeen, null); + } } 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 97c37930..6774c9f7 100644 --- a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceImpl.java @@ -94,7 +94,7 @@ public AuthenticationInfo validateAndRefreshSessionWithTokensAuthenticationInfo( } else if (StringUtils.isNotBlank(sessionToken)) { try { Token refresh = validateAndCreateToken(refreshToken); - return new AuthenticationInfo(validateSessionWithToken(sessionToken), refresh, null, null); + return new AuthenticationInfo(validateSessionWithToken(sessionToken), refresh, null, null, null); } catch (Exception e) { if (StringUtils.isNotBlank(refreshToken)) { return refreshSessionWithTokenAuthenticationInfo(refreshToken); diff --git a/src/main/java/com/descope/sdk/auth/impl/AuthenticationsBase.java b/src/main/java/com/descope/sdk/auth/impl/AuthenticationsBase.java index ad36605e..3acf3125 100644 --- a/src/main/java/com/descope/sdk/auth/impl/AuthenticationsBase.java +++ b/src/main/java/com/descope/sdk/auth/impl/AuthenticationsBase.java @@ -132,7 +132,8 @@ AuthenticationInfo getAuthenticationInfo(JWTResponse jwtResponse) { refreshToken = validateAndCreateToken(jwtResponse.getRefreshJwt()); } return new AuthenticationInfo( - sessionToken, refreshToken, jwtResponse.getUser(), jwtResponse.getFirstSeen()); + sessionToken, refreshToken, jwtResponse.getUser(), jwtResponse.getFirstSeen(), + jwtResponse.getIdpResponse()); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/descope/sdk/mgmt/impl/JwtServiceImpl.java b/src/main/java/com/descope/sdk/mgmt/impl/JwtServiceImpl.java index 68456a3c..ca703906 100644 --- a/src/main/java/com/descope/sdk/mgmt/impl/JwtServiceImpl.java +++ b/src/main/java/com/descope/sdk/mgmt/impl/JwtServiceImpl.java @@ -138,7 +138,8 @@ private AuthenticationInfo validateAndCreateAuthInfo(JWTResponse jwtResponse) th } Token sessionToken = validateAndCreateToken(jwtResponse.getSessionJwt()); Token refreshToken = validateAndCreateToken(jwtResponse.getRefreshJwt()); - return new AuthenticationInfo(sessionToken, refreshToken, jwtResponse.getUser(), jwtResponse.getFirstSeen()); + return new AuthenticationInfo(sessionToken, refreshToken, jwtResponse.getUser(), jwtResponse.getFirstSeen(), + jwtResponse.getIdpResponse()); } private URI composeUpdateJwtUri() { diff --git a/src/test/java/com/descope/sdk/TestUtils.java b/src/test/java/com/descope/sdk/TestUtils.java index c0fc9dc6..5157f2d2 100644 --- a/src/test/java/com/descope/sdk/TestUtils.java +++ b/src/test/java/com/descope/sdk/TestUtils.java @@ -62,7 +62,8 @@ public class TestUtils { 1234567, 1234567890, MOCK_USER_RESPONSE, - true); + true, + null); public static final Map TENANTS_AUTHZ = mapOf("permissions", Arrays.asList("tp1", "tp2"), "roles", Arrays.asList("tr1", "tr2")); public static final Token MOCK_TOKEN = Token.builder() diff --git a/src/test/java/com/descope/sdk/auth/impl/OAuthServiceImplTest.java b/src/test/java/com/descope/sdk/auth/impl/OAuthServiceImplTest.java index c715abf6..fc73c05b 100644 --- a/src/test/java/com/descope/sdk/auth/impl/OAuthServiceImplTest.java +++ b/src/test/java/com/descope/sdk/auth/impl/OAuthServiceImplTest.java @@ -16,9 +16,11 @@ import static org.mockito.Mockito.mockStatic; import com.descope.model.auth.AuthenticationInfo; +import com.descope.model.auth.IDPResponse; import com.descope.model.auth.OAuthResponse; import com.descope.model.client.Client; import com.descope.model.jwt.Token; +import com.descope.model.jwt.response.JWTResponse; import com.descope.model.jwt.response.SigningKeysResponse; import com.descope.model.magiclink.LoginOptions; import com.descope.model.user.response.UserResponse; @@ -111,6 +113,65 @@ void testExchangeToken() { Assertions.assertThat(user.getLoginIds()).isNotEmpty(); } + @Test + void testExchangeTokenWithoutIDPResponse() { + JWTResponse jwtResponseNoIdp = new JWTResponse( + "someSessionJwt", "someRefreshJwt", "", "/", 1234567, 1234567890, + MOCK_JWT_RESPONSE.getUser(), true); + + ApiProxy apiProxy = mock(ApiProxy.class); + doReturn(jwtResponseNoIdp).when(apiProxy).post(any(), any(), any()); + doReturn(new SigningKeysResponse(Arrays.asList(MOCK_SIGNING_KEY))).when(apiProxy).get(any(), + eq(SigningKeysResponse.class)); + + AuthenticationInfo authenticationInfo; + try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { + mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + try (MockedStatic mockedJwtUtils = mockStatic(JwtUtils.class)) { + mockedJwtUtils.when(() -> JwtUtils.getToken(anyString(), any())).thenReturn(MOCK_TOKEN); + authenticationInfo = oauthService.exchangeToken("somecode"); + } + } + + Assertions.assertThat(authenticationInfo).isNotNull(); + Assertions.assertThat(authenticationInfo.getUser()).isNotNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse()).isNull(); + } + + @Test + void testExchangeTokenWithIDPResponse() { + IDPResponse idpResponse = new IDPResponse( + Arrays.asList("users"), + null, + mapOf("email_verified", true, "locale", "en-US")); + JWTResponse jwtResponseWithIdp = new JWTResponse( + "someSessionJwt", "someRefreshJwt", "", "/", 1234567, 1234567890, + MOCK_JWT_RESPONSE.getUser(), true, idpResponse); + + ApiProxy apiProxy = mock(ApiProxy.class); + doReturn(jwtResponseWithIdp).when(apiProxy).post(any(), any(), any()); + doReturn(new SigningKeysResponse(Arrays.asList(MOCK_SIGNING_KEY))).when(apiProxy).get(any(), + eq(SigningKeysResponse.class)); + + AuthenticationInfo authenticationInfo; + try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { + mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + try (MockedStatic mockedJwtUtils = mockStatic(JwtUtils.class)) { + mockedJwtUtils.when(() -> JwtUtils.getToken(anyString(), any())).thenReturn(MOCK_TOKEN); + authenticationInfo = oauthService.exchangeToken("somecode"); + } + } + + Assertions.assertThat(authenticationInfo).isNotNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse()).isNotNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse().getIdpGroups()) + .isEqualTo(Arrays.asList("users")); + Assertions.assertThat(authenticationInfo.getIdpResponse().getIdpSAMLAttributes()).isNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse().getIdpOIDCClaims()) + .containsEntry("email_verified", true) + .containsEntry("locale", "en-US"); + } + void testExampleRequireBrowser() throws Exception { System.out.println(oauthService.start(OAUTH_PROVIDER_GOOGLE, "https://localhost/kuku", null)); String encodedCode = ""; diff --git a/src/test/java/com/descope/sdk/auth/impl/SamlLinkServiceImplTest.java b/src/test/java/com/descope/sdk/auth/impl/SamlLinkServiceImplTest.java index f489b70d..1480a2bb 100644 --- a/src/test/java/com/descope/sdk/auth/impl/SamlLinkServiceImplTest.java +++ b/src/test/java/com/descope/sdk/auth/impl/SamlLinkServiceImplTest.java @@ -5,6 +5,7 @@ import static com.descope.sdk.TestUtils.MOCK_TOKEN; import static com.descope.sdk.TestUtils.MOCK_URL; import static com.descope.sdk.TestUtils.PROJECT_ID; +import static com.descope.utils.CollectionUtils.mapOf; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -13,9 +14,11 @@ import static org.mockito.Mockito.mockStatic; import com.descope.model.auth.AuthenticationInfo; +import com.descope.model.auth.IDPResponse; import com.descope.model.auth.SAMLResponse; import com.descope.model.client.Client; import com.descope.model.jwt.Token; +import com.descope.model.jwt.response.JWTResponse; import com.descope.model.jwt.response.SigningKeysResponse; import com.descope.model.magiclink.LoginOptions; import com.descope.model.user.response.UserResponse; @@ -88,4 +91,65 @@ void testExchangeToken() { Assertions.assertThat(user.getUserId()).isNotBlank(); Assertions.assertThat(user.getLoginIds()).isNotEmpty(); } + + @Test + void testExchangeTokenWithoutIDPResponse() { + JWTResponse jwtResponseNoIdp = new JWTResponse( + "someSessionJwt", "someRefreshJwt", "", "/", 1234567, 1234567890, + MOCK_JWT_RESPONSE.getUser(), true); + + ApiProxy apiProxy = mock(ApiProxy.class); + doReturn(jwtResponseNoIdp).when(apiProxy).post(any(), any(), any()); + doReturn(new SigningKeysResponse(Arrays.asList(MOCK_SIGNING_KEY))) + .when(apiProxy).get(any(), eq(SigningKeysResponse.class)); + + AuthenticationInfo authenticationInfo; + try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { + mockedApiProxyBuilder.when( + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + try (MockedStatic mockedJwtUtils = mockStatic(JwtUtils.class)) { + mockedJwtUtils.when(() -> JwtUtils.getToken(anyString(), any())).thenReturn(MOCK_TOKEN); + authenticationInfo = samlService.exchangeToken("somecode"); + } + } + + Assertions.assertThat(authenticationInfo).isNotNull(); + Assertions.assertThat(authenticationInfo.getUser()).isNotNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse()).isNull(); + } + + @Test + void testExchangeTokenWithIDPResponse() { + IDPResponse idpResponse = new IDPResponse( + Arrays.asList("engineering", "devops"), + mapOf("department", "engineering", "title", "Staff Engineer"), + null); + JWTResponse jwtResponseWithIdp = new JWTResponse( + "someSessionJwt", "someRefreshJwt", "", "/", 1234567, 1234567890, + MOCK_JWT_RESPONSE.getUser(), true, idpResponse); + + ApiProxy apiProxy = mock(ApiProxy.class); + doReturn(jwtResponseWithIdp).when(apiProxy).post(any(), any(), any()); + doReturn(new SigningKeysResponse(Arrays.asList(MOCK_SIGNING_KEY))) + .when(apiProxy).get(any(), eq(SigningKeysResponse.class)); + + AuthenticationInfo authenticationInfo; + try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { + mockedApiProxyBuilder.when( + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + try (MockedStatic mockedJwtUtils = mockStatic(JwtUtils.class)) { + mockedJwtUtils.when(() -> JwtUtils.getToken(anyString(), any())).thenReturn(MOCK_TOKEN); + authenticationInfo = samlService.exchangeToken("somecode"); + } + } + + Assertions.assertThat(authenticationInfo).isNotNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse()).isNotNull(); + Assertions.assertThat(authenticationInfo.getIdpResponse().getIdpGroups()) + .isEqualTo(Arrays.asList("engineering", "devops")); + Assertions.assertThat(authenticationInfo.getIdpResponse().getIdpSAMLAttributes()) + .containsEntry("department", "engineering") + .containsEntry("title", "Staff Engineer"); + Assertions.assertThat(authenticationInfo.getIdpResponse().getIdpOIDCClaims()).isNull(); + } }