Skip to content

Commit

Permalink
Hotfix/v1.1.2 (#37)
Browse files Browse the repository at this point in the history
* ADD check for the response from sending the authorization response during the standard presentation flow is a jwt token

* ADD test

* ADD test

* ADD WebClient bean and MODIFY post request authorization Response

* Exclude config tests temporarily

* Include webClientConfig test

* Include webClientConfig test

* Review

* Update project name

---------

Co-authored-by: Oriol Canadés <oriol.canades@in2.es>
  • Loading branch information
jiabowangin2 and oriolcanades committed Apr 30, 2024
1 parent 9017978 commit 67f614b
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 82 deletions.
41 changes: 23 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ 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.1.3] - 2024-04-25
### Fixed
- Added WebClient bean
- Modify post request in the process of sending the authorization response during the standard presentation flow

## [v1.1.2](https://github.com/in2workspace/wallet-server/releases/tag/v1.1.2) - 2024-04-04
### Fixed
- Change the return value from a exception to an empty list when the user don't have any credential
- Added the logic for selecting the credential that is wanted to be presented during the DOME presentation flow.

## [v1.1.1](https://github.com/in2workspace/wallet-server/releases/tag/v1.1.1) - 2024-04-02
### Fixed
- Change the versioning of the api endpoints from v2 to v1
- Change hardcoded verifiable presentation expiration to dynamic variable

## [v1.1.0](https://github.com/in2workspace/wallet-server/releases/tag/v1.1.0) - 2024-3-26
### Added
- Support Verifiable Credentials in cwt format
- Introduced the authentication process for Verifiable Presentations flow [OpenID.VP] combined with the Self-Issued OP v2 specification [OpenID.SIOP2]
- Introduced the Verifiable Presentation flow for CWT (Cbor web token) format
- Introduced the abstraction of the configuration and the vault
- Compatibility with the attestation exchange of the DOME marketplace.

## [v1.0.0](https://github.com/in2workspace/wallet-server/releases/tag/v1.0.0) - 2024-2-19
### Added
- Implemented key pair generation using the secp256r1 algorithm.
Expand All @@ -20,21 +43,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Structural Patterns, implementation of the Facade pattern.
- Factory and Adapter Patterns: Current use of the Factory pattern combined with the Adapter pattern, enhancing the flexibility and reusability of object creation and interaction within the system.
- Type Interpretation in Presentation Definition Filters: Added the capability to interpret the type of Verifiable Credential in Presentation Definition filters, allowing for more precise selection and verification of the credentials presented by the user. This enhancement facilitates adaptation to various use cases where verifying the specific type of the user's verifiable credential is required.

## [v1.1.0](https://github.com/in2workspace/wallet-server/releases/tag/v1.1.0) - 2024-3-26
### Added
- Support Verifiable Credentials in cwt format
- Introduced the authentication process for Verifiable Presentations flow [OpenID.VP] combined with the Self-Issued OP v2 specification [OpenID.SIOP2]
- Introduced the Verifiable Presentation flow for CWT (Cbor web token) format
- Introduced the abstraction of the configuration and the vault
- Compatibility with the attestation exchange of the DOME marketplace.

## [v1.1.1](https://github.com/in2workspace/wallet-server/releases/tag/v1.1.1) - 2024-04-02
### Fixed
- Change the versioning of the api endpoints from v2 to v1
- Change hardcoded verifiable presentation expiration to dynamic variable

