Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotfix/http status codes for errors #21

Merged
merged 12 commits into from
Nov 22, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v1.0.12](https://github.com/in2workspace/in2-verifier-api/releases/tag/v1.0.12)
### Fixed
- Unauthorized Http response code for failed validation of VP token

## [v1.0.11](https://github.com/in2workspace/in2-verifier-api/releases/tag/v1.0.11)
### Fixed
- Add cors configuration to allow requests from external wallets, on the endpoints the wallet use.
Expand Down
16 changes: 9 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = 'es.in2'
version = '1.0.11'
version = '1.0.12'

java {
toolchain {
Expand Down Expand Up @@ -46,10 +46,11 @@ sonar {
property "sonar.organization", "in2workspace"
property "sonar.host.url", "https://sonarcloud.io"
property 'sonar.coverage.exclusions',
"src/main/java/es/in2/verifier/VerifierApplication.java, " +
"src/main/java/es/in2/verifier/service/impl/TrustFrameworkServiceImpl.java, " +
"src/main/java/es/in2/verifier/service/impl/CertificateValidationServiceImpl.java, " +
"src/main/java/es/in2/verifier/exception/MismatchOrganizationIdentifierException.java"
"src/main/java/es/in2/vcverifier/VcVerifierApplication.java, " +
"src/main/java/es/in2/vcverifier/service/impl/ClientAssertionValidationServiceImpl.java, " +
"src/main/java/es/in2/vcverifier/service/impl/TrustFrameworkServiceImpl.java, " +
"src/main/java/es/in2/vcverifier/service/impl/CertificateValidationServiceImpl.java, " +
"src/main/java/es/in2/vcverifier/exception/MismatchOrganizationIdentifierException.java"
}
}

Expand All @@ -66,12 +67,12 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// Cryptography
implementation 'com.nimbusds:nimbus-jose-jwt:9.40'
implementation 'org.bitcoinj:bitcoinj-core:0.17-beta1'
implementation 'org.bitcoinj:bitcoinj-core:0.17-alpha5'
implementation 'io.github.novacrypto:Base58:2022.01.17'
//Jackson
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2'
// JSON
implementation 'org.json:json:20240303'
implementation 'org.json:json:20230227'
// QR GENERATOR
implementation 'com.github.kenglxn.QRGen:javase:3.0.1'
// DevTools
Expand Down Expand Up @@ -121,6 +122,7 @@ tasks.jacocoTestReport {
}
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
"**/AuthorizationResponseProcessorServiceImpl.class",
"**/TrustFrameworkServiceImpl.class",
"**/CertificateValidationServiceImpl.class",
"**/MismatchOrganizationIdentifierException.class",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package es.in2.verifier.exception;

public class InvalidVPtokenException extends RuntimeException {

public InvalidVPtokenException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package es.in2.verifier.exception.handler;

import es.in2.verifier.exception.CredentialRevokedException;
import es.in2.verifier.exception.MismatchOrganizationIdentifierException;
import es.in2.verifier.exception.QRCodeGenerationException;
import es.in2.verifier.exception.ResourceNotFoundException;
import es.in2.verifier.exception.InvalidVPtokenException;
import es.in2.verifier.exception.*;
import es.in2.verifier.model.GlobalErrorMessage;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand Down Expand Up @@ -52,11 +51,20 @@ public GlobalErrorMessage handleException(MismatchOrganizationIdentifierExceptio
log.error("The organization identifier of the cert does not match the organization identifier from the credential payload: ", ex);
return new GlobalErrorMessage("","","");
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public GlobalErrorMessage handleException(Exception ex) {
log.error("An unexpected error occurred: ", ex);
return new GlobalErrorMessage("","","");
}

@ExceptionHandler(InvalidVPtokenException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public GlobalErrorMessage handleException(InvalidVPtokenException ex, HttpServletRequest request) {
String contextPath = request.getContextPath();
log.error("VP token is not valid: {}", ex.getMessage());
return new GlobalErrorMessage("Invalid VP Token", ex.getMessage(), contextPath);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.nimbusds.jwt.SignedJWT;
import es.in2.verifier.config.CacheStore;
import es.in2.verifier.exception.InvalidCredentialTypeException;
import es.in2.verifier.exception.InvalidVPtokenException;
import es.in2.verifier.exception.UnsupportedGrantTypeException;
import es.in2.verifier.model.AuthorizationCodeData;
import es.in2.verifier.model.credentials.machine.LEARCredentialMachine;
Expand Down Expand Up @@ -124,7 +125,7 @@ private Authentication handleM2MGrant(MultiValueMap<String, String> parameters)
isValid = vpService.validateVerifiablePresentation(vpToken);
if (!isValid) {
log.error("CustomTokenRequestConverter -- handleM2MGrant -- VP Token is invalid");
throw new IllegalArgumentException("Invalid VP Token");
throw new InvalidVPtokenException("VP Token used in M2M flow is invalid");
}
log.info("VP Token validated successfully");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import es.in2.verifier.config.CacheStore;
import es.in2.verifier.config.properties.SecurityProperties;
import es.in2.verifier.exception.InvalidVPtokenException;
import es.in2.verifier.model.AuthorizationCodeData;
import es.in2.verifier.service.AuthorizationResponseProcessorService;
import es.in2.verifier.service.VpService;
Expand Down Expand Up @@ -62,7 +63,7 @@ public void processAuthResponse(String state, String vpToken){
boolean isValid = vpService.validateVerifiablePresentation(decodedVpToken);
if (!isValid) {
log.error("VP Token is invalid");
throw new IllegalArgumentException("Invalid VP Token");
throw new InvalidVPtokenException("VP Token used in H2M flow is invalid");
}
log.info("VP Token validated successfully");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package es.in2.vcverifier.exception.handler;

import es.in2.verifier.exception.InvalidVPtokenException;
import es.in2.verifier.exception.CredentialRevokedException;
import es.in2.verifier.exception.MismatchOrganizationIdentifierException;
import es.in2.verifier.exception.QRCodeGenerationException;
import es.in2.verifier.exception.ResourceNotFoundException;
import es.in2.verifier.exception.handler.GlobalExceptionHandler;
import es.in2.verifier.model.GlobalErrorMessage;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.NoSuchElementException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class GlobalExceptionHandlerTest {

@InjectMocks
private GlobalExceptionHandler globalExceptionHandler;

@Mock
private HttpServletRequest mockRequest;

@Test
void testHandleResourceNotFoundException() {
ResourceNotFoundException exception = new ResourceNotFoundException("Resource not found");

GlobalErrorMessage response = globalExceptionHandler.handleResourceNotFoundException(exception);

assertThat(response.title()).isEmpty();
assertThat(response.message()).isEmpty();
assertThat(response.path()).isEmpty();
}

@Test
void testHandleNoSuchElementException() {
NoSuchElementException exception = new NoSuchElementException("Element not found");

GlobalErrorMessage response = globalExceptionHandler.handleNoSuchElementException(exception);

assertThat(response.title()).isEmpty();
assertThat(response.message()).isEmpty();
assertThat(response.path()).isEmpty();
}

@Test
void testHandleQRCodeGenerationException() {
QRCodeGenerationException exception = new QRCodeGenerationException("QR Code Generation Failed");

GlobalErrorMessage response = globalExceptionHandler.handleQRCodeGenerationException(exception);

assertThat(response.title()).isEqualTo("QR Code Generation Failed");
assertThat(response.message()).isEmpty();
assertThat(response.path()).isEmpty();
}

@Test
void testHandleCredentialRevokedException() {
CredentialRevokedException exception = new CredentialRevokedException("Credential revoked");

GlobalErrorMessage response = globalExceptionHandler.handleException(exception);

assertThat(response.title()).isEqualTo("Verifiable presentation failed");
assertThat(response.message()).isEmpty();
assertThat(response.path()).isEmpty();
}

@Test
void testHandleMismatchOrganizationIdentifierException() {
MismatchOrganizationIdentifierException exception = new MismatchOrganizationIdentifierException("Mismatch org identifier");

GlobalErrorMessage response = globalExceptionHandler.handleException(exception);

assertThat(response.title()).isEmpty();
assertThat(response.message()).isEmpty();
assertThat(response.path()).isEmpty();
}

@Test
void testHandleGenericException() {
Exception exception = new Exception("Generic error");

GlobalErrorMessage response = globalExceptionHandler.handleException(exception);

assertThat(response.title()).isEmpty();
assertThat(response.message()).isEmpty();
assertThat(response.path()).isEmpty();
}

@Test
void testHandleInvalidVPtokenException() {
InvalidVPtokenException exception = new InvalidVPtokenException("Invalid VP token");

// Stub the contextPath value
when(mockRequest.getContextPath()).thenReturn("/test-path");

GlobalErrorMessage response = globalExceptionHandler.handleException(exception, mockRequest);

assertThat(response.title()).isEqualTo("Invalid VP Token");
assertThat(response.message()).isEqualTo("Invalid VP token");
assertThat(response.path()).isEqualTo("/test-path");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.nimbusds.jwt.SignedJWT;
import es.in2.verifier.config.CacheStore;
import es.in2.verifier.exception.InvalidCredentialTypeException;
import es.in2.verifier.exception.InvalidVPtokenException;
import es.in2.verifier.exception.UnsupportedGrantTypeException;
import es.in2.verifier.model.AuthorizationCodeData;
import es.in2.verifier.model.credentials.machine.LEARCredentialMachine;
Expand Down Expand Up @@ -192,7 +193,7 @@ void convert_clientCredentialsGrant_shouldReturnIllegalArgumentException_Invalid
when(clientAssertionValidationService.validateClientAssertionJWTClaims(anyString(), any())).thenReturn(true);
when(vpService.validateVerifiablePresentation(anyString())).thenReturn(false);

assertThrows(IllegalArgumentException.class, () ->
assertThrows(InvalidVPtokenException.class, () ->
customTokenRequestConverter.convert(mockRequest));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import es.in2.verifier.config.CacheStore;
import es.in2.verifier.config.properties.SecurityProperties;
import es.in2.verifier.exception.InvalidVPtokenException;
import es.in2.verifier.service.impl.AuthorizationResponseProcessorServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -148,11 +149,11 @@ void processAuthResponse_invalidVpToken_shouldThrowException() {
when(vpService.validateVerifiablePresentation("invalid-vp-token")).thenReturn(false);

// Act & Assert
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
InvalidVPtokenException exception = assertThrows(InvalidVPtokenException.class, () ->
authorizationResponseProcessorService.processAuthResponse(state, vpToken)
);

assertEquals("Invalid VP Token", exception.getMessage());
assertEquals("VP Token used in H2M flow is invalid", exception.getMessage());
}

@Test
Expand Down