diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/spi/TokenService.java b/esignet-core/src/main/java/io/mosip/esignet/core/spi/TokenService.java index 2324ed60c..8fa89709b 100644 --- a/esignet-core/src/main/java/io/mosip/esignet/core/spi/TokenService.java +++ b/esignet-core/src/main/java/io/mosip/esignet/core/spi/TokenService.java @@ -134,11 +134,13 @@ public interface TokenService { /** * Creates ID token with the given subject and audience and nonce + * * @param subject * @param audience * @param validitySeconds * @param transaction + * @param nonce * @return */ - String getIDToken(String subject, String audience, int validitySeconds, OIDCTransaction transaction); + String getIDToken(String subject, String audience, int validitySeconds, OIDCTransaction transaction, String nonce); } diff --git a/esignet-service/src/main/resources/application-default.properties b/esignet-service/src/main/resources/application-default.properties index ed6c12d78..8f9dcfdac 100644 --- a/esignet-service/src/main/resources/application-default.properties +++ b/esignet-service/src/main/resources/application-default.properties @@ -173,7 +173,7 @@ mosip.esignet.cache.security.secretkey.reference-id=TRANSACTION_CACHE mosip.esignet.cache.security.algorithm-name=AES/ECB/PKCS5Padding mosip.esignet.cache.key.hash.algorithm=SHA3-256 -mosip.esignet.cache.names=clientdetails,preauth,authenticated,authcodegenerated,userinfo,linkcodegenerated,linked,linkedcode,linkedauth,consented,authtokens,bindingtransaction,apiratelimit,blocked,halted +mosip.esignet.cache.names=clientdetails,preauth,authenticated,authcodegenerated,userinfo,linkcodegenerated,linked,linkedcode,linkedauth,consented,authtokens,bindingtransaction,apiratelimit,blocked,halted,nonce # 'simple' cache type is only applicable only for Non-Production setup spring.cache.type=redis @@ -199,9 +199,11 @@ mosip.esignet.cache.size={'clientdetails' : 200, \ 'bindingtransaction': 200, \ 'apiratelimit' : 500, \ 'blocked': 500, \ -'halted' : 500 } +'halted' : 500,\ +'nonce' : 500 } # Cache expire in seconds is applicable for both 'simple' and 'Redis' cache type +# TTL of 'authtokens' cache depends on the auth token expire time acquired from IAM / MOSIP authmanager. mosip.esignet.cache.expire-in-seconds={'clientdetails' : 86400, \ 'preauth': ${mosip.esignet.preauthentication-expire-in-secs},\ 'authenticated': ${mosip.esignet.authentication-expire-in-secs}, \ @@ -212,11 +214,12 @@ mosip.esignet.cache.expire-in-seconds={'clientdetails' : 86400, \ 'linkedcode': ${mosip.esignet.link-code-expire-in-secs}, \ 'linkedauth' : ${mosip.esignet.authentication-expire-in-secs}, \ 'consented': 600, \ -'authtokens': 86400, \ +'authtokens': 7200, \ 'bindingtransaction': 600, \ 'apiratelimit' : 180, \ 'blocked': 300, \ -'halted' : ${mosip.esignet.signup.halt.expire-seconds} } +'halted' : ${mosip.esignet.signup.halt.expire-seconds}, \ +'nonce' : 86400 } ## ------------------------------------------ Discovery openid-configuration ------------------------------------------- diff --git a/esignet-service/src/test/java/io/mosip/esignet/flows/AuthCodeFlowTest.java b/esignet-service/src/test/java/io/mosip/esignet/flows/AuthCodeFlowTest.java index 8e8975cea..cd557038a 100644 --- a/esignet-service/src/test/java/io/mosip/esignet/flows/AuthCodeFlowTest.java +++ b/esignet-service/src/test/java/io/mosip/esignet/flows/AuthCodeFlowTest.java @@ -44,10 +44,15 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisScriptingCommands; +import org.springframework.data.redis.connection.ReturnType; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -65,6 +70,7 @@ import java.util.Map; import static io.mosip.esignet.core.constants.Constants.UTC_DATETIME_PATTERN; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; @@ -121,6 +127,17 @@ public class AuthCodeFlowTest { public void init() throws Exception { createOIDCClient(clientId, clientJWK.toPublicJWK(), replyingPartyId); log.info("Successfully create OIDC Client {}", clientId); + + RedisScriptingCommands redisScriptingCommands = Mockito.mock(RedisScriptingCommands.class); + RedisConnection redisConnection = Mockito.mock(RedisConnection.class); + RedisConnectionFactory redisConnectionFactory = Mockito.mock(RedisConnectionFactory.class); + when(redisConnectionFactory.getConnection()).thenReturn(redisConnection); + when(redisConnection.scriptingCommands()).thenReturn(redisScriptingCommands); + when(redisScriptingCommands.evalSha(anyString(), any(ReturnType.class), anyInt(), any(), any())).thenReturn(1L); + + ReflectionTestUtils.setField(cacheUtilService, "redisConnectionFactory", redisConnectionFactory); + ReflectionTestUtils.setField(cacheUtilService, "nonceScriptHash", "nonceScriptHash"); + ReflectionTestUtils.setField(cacheUtilService, "nonceValidity", 86400); } @Test diff --git a/esignet-service/src/test/java/io/mosip/esignet/flows/AuthorizationAPIFlowTest.java b/esignet-service/src/test/java/io/mosip/esignet/flows/AuthorizationAPIFlowTest.java index 2aff68eca..ccbb42fc3 100644 --- a/esignet-service/src/test/java/io/mosip/esignet/flows/AuthorizationAPIFlowTest.java +++ b/esignet-service/src/test/java/io/mosip/esignet/flows/AuthorizationAPIFlowTest.java @@ -43,8 +43,13 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisScriptingCommands; +import org.springframework.data.redis.connection.ReturnType; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -60,6 +65,9 @@ import static io.mosip.esignet.api.util.ErrorConstants.AUTH_FAILED; import static io.mosip.esignet.core.constants.Constants.UTC_DATETIME_PATTERN; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -104,6 +112,20 @@ public class AuthorizationAPIFlowTest { private JWK clientJWK = TestUtil.generateJWK_RSA(); private boolean created = false; + @Before + public void init() { + RedisScriptingCommands redisScriptingCommands = Mockito.mock(RedisScriptingCommands.class); + RedisConnection redisConnection = Mockito.mock(RedisConnection.class); + RedisConnectionFactory redisConnectionFactory = Mockito.mock(RedisConnectionFactory.class); + when(redisConnectionFactory.getConnection()).thenReturn(redisConnection); + when(redisConnection.scriptingCommands()).thenReturn(redisScriptingCommands); + when(redisScriptingCommands.evalSha(anyString(), any(ReturnType.class), anyInt(), any(), any())).thenReturn(1L); + + ReflectionTestUtils.setField(cacheUtilService, "redisConnectionFactory", redisConnectionFactory); + ReflectionTestUtils.setField(cacheUtilService, "nonceScriptHash", "nonceScriptHash"); + ReflectionTestUtils.setField(cacheUtilService, "nonceValidity", 86400); + } + @Test public void invalidClientId_thenFail() throws Exception { diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java index fa8b44298..2cfba9af5 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java @@ -28,6 +28,7 @@ import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.util.Pair; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -232,7 +233,7 @@ protected KycAuthResult handleInternalAuthenticateRequest(@NonNull AuthChallenge String subject = jwt.getJWTClaimsSet().getSubject(); //compares individual from auth request against subject from jwt token. - if(!individualId.equals(subject)){ + if(!individualId.equals(subject)) { throw new EsignetException(INVALID_INDIVIDUAL_ID); } @@ -241,9 +242,14 @@ protected KycAuthResult handleInternalAuthenticateRequest(@NonNull AuthChallenge .findFirst(); OIDCTransaction haltedTransaction = cacheUtilService.getHaltedTransaction(subject); + //Checks to confirm that the ID token is not mis-used or re-used //Validate if cookie is present with token subject as name and halted transaction is present in cache - if(result.isPresent() && haltedTransaction != null && haltedTransaction.getServerNonce().equals( - result.get().getValue().split(SERVER_NONCE_SEPARATOR)[0])) { + //validate if the server nonce in the halted transaction is same as the nonce in the ID token + //validate if the nonce in the ID token is same as the nonce in the current OIDC transaction + if(result.isPresent() && haltedTransaction != null && + haltedTransaction.getServerNonce().equals(result.get().getValue().split(SERVER_NONCE_SEPARATOR)[0]) && + haltedTransaction.getServerNonce().equals(jwt.getJWTClaimsSet().getStringClaim(TokenService.NONCE)) && + transaction.getNonce().equals(jwt.getJWTClaimsSet().getStringClaim(TokenService.NONCE))) { transaction.setIndividualId(haltedTransaction.getIndividualId()); KycAuthResult kycAuthResult = new KycAuthResult(); kycAuthResult.setKycToken(subject); @@ -402,7 +408,7 @@ private String getKeyAlias(String keyAppId, String keyRefId) { throw new EsignetException(NO_UNIQUE_ALIAS); } - protected String validateAndGetSubject(String clientId, String idTokenHint) { + protected Pair validateAndGetSubjectAndNonce(String clientId, String idTokenHint) { try { String[] jwtParts = idTokenHint.split("\\."); if (jwtParts.length == 3) { @@ -411,7 +417,7 @@ protected String validateAndGetSubject(String clientId, String idTokenHint) { String audience = payloadJson.getString(TokenService.AUD); if(!signupIDTokenAudience.equals(audience) || !signupIDTokenAudience.equals(clientId)) throw new EsignetException(ErrorConstants.INVALID_ID_TOKEN_HINT); - return payloadJson.getString(TokenService.SUB); + return Pair.of(payloadJson.getString(TokenService.SUB), payloadJson.getString(TokenService.NONCE)); } } catch (Exception e) { log.error("Failed to parse the given IDTokenHint as JWT", e); diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java index f7f826bd4..e4e8d34fb 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java @@ -153,14 +153,15 @@ public OAuthDetailResponseV2 getOauthDetailsV2(OAuthDetailRequestV2 oauthDetailR public OAuthDetailResponseV2 getOauthDetailsV3(OAuthDetailRequestV3 oauthDetailReqDto, HttpServletRequest httpServletRequest) throws EsignetException { //id_token_hint is an optional parameter, if provided then it is expected to be a valid JWT if (oauthDetailReqDto.getIdTokenHint() != null) { - String subject = authorizationHelperService.validateAndGetSubject(oauthDetailReqDto.getClientId(), oauthDetailReqDto.getIdTokenHint()); + Pair pair = authorizationHelperService.validateAndGetSubjectAndNonce(oauthDetailReqDto.getClientId(), oauthDetailReqDto.getIdTokenHint()); if(httpServletRequest.getCookies() == null) throw new EsignetException(ErrorConstants.INVALID_ID_TOKEN_HINT); - Optional result = Arrays.stream(httpServletRequest.getCookies()).filter(x -> x.getName().equals(subject)).findFirst(); + Optional result = Arrays.stream(httpServletRequest.getCookies()).filter(x -> x.getName().equals(pair.getFirst())).findFirst(); if (result.isEmpty()) { throw new EsignetException(ErrorConstants.INVALID_ID_TOKEN_HINT); } String[] parts = result.get().getValue().split(SERVER_NONCE_SEPARATOR); + oauthDetailReqDto.setNonce(pair.getSecond()); oauthDetailReqDto.setState(parts.length == 2? parts[1] : result.get().getValue()); } return getOauthDetailsV2(oauthDetailReqDto); @@ -300,7 +301,8 @@ public SignupRedirectResponse prepareSignupRedirect(SignupRedirectRequest signup SignupRedirectResponse signupRedirectResponse = new SignupRedirectResponse(); signupRedirectResponse.setTransactionId(signupRedirectRequest.getTransactionId()); - signupRedirectResponse.setIdToken(tokenService.getIDToken(signupRedirectRequest.getTransactionId(), signupIDTokenAudience, signupIDTokenValidity, oidcTransaction)); + signupRedirectResponse.setIdToken(tokenService.getIDToken(signupRedirectRequest.getTransactionId(), signupIDTokenAudience, signupIDTokenValidity, + oidcTransaction, oidcTransaction.getServerNonce())); //Move the transaction to halted transaction cacheUtilService.setHaltedTransaction(signupRedirectRequest.getTransactionId(), oidcTransaction); @@ -395,6 +397,7 @@ private Pair checkAndBuildOIDCTransaction( OAuthDetailResponse oAuthDetailResponse) { log.info("nonce : {} Valid client id found, proceeding to validate redirect URI", oauthDetailReqDto.getNonce()); IdentityProviderUtil.validateRedirectURI(clientDetailDto.getRedirectUris(), oauthDetailReqDto.getRedirectUri()); + validateNonce(oauthDetailReqDto.getNonce()); //Resolve the final set of claims based on registered and request parameter. Claims resolvedClaims = claimsHelperService.resolveRequestedClaims(oauthDetailReqDto, clientDetailDto); @@ -503,6 +506,12 @@ private String getAuthTransactionId(String oidcTransactionId) { return new String(authTransactionIdBytes); } + private void validateNonce(String nonce) { + if(nonce == null || nonce.isBlank()) + return; + if(cacheUtilService.checkNonce(nonce.trim()) == 0L) + throw new EsignetException(ErrorConstants.INVALID_REQUEST); + } } diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java index bf6bf1354..6e954eac3 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java @@ -13,13 +13,18 @@ import io.mosip.esignet.core.util.IdentityProviderUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.ReturnType; import org.springframework.stereotype.Service; +import java.nio.charset.StandardCharsets; + import static io.mosip.esignet.core.util.IdentityProviderUtil.ALGO_SHA3_256; @@ -27,9 +32,24 @@ @Service public class CacheUtilService { + private static final String NONCE_CHECK_SCRIPT = "if redis.call(\"EXISTS\", KEYS[1]) == 0 then\n" + + " redis.call(\"SETEX\", KEYS[1], tonumber(ARGV[1]), \"1\")\n" + + " return 1\n" + + "else\n" + + " return 0\n" + + "end"; + private String nonceScriptHash = null; + private static String NONCE_KEY = "nonce::%s"; + + @Value("${mosip.esignet.nonce-expire-seconds:86400}") + private int nonceValidity; + @Autowired CacheManager cacheManager; + @Autowired + private RedisConnectionFactory redisConnectionFactory; + @Cacheable(value = Constants.PRE_AUTH_SESSION_CACHE, key = "#transactionId") public OIDCTransaction setTransaction(String transactionId, OIDCTransaction oidcTransaction) { return oidcTransaction; @@ -75,6 +95,24 @@ public void removeHaltedTransaction(String transactionId) { log.debug("Evicting entry from HALTED_CACHE"); } + public long checkNonce(String nonce) { + if (redisConnectionFactory.getConnection() != null) { + if (nonceScriptHash == null) { + nonceScriptHash = redisConnectionFactory.getConnection().scriptingCommands().scriptLoad(NONCE_CHECK_SCRIPT.getBytes()); + } + log.info("Running NONCE_CHECK_SCRIPT script: {}", nonceScriptHash); + final String key = String.format(NONCE_KEY, nonce); + return redisConnectionFactory.getConnection().scriptingCommands().evalSha( + nonceScriptHash, + ReturnType.INTEGER, + 1, // Number of keys + key.getBytes(), // The Redis hash name (key) + String.valueOf(nonceValidity).getBytes(StandardCharsets.UTF_8) // ttl + ); + } + return 0; + } + //---------------------------------------------- Linked authorization ---------------------------------------------- @CacheEvict(value = Constants.PRE_AUTH_SESSION_CACHE, key = "#transactionId") diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java index 92d6c87f0..f2e8fcd3c 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java @@ -40,7 +40,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import java.util.*; @@ -92,15 +91,15 @@ public class TokenServiceImpl implements TokenService { @Override public String getIDToken(@NonNull OIDCTransaction transaction) { JSONObject payload = buildIDToken(transaction.getPartnerSpecificUserToken(), - transaction.getClientId(), idTokenExpireSeconds, transaction); + transaction.getClientId(), idTokenExpireSeconds, transaction, null); payload.put(ACCESS_TOKEN_HASH, transaction.getAHash()); return getSignedJWT(Constants.OIDC_SERVICE_APP_ID, payload); } @Override public String getIDToken(@NonNull String subject, @NonNull String audience, int validitySeconds, - @NonNull OIDCTransaction transaction) { - JSONObject payload = buildIDToken(subject, audience, validitySeconds, transaction); + @NonNull OIDCTransaction transaction, String nonce) { + JSONObject payload = buildIDToken(subject, audience, validitySeconds, transaction, nonce); return getSignedJWT(Constants.OIDC_SERVICE_APP_ID, payload); } @@ -213,7 +212,7 @@ public String getSignedJWT(String applicationId, JSONObject payload) { } private JSONObject buildIDToken(String subject, String audience, int validitySeconds, - OIDCTransaction transaction) { + OIDCTransaction transaction, String nonce) { JSONObject payload = new JSONObject(); payload.put(ISS, issuerId); payload.put(SUB, subject); @@ -222,7 +221,7 @@ private JSONObject buildIDToken(String subject, String audience, int validitySec payload.put(IAT, issueTime); payload.put(EXP, issueTime + (validitySeconds<=0 ? 3600 : validitySeconds)); payload.put(AUTH_TIME, transaction.getAuthTimeInSeconds()); - payload.put(NONCE, transaction.getNonce()); + payload.put(NONCE, nonce == null ? transaction.getNonce() : nonce); List acrs = authenticationContextClassRefUtil.getACRs(transaction.getProvidedAuthFactors()); payload.put(ACR, String.join(SPACE, acrs)); return payload; diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java index d12b30197..d088b52e8 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java @@ -503,16 +503,19 @@ public void testHandleInternalAuthenticateRequest_ValidDetails_thenPass(){ ReflectionTestUtils.setField(authorizationHelperService, "objectMapper",objectMapper); AuthChallenge authChallenge = new AuthChallenge(); - authChallenge.setChallenge("eyJ0b2tlbiI6ImV5SmhiR2NpT2lKSVV6STFOaUo5LmV5SnpkV0lpT2lKemRXSnFaV04wSW4wLjl0MG5GMkNtVWZaeTlCYlA3cjM4bElhSlJSeTNaSk41MnBRNlpLSl9qVWMifQ=="); + authChallenge.setChallenge("eyJ0b2tlbiI6ImV5SmhiR2NpT2lKSVV6STFOaUo5LmV5SnpkV0lpT2lKemRXSnFaV04wSWl3aWJtOXVZMlVpT2lKelpYSjJaWEl0Ym05dVkyVWlmUS5CcU5FWF82YUhIc0J2MDVzc0ZqaXVjZ0dzQTZYSW1RWUxWaDZseXFXMXM0In0="); OIDCTransaction transaction = new OIDCTransaction(); transaction.setIndividualId("individualId"); + transaction.setNonce("server-nonce"); Mockito.doNothing().when(tokenService).verifyIdToken(any(), any()); - Mockito.when(httpServletRequest.getCookies()).thenReturn(createMockCookies("subject")); + + Cookie cookie = new Cookie("subject", "server-nonce".concat(SERVER_NONCE_SEPARATOR).concat("path-fragment")); + Mockito.when(httpServletRequest.getCookies()).thenReturn(new Cookie[]{cookie}); OIDCTransaction haltedTransaction = new OIDCTransaction(); haltedTransaction.setIndividualId("individualId"); haltedTransaction.setTransactionId("transactionId"); - haltedTransaction.setServerNonce("subject"); + haltedTransaction.setServerNonce("server-nonce"); Mockito.when(cacheUtilService.getHaltedTransaction(Mockito.anyString())).thenReturn(haltedTransaction); KycAuthResult result = authorizationHelperService.handleInternalAuthenticateRequest(authChallenge, "subject", transaction, httpServletRequest); @@ -569,10 +572,4 @@ public void testHandleInternalAuthenticateRequest_NoHaltedTransaction_thenFail() Assert.assertEquals("auth_failed",e.getErrorCode()); } } - - private Cookie[] createMockCookies(String subject) { - Cookie cookie = new Cookie(subject, "subject"); - return new Cookie[]{cookie}; - } - } diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java index 8e0fc9071..5f4ee6125 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java @@ -195,6 +195,7 @@ public void getOauthDetails_withNullClaimsInDbAndNullClaimsInReq_thenPass() thro oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:static-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV1 oauthDetailResponse = authorizationServiceImpl.getOauthDetails(oauthDetailRequest); @@ -227,6 +228,7 @@ public void getOauthDetails_withNullClaimsInDbAndValidClaimsInReq_thenPass() thr oauthDetailRequest.setAcrValues("level4"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:static-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV1 oauthDetailResponse = authorizationServiceImpl.getOauthDetails(oauthDetailRequest); @@ -259,6 +261,7 @@ public void getOauthDetails_withValidClaimsInDbAndValidClaimsInReq_thenPass() th when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:static-code"})).thenReturn(new ArrayList<>()); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); OAuthDetailResponseV1 oauthDetailResponse = authorizationServiceImpl.getOauthDetails(oauthDetailRequest); Assert.assertNotNull(oauthDetailResponse); @@ -289,6 +292,7 @@ public void getOauthDetails_withValidClaimsInDbAndInValidClaimsInReq_thenPass() oauthDetailRequest.setAcrValues("mosip:idp:acr:generated-code"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:generated-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV1 oauthDetailResponse = authorizationServiceImpl.getOauthDetails(oauthDetailRequest); @@ -313,6 +317,7 @@ public void getOauthDetails_withNullAcrInDB_thenFail() throws Exception { oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); try { authorizationServiceImpl.getOauthDetails(oauthDetailRequest); @@ -340,6 +345,7 @@ public void getOauthDetails_withValidAcrInDBAndNullAcrInReq_thenPass() throws Ex oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); authFactors.add(Collections.emptyList()); @@ -369,6 +375,7 @@ public void getOauthDetails_withValidAcrInDBAndValidAcrInReq_thenPass() throws E oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:linked-wallet"})).thenReturn(authFactors); @@ -399,6 +406,7 @@ public void getOauthDetails_withValidAcrInDBAndValidAcrInReq_orderOfPrecedencePr //NOTE: if order differs then below mock will not be used, hence will not return null when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:linked-wallet", "mosip:idp:acr:generated-code"})).thenReturn(null); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); OAuthDetailResponseV1 oauthDetailResponse = authorizationServiceImpl.getOauthDetails(oauthDetailRequest); Assert.assertNotNull(oauthDetailResponse); @@ -428,6 +436,7 @@ public void getOauthDetails_withValidAcrInDBAndValidAcrClaimInReq_thenPass() thr oauthDetailRequest.setAcrValues("mosip:idp:acr:biometrics mosip:idp:acr:generated-code"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); //Highest priority is given to ACR in claims request parameter @@ -459,6 +468,7 @@ public void getOauthDetails_withValidClaimsInDbAndValidClaimsInReqAndNoOPENIDSco oauthDetailRequest.setAcrValues("mosip:idp:acr:wallet"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); try { authorizationServiceImpl.getOauthDetails(oauthDetailRequest); @@ -540,6 +550,7 @@ public void getOauthDetailsV2_withNullClaimsInDbAndNullClaimsInReq_thenPass() th oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:static-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV2 oauthDetailResponseV2 = authorizationServiceImpl.getOauthDetailsV2(oauthDetailRequest); @@ -571,6 +582,7 @@ public void getOauthDetailsV2_withNullClaimsInDbAndValidClaimsInReq_thenPass() t oauthDetailRequest.setAcrValues("level4"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:static-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV2 oauthDetailResponseV2 = authorizationServiceImpl.getOauthDetailsV2(oauthDetailRequest); @@ -602,6 +614,7 @@ public void getOauthDetailsV2_withValidClaimsInDbAndValidClaimsInReq_thenPass() oauthDetailRequest.setAcrValues("mosip:idp:acr:static-code"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:static-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV2 oauthDetailResponseV2 = authorizationServiceImpl.getOauthDetailsV2(oauthDetailRequest); @@ -633,6 +646,7 @@ public void getOauthDetailsV2_withValidClaimsInDbAndInValidClaimsInReq_thenPass( oauthDetailRequest.setAcrValues("mosip:idp:acr:generated-code"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:generated-code"})).thenReturn(new ArrayList<>()); OAuthDetailResponseV2 oauthDetailResponseV2 = authorizationServiceImpl.getOauthDetailsV2(oauthDetailRequest); @@ -657,6 +671,7 @@ public void getOauthDetailsV2_withNullAcrInDB_thenFail() throws Exception { oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); try { authorizationServiceImpl.getOauthDetailsV2(oauthDetailRequest); @@ -684,6 +699,7 @@ public void getOauthDetailsV2_withValidAcrInDBAndNullAcrInReq_thenPass() throws oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); authFactors.add(Collections.emptyList()); @@ -713,6 +729,7 @@ public void getOauthDetailsV2_withValidAcrInDBAndValidAcrInReq_thenPass() throws oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:linked-wallet"})).thenReturn(authFactors); @@ -740,6 +757,7 @@ public void getOauthDetailsV2_withValidAcrInDBAndValidAcrInReq_orderOfPrecedence oauthDetailRequest.setNonce("test-nonce"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); //NOTE: if order differs then below mock will not be used, hence will not return null when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:linked-wallet", "mosip:idp:acr:generated-code"})).thenReturn(null); @@ -772,6 +790,7 @@ public void getOauthDetailsV2_withValidAcrInDBAndValidAcrClaimInReq_thenPass() t oauthDetailRequest.setAcrValues("mosip:idp:acr:biometrics mosip:idp:acr:generated-code"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); //Highest priority is given to ACR in claims request parameter @@ -803,6 +822,7 @@ public void getOauthDetailsV2_withValidClaimsInDbAndValidClaimsInReqAndNoOPENIDS oauthDetailRequest.setAcrValues("mosip:idp:acr:wallet"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); try { authorizationServiceImpl.getOauthDetailsV2(oauthDetailRequest); @@ -836,6 +856,7 @@ public void getOauthDetailsV2_withoutPKCE_thenFail() { oauthDetailRequest.setScope("sample_ldp_vc"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); //Highest priority is given to ACR in claims request parameter @@ -873,6 +894,7 @@ public void getOauthDetailsV3_withValidIDTokenHint_thenPass() { oauthDetailRequest.setAcrValues("mosip:idp:acr:biometrics mosip:idp:acr:generated-code"); when(clientManagementService.getClientDetails(oauthDetailRequest.getClientId())).thenReturn(clientDetail); + when(cacheUtilService.checkNonce(anyString())).thenReturn(1L); List> authFactors = new ArrayList<>(); authFactors.add(Collections.emptyList()); //Highest priority is given to ACR in claims request parameter @@ -1328,10 +1350,10 @@ public void authenticateV3_withIDTokenInvalidIndividualId_thenFail() { public void authenticateV3_withIDToken_thenPass() { String transactionId = "test-transaction"; String individualId = "subject"; - when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(createIdpTransaction( - new String[]{"mosip:idp:acr:id-token"})); - when(cacheUtilService.updateIndividualIdHashInPreAuthCache(transactionId, individualId)).thenReturn(createIdpTransaction( - new String[]{"mosip:idp:acr:id-token"})); + OIDCTransaction oidcTransaction = createIdpTransaction(new String[]{"mosip:idp:acr:id-token"}); + oidcTransaction.setNonce("server-nonce"); + when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(oidcTransaction); + when(cacheUtilService.updateIndividualIdHashInPreAuthCache(transactionId, individualId)).thenReturn(oidcTransaction); List> allAuthFactors=new ArrayList<>(); allAuthFactors.add(getAuthFactors("mosip:idp:acr:id-token")); @@ -1345,16 +1367,17 @@ public void authenticateV3_withIDToken_thenPass() { List authChallenges = new ArrayList<>(); AuthChallenge authChallenge = new AuthChallenge(); authChallenge.setAuthFactorType("IDT"); - authChallenge.setChallenge("eyJ0b2tlbiI6ImV5SmhiR2NpT2lKSVV6STFOaUo5LmV5SnpkV0lpT2lKemRXSnFaV04wSW4wLjl0MG5GMkNtVWZaeTlCYlA3cjM4bElhSlJSeTNaSk41MnBRNlpLSl9qVWMifQ=="); + authChallenge.setChallenge("eyJ0b2tlbiI6ImV5SmhiR2NpT2lKSVV6STFOaUo5LmV5SnpkV0lpT2lKemRXSnFaV04wSWl3aWJtOXVZMlVpT2lKelpYSjJaWEl0Ym05dVkyVWlmUS5CcU5FWF82YUhIc0J2MDVzc0ZqaXVjZ0dzQTZYSW1RWUxWaDZseXFXMXM0In0="); authChallenges.add(authChallenge); authRequest.setChallengeList(authChallenges); - Mockito.when(httpServletRequest.getCookies()).thenReturn(new Cookie[]{new Cookie("subject", "subject")}); + Mockito.when(httpServletRequest.getCookies()).thenReturn(new Cookie[]{new Cookie("subject", + "server-nonce".concat(SERVER_NONCE_SEPARATOR).concat("sanitized-path-fragment"))}); OIDCTransaction haltedTransaction = new OIDCTransaction(); haltedTransaction.setIndividualId("individualId"); haltedTransaction.setTransactionId("transactionId"); - haltedTransaction.setServerNonce("subject"); + haltedTransaction.setServerNonce("server-nonce"); Mockito.when(cacheUtilService.getHaltedTransaction(Mockito.anyString())).thenReturn(haltedTransaction); AuthResponseV2 authResponseV2 = authorizationServiceImpl.authenticateUserV3(authRequest, httpServletRequest); diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java index 739d2ff3a..e7ec0c4cf 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java @@ -70,6 +70,7 @@ public void getIDToken_test() throws JSONException { transaction.setPartnerSpecificUserToken("psut"); transaction.setNonce("nonce"); transaction.setAuthTimeInSeconds(22); + transaction.setServerNonce("server-nonce"); transaction.setAHash("access-token-hash"); transaction.setProvidedAuthFactors(new HashSet<>()); Mockito.when(authenticationContextClassRefUtil.getACRs(Mockito.any())).thenReturn(Arrays.asList("generated-code", "static-code")); @@ -84,13 +85,13 @@ public void getIDToken_test() throws JSONException { Assert.assertEquals("generated-code static-code", jsonObject.get(ACR)); Assert.assertEquals("test-issuer", jsonObject.get(ISS)); - token = tokenService.getIDToken("subject", "audience",30, transaction); + token = tokenService.getIDToken("subject", "audience",30, transaction, transaction.getServerNonce()); Assert.assertNotNull(token); jsonObject = new JSONObject(new String(IdentityProviderUtil.b64Decode(token))); Assert.assertEquals("audience", jsonObject.get(AUD)); Assert.assertEquals("subject", jsonObject.get(SUB)); Assert.assertEquals(transaction.getAuthTimeInSeconds(), jsonObject.getLong(AUTH_TIME)); - Assert.assertEquals(transaction.getNonce(), jsonObject.get(NONCE)); + Assert.assertEquals(transaction.getServerNonce(), jsonObject.get(NONCE)); Assert.assertEquals("generated-code static-code", jsonObject.get(ACR)); Assert.assertEquals("test-issuer", jsonObject.get(ISS)); }