diff --git a/kernel/kernel-authcodeflowproxy-api/pom.xml b/kernel/kernel-authcodeflowproxy-api/pom.xml
index 5e603c5d65a..3ba3ede2f85 100644
--- a/kernel/kernel-authcodeflowproxy-api/pom.xml
+++ b/kernel/kernel-authcodeflowproxy-api/pom.xml
@@ -222,6 +222,7 @@
1.4.2
1.2.1-SNAPSHOT
0.8.5
+ 2.0.0
@@ -239,6 +240,18 @@
jwks-rsa
0.18.0
+
+ org.powermock
+ powermock-api-mockito2
+ ${powermock.version}
+ test
+
+
+ org.powermock
+ powermock-module-junit4
+ ${powermock.version}
+ test
+
diff --git a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/controller/LoginController.java b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/controller/LoginController.java
index 449285ad1a3..91cabb78112 100644
--- a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/controller/LoginController.java
+++ b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/controller/LoginController.java
@@ -33,6 +33,8 @@
@RestController
public class LoginController {
+ private static final String ID_TOKEN = "id_token";
+
private final static Logger LOGGER= LoggerFactory.getLogger(LoginController.class);
@Value("${auth.token.header:Authorization}")
@@ -46,7 +48,10 @@ public class LoginController {
private LoginService loginService;
@Autowired
- private ValidateTokenHelper validateTokenHelper;
+ private ValidateTokenHelper validateTokenHelper;
+
+ @Value("${auth.validate.id-token:false}")
+ private boolean validateIdToken;
@GetMapping(value = "/login/{redirectURI}")
public void login(@CookieValue(name = "state", required = false) String state,
@@ -88,11 +93,17 @@ public void loginRedirect(@PathVariable("redirectURI") String redirectURI, @Requ
redirectURI);
String accessToken = jwtResponseDTO.getAccessToken();
validateToken(accessToken);
- String idToken = jwtResponseDTO.getIdToken();
- validateToken(idToken);
Cookie cookie = loginService.createCookie(accessToken);
res.addCookie(cookie);
- res.addCookie(new Cookie("id_token", idToken));
+ if(validateIdToken) {
+ String idToken = jwtResponseDTO.getIdToken();
+ if(idToken == null) {
+ throw new ClientException(Errors.TOKEN_NOTPRESENT_ERROR.getErrorCode(),
+ Errors.TOKEN_NOTPRESENT_ERROR.getErrorMessage() + ": " + ID_TOKEN);
+ }
+ validateToken(idToken);
+ res.addCookie(new Cookie(ID_TOKEN, idToken));
+ }
res.setStatus(302);
String url = new String(Base64.decodeBase64(redirectURI.getBytes()));
if(url.contains("#")) {
@@ -103,7 +114,7 @@ public void loginRedirect(@PathVariable("redirectURI") String redirectURI, @Requ
throw new ServiceException(Errors.ALLOWED_URL_EXCEPTION.getErrorCode(), Errors.ALLOWED_URL_EXCEPTION.getErrorMessage());
}
res.sendRedirect(url);
- }
+ }
private void validateToken(String accessToken) {
if(!validateTokenHelper.isTokenValid(accessToken).getKey()){
diff --git a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/exception/AuthCodeProxyExceptionHandler.java b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/exception/AuthCodeProxyExceptionHandler.java
index 97a5b85209b..adbf343b7d5 100644
--- a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/exception/AuthCodeProxyExceptionHandler.java
+++ b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/exception/AuthCodeProxyExceptionHandler.java
@@ -46,8 +46,14 @@ public ResponseEntity> clientException(
public ResponseEntity> servieException(
HttpServletRequest httpServletRequest, final ServiceException e) throws IOException {
ExceptionUtils.logRootCause(e);
+ HttpStatus status;
+ if(e.getErrorCode().equals(Errors.INVALID_TOKEN.getErrorCode())) {
+ status = HttpStatus.UNAUTHORIZED;
+ } else {
+ status = HttpStatus.OK;
+ }
return new ResponseEntity<>(
- getErrorResponse(httpServletRequest, e.getErrorCode(), e.getErrorText()), HttpStatus.OK);
+ getErrorResponse(httpServletRequest, e.getErrorCode(), e.getErrorText()), status);
}
@ExceptionHandler(AuthenticationServiceException.class)
diff --git a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ScopeValidator.java b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ScopeValidator.java
deleted file mode 100644
index bae4b134674..00000000000
--- a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ScopeValidator.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package io.mosip.kernel.authcodeflowproxy.api.service.validator;
-
-import java.util.List;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-
-import com.auth0.jwt.JWT;
-import com.auth0.jwt.interfaces.DecodedJWT;
-
-import io.mosip.kernel.core.authmanager.authadapter.model.AuthUserDetails;
-/**
- * Validator used to validate the scope in the token
- *
- * @author Loganathan S
- *
- */
-@Component("scopeValidator")
-public class ScopeValidator {
-
- private static final String SCOPE = "scope";
-
- public static boolean hasAllScopes(List scopes) {
- return hasScopes(scopes, Stream::allMatch);
- }
-
- public static boolean hasAnyScopes(List scopes) {
- return hasScopes(scopes, Stream::anyMatch);
- }
-
- public static boolean hasScope(String scope) {
- return hasAllScopes(List.of(scope));
- }
-
- public static boolean hasScopes(List scopes, BiPredicate, Predicate super String>> condition) {
- List extends String> scopesInToken = getScopes();
- return condition.test(scopes.stream(), scopesInToken::contains);
- }
-
- private static List getScopes() {
- Object principal = SecurityContextHolder
- .getContext()
- .getAuthentication().getPrincipal();
- if(principal instanceof AuthUserDetails) {
- AuthUserDetails authUserDetails = (AuthUserDetails) principal;
- String jwtToken = authUserDetails.getToken();
- DecodedJWT decodedJWT = JWT.decode(jwtToken);
- String scpoeClaim = decodedJWT.getClaim(SCOPE).asString();
- List scopes = Stream.of( scpoeClaim.split(" ")).collect(Collectors.toList());
- return scopes;
- }
-
- return List.of();
- }
-
-
-}
diff --git a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ValidateTokenHelper.java b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ValidateTokenHelper.java
index cd0db8c39ff..8113d628907 100644
--- a/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ValidateTokenHelper.java
+++ b/kernel/kernel-authcodeflowproxy-api/src/main/java/io/mosip/kernel/authcodeflowproxy/api/service/validator/ValidateTokenHelper.java
@@ -31,6 +31,8 @@
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
+import com.auth0.jwt.impl.NullClaim;
+import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.mosip.kernel.authcodeflowproxy.api.constants.AuthConstant;
@@ -101,8 +103,7 @@ public ImmutablePair isTokenValid(DecodedJWT decodedJWT)
}
// Second, issuer domain check.
- boolean tokenDomainMatch = getTokenIssuerDomain(decodedJWT);
- if (validateIssuerDomain && !tokenDomainMatch) {
+ if (validateIssuerDomain && !getTokenIssuerDomain(decodedJWT)) {
LOGGER.error(
"Provided Auth Token Issue domain does not match. Throwing Authentication Exception. UserName: "
+ userName);
@@ -121,9 +122,8 @@ public ImmutablePair isTokenValid(DecodedJWT decodedJWT)
}
// Fourth, audience | azp validation.
- boolean matchFound = validateAudience(decodedJWT);
// No match found after comparing audience & azp
- if (!matchFound) {
+ if (validateAudClaim && !validateAudience(decodedJWT)) {
LOGGER.error("Provided Client Id does not match with Aud/AZP. Throwing Authorizaion Exception. UserName: "
+ userName);
return ImmutablePair.of(Boolean.FALSE, AuthErrorCode.FORBIDDEN);
@@ -132,18 +132,17 @@ public ImmutablePair isTokenValid(DecodedJWT decodedJWT)
}
private boolean validateAudience(DecodedJWT decodedJWT) {
- boolean matchFound = false;
- if (validateAudClaim) {
+ boolean matchFound;
- List tokenAudience = decodedJWT.getAudience();
- matchFound = tokenAudience.stream().anyMatch(allowedAudience::contains);
+ List tokenAudience = decodedJWT.getAudience();
+ matchFound = tokenAudience != null && tokenAudience.stream().anyMatch(allowedAudience::contains);
- // comparing with azp.
- String azp = decodedJWT.getClaim(AuthConstant.AZP).asString();
- if (!matchFound) {
- matchFound = allowedAudience.stream().anyMatch(azp::equalsIgnoreCase);
- }
+ // comparing with azp.
+ if (!matchFound) {
+ Claim azp = decodedJWT.getClaim(AuthConstant.AZP);
+ matchFound = azp != null && !(azp instanceof NullClaim) && allowedAudience.stream().anyMatch(azp.asString()::equalsIgnoreCase);
}
+
return matchFound;
}
diff --git a/kernel/kernel-authcodeflowproxy-api/src/main/resources/META-INF/spring.factories b/kernel/kernel-authcodeflowproxy-api/src/main/resources/META-INF/spring.factories
index ad32768515f..3230fb97de2 100644
--- a/kernel/kernel-authcodeflowproxy-api/src/main/resources/META-INF/spring.factories
+++ b/kernel/kernel-authcodeflowproxy-api/src/main/resources/META-INF/spring.factories
@@ -3,5 +3,4 @@ io.mosip.kernel.authcodeflowproxy.api.controller.LoginController,\
io.mosip.kernel.authcodeflowproxy.api.service.impl.LoginServiceImpl,\
io.mosip.kernel.authcodeflowproxy.api.config.AuthCodeProxyConfig,\
io.mosip.kernel.authcodeflowproxy.api.exception.AuthCodeProxyExceptionHandler,\
-io.mosip.kernel.authcodeflowproxy.api.service.validator.ValidateTokenHelper,\
-io.mosip.kernel.authcodeflowproxy.api.service.validator.ScopeValidator
+io.mosip.kernel.authcodeflowproxy.api.service.validator.ValidateTokenHelper
diff --git a/kernel/kernel-authcodeflowproxy-api/src/test/java/io/mosip/kernel/authcodeflowproxy/api/test/controller/AuthProxyControllerTests.java b/kernel/kernel-authcodeflowproxy-api/src/test/java/io/mosip/kernel/authcodeflowproxy/api/test/controller/AuthProxyControllerTests.java
index e42a68e9ba6..bf865be9552 100644
--- a/kernel/kernel-authcodeflowproxy-api/src/test/java/io/mosip/kernel/authcodeflowproxy/api/test/controller/AuthProxyControllerTests.java
+++ b/kernel/kernel-authcodeflowproxy-api/src/test/java/io/mosip/kernel/authcodeflowproxy/api/test/controller/AuthProxyControllerTests.java
@@ -2,7 +2,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.isA;
-import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
@@ -13,6 +13,7 @@
import java.net.URI;
import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -20,20 +21,26 @@
import javax.servlet.http.Cookie;
-import org.apache.commons.lang3.tuple.ImmutablePair;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.boot.test.mock.mockito.SpyBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
@@ -42,6 +49,7 @@
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.mosip.kernel.authcodeflowproxy.api.constants.AuthConstant;
@@ -54,12 +62,18 @@
import io.mosip.kernel.core.exception.ServiceError;
import io.mosip.kernel.core.http.ResponseWrapper;
import io.mosip.kernel.core.util.CryptoUtil;
+import io.mosip.kernel.core.util.DateUtils;
@SpringBootTest(classes = { AuthProxyFlowTestBootApplication.class })
-@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(SpringRunner.class)
+@PowerMockIgnore({ "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.management.*", "com.sun.org.apache.xalan.*" })
+@PrepareForTest(Algorithm.class)
public class AuthProxyControllerTests {
+ private static final int UNAUTHORIZED_STATUS = 401;
+
@Value("${auth.server.admin.validate.url}")
private String validateUrl;
@@ -71,13 +85,19 @@ public class AuthProxyControllerTests {
private MockRestServiceServer mockServer;
- @MockBean
+ @SpyBean
private ValidateTokenHelper validateTokenHelper;
+ @Mock
+ private Algorithm mockAlgo;
+
@Before
- public void init() {
+ public void init() throws Exception {
mockServer = MockRestServiceServer.createServer(restTemplate);
- when(validateTokenHelper.isTokenValid(Mockito.anyString())).thenReturn(ImmutablePair.of(true, null));
+ PowerMockito.mockStatic(Algorithm.class);
+ when(Algorithm.RSA256(Mockito.any(), Mockito.any())).thenReturn(mockAlgo);
+ ReflectionTestUtils.setField(validateTokenHelper, "validateIssuerDomain", false);
+ ReflectionTestUtils.setField(validateTokenHelper, "validateAudClaim", false);
}
@Autowired
@@ -190,8 +210,213 @@ public void loginTest() throws Exception {
@Test
public void loginRedirectTest() throws Exception {
AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
- accessTokenResponse.setAccess_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
- accessTokenResponse.setId_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "http://localhost");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setId_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is3xxRedirection());
+ }
+
+ @Test
+ public void loginRedirectTest_signatureVerification_negative() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "http://localhost");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ doThrow(new SignatureVerificationException(mockAlgo)).when(mockAlgo).verify(Mockito.any());
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is(UNAUTHORIZED_STATUS));
+ }
+
+ @Test
+ public void loginRedirectTest_expiredToken() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().minusDays(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "http://localhost");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is(401));
+ }
+
+ @Test
+ public void loginRedirectTest_domain_match_positive() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "http://localhost");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateIssuerDomain", true);
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setId_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is3xxRedirection());
+ }
+
+ @Test
+ public void loginRedirectTest_invalid_issuer() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "~!::#@///wrongurl");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateIssuerDomain", true);
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is(401));
+ }
+
+ @Test
+ public void loginRedirectTest_domain_match_negative() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "http://someotherdomain");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateIssuerDomain", true);
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is(401));
+ }
+
+ @Test
+ public void loginRedirectTest_aud_match_positive() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withAudience("myapp-client");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateAudClaim", true);
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setId_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is3xxRedirection());
+ }
+
+ @Test
+ public void loginRedirectTest_aud_match_negative_azp_positive() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withAudience("somether-app-client");
+ withExpiresAt.withClaim(AuthConstant.AZP, "myapp-client");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateAudClaim", true);
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setId_token(token);
accessTokenResponse.setExpires_in("111");
mockServer
@@ -208,6 +433,62 @@ public void loginRedirectTest() throws Exception {
.contentType(MediaType.APPLICATION_JSON).cookie(cookie))
.andExpect(status().is3xxRedirection());
}
+
+ @Test
+ public void loginRedirectTest_aud_match_null_azp_null() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateAudClaim", true);
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is(401));
+ }
+
+ @Test
+ public void loginRedirectTest_aud_match_negative_azp_negative() throws Exception {
+ AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withAudience("someother-app-client");
+ withExpiresAt.withClaim(AuthConstant.AZP, "someother-app-client");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ ReflectionTestUtils.setField(validateTokenHelper, "validateAudClaim", true);
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setExpires_in("111");
+
+ mockServer
+ .expect(ExpectedCount.once(),
+ requestTo(new URI(
+ "http://localhost:8080/keycloak/auth/realms/mosip/protocol/openid-connect/token")))
+ .andExpect(method(HttpMethod.POST))
+ .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+ .body(objectMapper.writeValueAsString(accessTokenResponse)));
+
+ Cookie cookie = new Cookie("state", "mockstate");
+ mockMvc.perform(get(
+ "/login-redirect/aHR0cDovL2xvY2FsaG9zdDo1MDAwLw==?state=mockstate&session_state=mock-session-state&code=mockcode")
+ .contentType(MediaType.APPLICATION_JSON).cookie(cookie))
+ .andExpect(status().is(401));
+ }
@Test
public void loginRedirectTestWithHash() throws Exception {
@@ -225,7 +506,7 @@ public void loginRedirectTestWithHash() throws Exception {
accessTokenResponse.setAccess_token(jwtToken);
accessTokenResponse.setId_token(jwtToken);
accessTokenResponse.setExpires_in("111");
-
+
mockServer
.expect(ExpectedCount.once(),
requestTo(new URI(
@@ -296,8 +577,14 @@ public void loginInvalidUUIDTest() throws Exception {
@Test
public void logoutRedirectHostCheckTest() throws Exception {
AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
- accessTokenResponse.setAccess_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
- accessTokenResponse.setId_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
+ Builder withExpiresAt = JWT.create().withExpiresAt(Date.from(DateUtils.getUTCCurrentDateTime().plusHours(1).toInstant(ZoneOffset.UTC)));
+ withExpiresAt.withClaim(AuthConstant.ISSUER, "http://localhost");
+
+ when(mockAlgo.getName()).thenReturn("RSA256");
+ String token = withExpiresAt.withClaim("scope", "aaa bbb").sign(mockAlgo);
+
+ accessTokenResponse.setAccess_token(token);
+ accessTokenResponse.setId_token(token);
accessTokenResponse.setExpires_in("111");
mockServer
@@ -313,7 +600,7 @@ public void logoutRedirectHostCheckTest() throws Exception {
.contentType(MediaType.APPLICATION_JSON).cookie(cookie))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors[0].errorCode", is(Errors.ALLOWED_URL_EXCEPTION.getErrorCode())));
- ;
+
}
}
diff --git a/kernel/kernel-authcodeflowproxy-api/src/test/resources/application-test.properties b/kernel/kernel-authcodeflowproxy-api/src/test/resources/application-test.properties
index 247296b4c58..250dc076b9d 100644
--- a/kernel/kernel-authcodeflowproxy-api/src/test/resources/application-test.properties
+++ b/kernel/kernel-authcodeflowproxy-api/src/test/resources/application-test.properties
@@ -46,3 +46,5 @@ auth.server.admin.audience.claim.validate=false
mosip.iam.certs_endpoint=http://localhost:5000/keycloak/auth/realms/mosip/protocol/openid-connect/certs
+auth.server.admin.allowed.audience=myapp-client
+
diff --git a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/authadapter/model/AuthUserDetails.java b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/authadapter/model/AuthUserDetails.java
index 0dce94919bf..00e2e87f779 100644
--- a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/authadapter/model/AuthUserDetails.java
+++ b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/authadapter/model/AuthUserDetails.java
@@ -1,7 +1,9 @@
package io.mosip.kernel.core.authmanager.authadapter.model;
import java.util.Collection;
+import java.util.Collections;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -18,6 +20,10 @@
public class AuthUserDetails implements UserDetails {
+ public static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
+
+ public static final String ROLE_AUTHORITY_PREFIX = "ROLE_";
+
/**
*
*/
@@ -41,12 +47,30 @@ public AuthUserDetails(MosipUserDto mosipUserDto, String token) {
@Override
public Collection extends GrantedAuthority> getAuthorities() {
- return authorities.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.getAuthority()))
- .collect(Collectors.toList());
- }
-
- public void setAuthorities(Collection extends GrantedAuthority> authorities) {
- this.authorities = authorities;
+ return authorities;
+ }
+
+ private void addAuthorities(Collection extends GrantedAuthority> authorities, String authorityPrefix) {
+ Stream authortiesStream = authorities.stream().map(grantedAuthority -> {
+ String authority = authorityPrefix == null ? grantedAuthority.getAuthority() : authorityPrefix + grantedAuthority.getAuthority();
+ return new SimpleGrantedAuthority(authority);
+ });
+
+ if(this.authorities == null) {
+ this.authorities = Collections.unmodifiableCollection(authortiesStream
+ .collect(Collectors.toList()));
+ } else {
+ this.authorities = Collections.unmodifiableCollection(Stream.concat(this.authorities.stream(), authortiesStream)
+ .collect(Collectors.toList()));
+ }
+ }
+
+ public void addRoleAuthorities(Collection extends GrantedAuthority> authorities) {
+ this.addAuthorities(authorities, ROLE_AUTHORITY_PREFIX);
+ }
+
+ public void addScopeAuthorities(Collection extends GrantedAuthority> authorities) {
+ this.addAuthorities(authorities, SCOPE_AUTHORITY_PREFIX);
}
@Override
diff --git a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/spi/ScopeValidator.java b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/spi/ScopeValidator.java
new file mode 100644
index 00000000000..412073f27b1
--- /dev/null
+++ b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/authmanager/spi/ScopeValidator.java
@@ -0,0 +1,23 @@
+package io.mosip.kernel.core.authmanager.spi;
+
+import java.util.List;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+/**
+ * Validator used to validate the scope in the token
+ *
+ * @author Loganathan S
+ *
+ */
+public interface ScopeValidator {
+
+ public boolean hasAllScopes(List scopes);
+
+ public boolean hasAnyScopes(List scopes);
+
+ public boolean hasScope(String scope);
+
+ public boolean hasScopes(List scopes, BiPredicate, Predicate super String>> condition);
+
+}