Skip to content

Commit

Permalink
Mosip 22165 add scope based authorization interface and junit tests (m…
Browse files Browse the repository at this point in the history
…osip#1338)

* Added token validation after oidc login with auth code flow

* Added token validation after oidc login with auth code flow

* Removed unused constants

* Added javadoc

* Revert unused changes to kernel core

* Test fix

* Test fixes and code coverage

* Added scope validator and support to scopes in AuthUserDetails

* Fix junit tests

* Fixed NPE on validating ID Token
  • Loading branch information
loganathan-sekaran authored Jun 23, 2022
1 parent 8300f78 commit 43c44c8
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 101 deletions.
13 changes: 13 additions & 0 deletions kernel/kernel-authcodeflowproxy-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
<micrometer.registry.prometheus.version>1.4.2</micrometer.registry.prometheus.version>
<kernel.core.version>1.2.1-SNAPSHOT</kernel.core.version>
<jacoco.maven.plugin.version>0.8.5</jacoco.maven.plugin.version>
<powermock.version>2.0.0</powermock.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -239,6 +240,18 @@
<artifactId>jwks-rsa</artifactId>
<version>0.18.0</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<distributionManagement>
<snapshotRepository>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand All @@ -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,
Expand Down Expand Up @@ -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("#")) {
Expand All @@ -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()){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@ public ResponseEntity<ResponseWrapper<ServiceError>> clientException(
public ResponseEntity<ResponseWrapper<ServiceError>> 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)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -101,8 +103,7 @@ public ImmutablePair<Boolean, AuthErrorCode> 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);
Expand All @@ -121,9 +122,8 @@ public ImmutablePair<Boolean, AuthErrorCode> 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);
Expand All @@ -132,18 +132,17 @@ public ImmutablePair<Boolean, AuthErrorCode> isTokenValid(DecodedJWT decodedJWT)
}

private boolean validateAudience(DecodedJWT decodedJWT) {
boolean matchFound = false;
if (validateAudClaim) {
boolean matchFound;

List<String> tokenAudience = decodedJWT.getAudience();
matchFound = tokenAudience.stream().anyMatch(allowedAudience::contains);
List<String> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 43c44c8

Please sign in to comment.