Skip to content

Feature/credentials management #21

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

Merged
merged 12 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ 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).

## [Unreleased]: v0.7.0
- LEARCredential compliance.

## [Unreleased]: v0.6.0
- DOME profile compliance.

## [Unreleased]: v0.5.0
- Deferred credential emission.
- tx_code support for PIN.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
}

group = 'es.in2'
version = '0.7.0'
version = '0.8.0'

java {
sourceCompatibility = '17'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public Mono<Void> updateSignedCredentials(SignedCredentials signedCredentials) {
// Parse the credential and extract the ID
JsonNode credentialNode = objectMapper.readTree(payload);
String credentialId = credentialNode.get("vc").get("id").asText();
String email = credentialNode.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("email").toString();
String firstName = credentialNode.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("first_name").toString();
String email = credentialNode.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("email").asText();
String firstName = credentialNode.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("first_name").asText();
// Update the credential in the database
return credentialProcedureService.updatedEncodedCredentialByCredentialId(jwt, credentialId)
.flatMap(procedureId -> deferredCredentialMetadataService.updateVcByProcedureId(jwt, procedureId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public class VerifiableCredentialIssuanceWorkflowImpl implements VerifiableCrede
public Mono<Void> completeWithdrawLearCredentialProcess(String processId, String type, LEARCredentialRequest learCredentialRequest) {
return verifiableCredentialService.generateVc(processId, type, learCredentialRequest)
.flatMap(transactionCode -> {
String email = learCredentialRequest.credential().get("mandatee").get("email").toString();
String firstName = learCredentialRequest.credential().get("mandatee").get("first_name").toString();
String email = learCredentialRequest.credential().get("mandatee").get("email").asText();
String firstName = learCredentialRequest.credential().get("mandatee").get("first_name").asText();
return emailService.sendTransactionCodeForCredentialOffer(email, "Credential Offer", appConfig.getIssuerUiExternalDomain() + "/credential-offer?transaction_code=" + transactionCode, firstName);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
@Builder
public record DeferredCredentialMetadataDeferredResponse(
String id,
String procedureId,
String transactionId,

String vc
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@Builder
public record LEARCredentialEmployee(
@JsonProperty("@context") List<String> context,
@JsonProperty("id") String id,
@JsonProperty("type") List<String> type,
@JsonProperty("credentialSubject") CredentialSubject credentialSubject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ public record LEARCredentialEmployeeJwtPayload(
String subject,

@JsonProperty("nbf")
String notValidBefore,
Long notValidBefore,

@JsonProperty("iss")
String issuer,

@JsonProperty("exp")
String expirationTime,
Long expirationTime,

@JsonProperty("iat")
String issuedAt,
Long issuedAt,

@JsonProperty("vc")
LEARCredentialEmployee learCredentialEmployee,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

public enum CredentialStatus {
WITHDRAWN,
VALID,
ISSUED,
PEND_DOWNLOAD,
VALID,
REVOKED,
EXPIRED
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@

public interface AccessTokenService {
Mono<String> getCleanBearerToken(String authorizationHeader);
Mono<String> getUserIdFromHeader(String authorizationHeader);
Mono<String> getUserId(String authorizationHeader);
Mono<String> getOrganizationId(String authorizationHeader);
Mono<String> getOrganizationIdFromCurrentSession();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ public interface CredentialProcedureService {

Mono<String> getCredentialTypeByProcedureId(String procedureId);

Mono<String> getCredentialStatusByProcedureId(String procedureId);

Mono<Void> updateDecodedCredentialByProcedureId(String procedureId, String credential, String format);

Mono<String> getDecodedCredentialByProcedureId(String procedureId);

Mono<String> getMandateeEmailFromDecodedCredentialByProcedureId(String procedureId);

Mono<String> getMandateeFirstNameFromDecodedCredentialByProcedureId(String procedureId);

Mono<String> getMandatorEmailFromDecodedCredentialByProcedureId(String procedureId);

Flux<String> getAllIssuedCredentialByOrganizationIdentifier(String organizationIdentifier);
Expand All @@ -25,5 +29,7 @@ public interface CredentialProcedureService {

Mono<CredentialDetails> getProcedureDetailByProcedureIdAndOrganizationId(String organizationIdentifier, String procedureId);

Mono<Void> updateCredentialProcedureCredentialStatusToValidByProcedureId(String procedureId);

Mono<String> updatedEncodedCredentialByCredentialId(String encodedCredential, String credentialId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

public interface DeferredCredentialMetadataService {
Mono<String> createDeferredCredentialMetadata(String procedureId);
Mono<String> updateTransactionCodeInDeferredCredentialMetadata(String procedureId);
Mono<String> getProcedureIdByTransactionCode(String transactionCode);
Mono<String> getProcedureIdByAuthServerNonce(String authServerNonce);
Mono<Void> updateAuthServerNonceByTransactionCode(String transactionCode, String authServerNonce);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package es.in2.issuer.domain.service;

import reactor.core.publisher.Mono;

public interface NotificationService {
Mono<Void> sendNotification(String processId,String procedureId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import es.in2.issuer.domain.exception.InvalidTokenException;
import es.in2.issuer.domain.service.AccessTokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

Expand All @@ -18,12 +21,17 @@ public class AccessTokenServiceImpl implements AccessTokenService {
@Override
public Mono<String> getCleanBearerToken(String authorizationHeader) {
return Mono.just(authorizationHeader)
.filter(header -> header.startsWith(BEARER_PREFIX))
.map(header -> header.replace(BEARER_PREFIX, "").trim())
.switchIfEmpty(Mono.error(new IllegalArgumentException("Invalid")));
.flatMap(header -> {
if (header.startsWith(BEARER_PREFIX)) {
return Mono.just(header.replace(BEARER_PREFIX, "").trim());
} else {
return Mono.just(header);
}
});
}

@Override
public Mono<String> getUserIdFromHeader(String authorizationHeader) {
public Mono<String> getUserId(String authorizationHeader) {
return getCleanBearerToken(authorizationHeader)
.flatMap(token -> {
try {
Expand All @@ -34,6 +42,45 @@ public Mono<String> getUserIdFromHeader(String authorizationHeader) {
return Mono.error(e);
}
})
.switchIfEmpty(Mono.error(new IllegalArgumentException("Invalid")));
.switchIfEmpty(Mono.error(new InvalidTokenException()));
}

@Override
public Mono<String> getOrganizationId(String authorizationHeader) {
return getCleanBearerToken(authorizationHeader)
.flatMap(token -> {
try {
SignedJWT parsedVcJwt = SignedJWT.parse(token);
JsonNode jsonObject = new ObjectMapper().readTree(parsedVcJwt.getPayload().toString());
return Mono.just(jsonObject.get("organizationIdentifier").asText());
} catch (ParseException | JsonProcessingException e) {
return Mono.error(e);
}
})
.switchIfEmpty(Mono.error(new InvalidTokenException()));
}

@Override
public Mono<String> getOrganizationIdFromCurrentSession() {
return getTokenFromCurrentSession()
.flatMap(this::getCleanBearerToken)
.flatMap(token -> {
try {
SignedJWT parsedVcJwt = SignedJWT.parse(token);
JsonNode jsonObject = new ObjectMapper().readTree(parsedVcJwt.getPayload().toString());
return Mono.just(jsonObject.get("organizationIdentifier").asText());
} catch (ParseException | JsonProcessingException e) {
return Mono.error(e);
}
})
.switchIfEmpty(Mono.error(new InvalidTokenException()));
}

private Mono<String> getTokenFromCurrentSession() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> {
JwtAuthenticationToken token = (JwtAuthenticationToken) ctx.getAuthentication();
return token.getToken().getTokenValue();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,32 @@ public Mono<String> getDecodedCredentialByProcedureId(String procedureId) {
.flatMap(credentialProcedure -> Mono.just(credentialProcedure.getCredentialDecoded()));
}

@Override
public Mono<String> getCredentialStatusByProcedureId(String procedureId) {
return credentialProcedureRepository.findCredentialStatusByProcedureId(UUID.fromString(procedureId));
}

@Override
public Mono<String> getMandateeEmailFromDecodedCredentialByProcedureId(String procedureId) {
return credentialProcedureRepository.findById(UUID.fromString(procedureId))
.flatMap(credentialProcedure -> {
try {
JsonNode credential = objectMapper.readTree(credentialProcedure.getCredentialDecoded());
return Mono.just(credential.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("email").toString());
return Mono.just(credential.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("email").asText());
} catch (JsonProcessingException e) {
return Mono.error(new RuntimeException());
}

});
}

@Override
public Mono<String> getMandateeFirstNameFromDecodedCredentialByProcedureId(String procedureId) {
return credentialProcedureRepository.findById(UUID.fromString(procedureId))
.flatMap(credentialProcedure -> {
try {
JsonNode credential = objectMapper.readTree(credentialProcedure.getCredentialDecoded());
return Mono.just(credential.get("vc").get("credentialSubject").get("mandate").get("mandatee").get("first_name").asText());
} catch (JsonProcessingException e) {
return Mono.error(new RuntimeException());
}
Expand All @@ -111,7 +130,7 @@ public Mono<String> getMandatorEmailFromDecodedCredentialByProcedureId(String pr
.flatMap(credentialProcedure -> {
try {
JsonNode credential = objectMapper.readTree(credentialProcedure.getCredentialDecoded());
return Mono.just(credential.get("vc").get("credentialSubject").get("mandate").get("mandator").get("emailAddress").toString());
return Mono.just(credential.get("vc").get("credentialSubject").get("mandate").get("mandator").get("emailAddress").asText());
} catch (JsonProcessingException e) {
return Mono.error(new RuntimeException());
}
Expand Down Expand Up @@ -149,12 +168,23 @@ public Mono<String> updatedEncodedCredentialByCredentialId(String encodedCredent
return credentialProcedureRepository.findByCredentialId(UUID.fromString(credentialId))
.flatMap(credentialProcedure -> {
credentialProcedure.setCredentialEncoded(encodedCredential);
credentialProcedure.setCredentialStatus(CredentialStatus.VALID);
credentialProcedure.setCredentialStatus(CredentialStatus.PEND_DOWNLOAD);
return credentialProcedureRepository.save(credentialProcedure)
.then(Mono.just(credentialProcedure.getProcedureId().toString()));
});
}

@Override
public Mono<Void> updateCredentialProcedureCredentialStatusToValidByProcedureId(String procedureId) {
return credentialProcedureRepository.findByProcedureId(UUID.fromString(procedureId))
.flatMap(credentialProcedure -> {
credentialProcedure.setCredentialStatus(CredentialStatus.VALID);
return credentialProcedureRepository.save(credentialProcedure)
.doOnSuccess(result -> log.info("Updated credential"))
.then();
});
}

@Override
public Mono<CredentialProcedures> getAllProceduresBasicInfoByOrganizationId(String organizationIdentifier) {
return credentialProcedureRepository.findAllByOrganizationIdentifier(organizationIdentifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ public Mono<String> createDeferredCredentialMetadata(String procedureId) {
});
}

@Override
public Mono<String> updateTransactionCodeInDeferredCredentialMetadata(String procedureId) {
return deferredCredentialMetadataRepository.findByProcedureId(UUID.fromString(procedureId))
.flatMap(existingDeferredCredentialMetadata -> generateCustomNonce()
.flatMap(nonce -> cacheStore.add(nonce, nonce)
.then(Mono.just(nonce))
.doOnNext(existingDeferredCredentialMetadata::setTransactionCode))
.flatMap(newNonce -> deferredCredentialMetadataRepository.save(existingDeferredCredentialMetadata)
.then(Mono.just(newNonce))));
}

@Override
public Mono<String> getProcedureIdByTransactionCode(String transactionCode) {
return deferredCredentialMetadataRepository.findByTransactionCode(transactionCode)
Expand Down Expand Up @@ -117,6 +128,7 @@ public Mono<DeferredCredentialMetadataDeferredResponse> getVcByTransactionId(Str
return Mono.just(DeferredCredentialMetadataDeferredResponse.builder()
.vc(deferredCredentialMetadata.getVc())
.id(deferredCredentialMetadata.getId().toString())
.procedureId(deferredCredentialMetadata.getProcedureId().toString())
.build());
}
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import static es.in2.issuer.domain.util.Constants.NO_REPLAY_EMAIL;
import static es.in2.issuer.domain.util.Constants.FROM_EMAIL;
import static es.in2.issuer.domain.util.Constants.UTF_8;

@Slf4j
Expand All @@ -27,7 +27,7 @@ public Mono<Void> sendPin(String to, String subject, String pin) {
return Mono.fromCallable(() -> {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, UTF_8);
helper.setFrom(NO_REPLAY_EMAIL);
helper.setFrom(FROM_EMAIL);
helper.setTo(to);
helper.setSubject(subject);

Expand All @@ -49,7 +49,7 @@ public Mono<Void> sendTransactionCodeForCredentialOffer(String to, String subjec
return Mono.fromCallable(() -> {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, UTF_8);
helper.setFrom(NO_REPLAY_EMAIL);
helper.setFrom(FROM_EMAIL);
helper.setTo(to);
helper.setSubject(subject);

Expand All @@ -69,7 +69,7 @@ public Mono<Void> sendPendingCredentialNotification(String to, String subject) {
return Mono.fromCallable(() -> {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, UTF_8);
helper.setFrom(NO_REPLAY_EMAIL);
helper.setFrom(FROM_EMAIL);
helper.setTo(to);
helper.setSubject(subject);

Expand All @@ -91,7 +91,7 @@ public Mono<Void> sendCredentialSignedNotification(String to, String subject, St
return Mono.fromCallable(() -> {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
helper.setFrom(NO_REPLAY_EMAIL);
helper.setFrom(FROM_EMAIL);
helper.setTo(to);
helper.setSubject(subject);

Expand Down
Loading
Loading