## [v1.1.2](https://github.com/in2workspace/wallet-server/releases/tag/v1.1.2) - 2024-04-04
### Fixed
- Change the return value from a exception to an empty list when the user don't have any credential
- Added the logic for selecting the credential that is wanted to be presented during the DOME presentation flow.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ RUN addgroup -S nonroot \
&& adduser -S nonroot -G nonroot
USER nonroot
WORKDIR /app
COPY --from=TEMP_BUILD /home/gradle/src/build/libs/*.jar /app/wallet-server.jar
ENTRYPOINT ["java", "-jar", "/app/wallet-server.jar"]
COPY --from=TEMP_BUILD /home/gradle/src/build/libs/*.jar /app/wallet-api.jar
ENTRYPOINT ["java", "-jar", "/app/wallet-api.jar"]
30 changes: 4 additions & 26 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = 'es.in2'
version = '1.1.2'
version = '1.1.3'

java {
sourceCompatibility = '17'
Expand All @@ -33,26 +33,12 @@ checkstyleTest {

sonar {
properties {
property 'sonar.projectName', 'Wallet Server'
property 'sonar.projectName', 'Wallet API'
property 'sonar.projectKey', 'in2workspace_wallet-server'
property 'sonar.organization', 'in2workspace'
property 'sonar.host.url', 'https://sonarcloud.io'
property 'sonar.coverage.exclusions',
"src/main/java/es/in2/wallet/WalletServerApplication.java, " +
"src/main/java/es/in2/wallet/api/config/**, " +
"src/main/java/es/in2/wallet/api/model/**, " +
"src/main/java/es/in2/wallet/api/util/**, " +
"src/main/java/es/in2/wallet/api/ebsi/comformance/config/**, " +
"src/main/java/es/in2/wallet/broker/config/**, " +
"src/main/java/es/in2/wallet/broker/util/**, " +
"src/main/java/es/in2/wallet/configuration/adapter/azure/config/**, " +
"src/main/java/es/in2/wallet/configuration/model/**, " +
"src/main/java/es/in2/wallet/configuration/util/**, " +
"src/main/java/es/in2/wallet/vault/adapter/azure/config/**, " +
"src/main/java/es/in2/wallet/vault/adapter/hashicorp/config/**, " +
"src/main/java/es/in2/wallet/vault/adapter/hashicorp/model/**, " +
"src/main/java/es/in2/wallet/vault/util/**, " +
"src/main/java/es/in2/wallet/vault/model/**"
"src/main/java/es/in2/wallet/WalletApiApplication.java"
}
}

Expand Down Expand Up @@ -177,15 +163,7 @@ tasks.jacocoTestReport {
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
"src/main/java/es/in2/wallet/WalletServerApplication.java",
"src/main/java/es/in2/wallet/api/config/**",
"src/main/java/es/in2/wallet/api/model/**",
"src/main/java/es/in2/wallet/api/util/**",
"src/main/java/es/in2/wallet/api/ebsi/comformance/config/**" +
"src/main/java/es/in2/wallet/broker/config/properties/**",
"**/config/**",
"**/configuration/**",
"**/util/**"
"src/main/java/es/in2/wallet/WalletApiApplication.java"
])
}))
}
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = 'wallet-server'
rootProject.name = 'wallet-api'
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@SpringBootApplication
@ConfigurationPropertiesScan
public class WalletServerApplication {
public class WalletApiApplication {

private static final ObjectMapper OBJECT_MAPPER =
JsonMapper.builder()
Expand All @@ -20,7 +20,7 @@ public class WalletServerApplication {
.build();

public static void main(String[] args) {
SpringApplication.run(WalletServerApplication.class, args);
SpringApplication.run(WalletApiApplication.class, args);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import es.in2.wallet.domain.exception.FailedCommunicationException;
import es.in2.wallet.domain.exception.FailedDeserializingException;
import es.in2.wallet.domain.model.*;
import es.in2.wallet.domain.service.AuthorizationResponseService;
import es.in2.wallet.domain.util.MessageUtils;
import es.in2.wallet.infrastructure.core.config.WebClientConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
Expand All @@ -36,29 +37,31 @@ public class AuthorizationResponseServiceImpl implements AuthorizationResponseSe

private final ObjectMapper objectMapper;

private final WebClientConfig webClient;

@Override
public Mono<String> buildAndPostAuthorizationResponseWithVerifiablePresentation(String processId, VcSelectorResponse vcSelectorResponse, String verifiablePresentation, String authorizationToken) throws JsonProcessingException {
return generateDescriptorMapping(verifiablePresentation)
.flatMap(descriptorMapping -> getPresentationSubmissionAsString(processId, descriptorMapping))
.flatMap(presentationSubmissionString -> postAuthorizationResponse(processId, vcSelectorResponse, verifiablePresentation, presentationSubmissionString, authorizationToken));
.flatMap(descriptorMapping ->
getPresentationSubmissionAsString(processId, descriptorMapping))
.flatMap(presentationSubmissionString ->
postAuthorizationResponse(processId, vcSelectorResponse, verifiablePresentation,
presentationSubmissionString, authorizationToken));
}

@Override
public Mono<Void> sendDomeAuthorizationResponse(String vpToken, VcSelectorResponse vcSelectorResponse) {
String body = "vp_token=" + vpToken;
List<Map.Entry<String, String>> headers = new ArrayList<>();
headers.add(new AbstractMap.SimpleEntry<>(CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED_FORM));

String urlWithState = vcSelectorResponse.redirectUri() + "?state=" + vcSelectorResponse.state();

return postRequest(urlWithState, headers,body).flatMap(message ->
{
if (!message.equals("{}")) {
return Mono.error(new RuntimeException("There was an error during the attestation exchange, error: " + message));
} else {
return Mono.empty();
}
}).then();
return postRequest(urlWithState, headers, body).flatMap(message -> {
if (!message.equals("{}")) {
return Mono.error(new RuntimeException("There was an error during the attestation exchange, error: " + message));
} else {
return Mono.empty();
}
}).then();
}

private Mono<DescriptorMap> generateDescriptorMapping(String verifiablePresentationString) throws JsonProcessingException {
Expand Down Expand Up @@ -90,11 +93,8 @@ private Mono<VerifiablePresentation> parseVerifiablePresentationFromString(Strin
try {
JWT jwt = JWTParser.parse(verifiablePresentationString);
JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();

JsonNode rootNode = objectMapper.valueToTree(claimsSet.getClaim("vp"));

VerifiablePresentation verifiablePresentation = objectMapper.treeToValue(rootNode, VerifiablePresentation.class);

return Mono.just(verifiablePresentation);
} catch (ParseException e) {
return Mono.error(new FailedDeserializingException("Error while deserializing Verifiable Presentation: " + e));
Expand All @@ -105,11 +105,8 @@ private Mono<VerifiableCredential> parseVerifiableCredentialFromString(String ve
try {
JWT jwt = JWTParser.parse(verifiableCredentialString);
JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();

JsonNode rootNode = objectMapper.valueToTree(claimsSet.getClaim("vc"));

VerifiableCredential verifiableCredential = objectMapper.treeToValue(rootNode, VerifiableCredential.class);

return Mono.just(verifiableCredential);
} catch (ParseException e) {
return Mono.error(new FailedDeserializingException("Error while deserializing Verifiable Credential: " + e));
Expand Down Expand Up @@ -177,10 +174,6 @@ private Mono<DescriptorMap> addCredentialDescriptorMap(DescriptorMap credentialD

private Mono<String> postAuthorizationResponse(String processId, VcSelectorResponse vcSelectorResponse,
String verifiablePresentation, String presentationSubmissionString, String authorizationToken) {
// Headers
List<Map.Entry<String, String>> headers = List.of(
Map.entry(CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED_FORM),
Map.entry(MessageUtils.HEADER_AUTHORIZATION, MessageUtils.BEARER + authorizationToken));
// Build URL encoded form data request body
Map<String, String> formDataMap = Map.of(
"state", vcSelectorResponse.state(),
Expand All @@ -191,8 +184,22 @@ private Mono<String> postAuthorizationResponse(String processId, VcSelectorRespo
.map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "=" + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
// Post request
return postRequest(vcSelectorResponse.redirectUri(), headers, xWwwFormUrlencodedBody)
.doOnSuccess(response -> log.info("ProcessID: {} - Authorization Response: {}", processId, response))
.onErrorResume(e -> Mono.error(new FailedCommunicationException("Error while fetching Credential Issuer Metadata from the Issuer")));
return webClient.centralizedWebClient()
.post()
.uri(vcSelectorResponse.redirectUri())
.contentType(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED)
.header(HttpHeaders.AUTHORIZATION, MessageUtils.BEARER + authorizationToken)
.bodyValue(xWwwFormUrlencodedBody)
.exchangeToMono(response -> {
if (response.statusCode().is4xxClientError() || response.statusCode().is5xxServerError()) {
return Mono.error(new RuntimeException("There was an error during the attestation exchange, error" + response));
} else if (response.statusCode().is3xxRedirection()) {
return Mono.just(Objects.requireNonNull(response.headers().asHttpHeaders().getFirst(HttpHeaders.LOCATION)));
} else {
log.info("ProcessID: {} - Authorization Response: {}", processId, response);
return response.bodyToMono(String.class);
}
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package es.in2.wallet.infrastructure.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;

import java.time.Duration;

@Configuration
public class WebClientConfig {

private static final ConnectionProvider connectionProvider = ConnectionProvider.builder("custom")
.maxConnections(500)
.maxIdleTime(Duration.ofSeconds(50))
.maxLifeTime(Duration.ofSeconds(300))
.evictInBackground(Duration.ofSeconds(80))
.build();

@Bean
public WebClient centralizedWebClient() {
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create(connectionProvider).followRedirect(false))
)
.build();
}

}
Loading

0 comments on commit 67f614b

Please sign in to comment.