Skip to content

feature/add-sign-access-request #122

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ 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.7.1](https://github.com/in2workspace/in2-issuer-api/releases/tag/v1.7.1)Add commentMore actions
### Fixed
- Assign always default config. email to Issuer.
## [v2.0.1](https://github.com/in2workspace/in2-issuer-api/releases/tag/v2.0.1)
### Added
- Add sign access request.

## [v2.0.0](https://github.com/in2workspace/in2-issuer-api/releases/tag/v2.0.0)
### Added
- Adapt endpoints to oid4vci.

## [v1.7.0](https://github.com/in2workspace/in2-issuer-api/releases/tag/v1.7.0)
### Added
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 = '1.7.1'
version = '2.0.1'

java {
sourceCompatibility = '17'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ private Constants() {
public static final String SIGNATURE_REMOTE_SCOPE_SERVICE = "service";
public static final String SIGNATURE_REMOTE_SCOPE_CREDENTIAL = "credential";
public static final String CREDENTIAL_ID = "credentialID";
public static final String NUM_SIGNATURES = "numSignatures";
public static final String AUTH_DATA = "authData";
public static final String AUTH_DATA_ID = "id";
public static final String AUTH_DATA_VALUE = "value";
public static final String CREDENTIAL_ACTIVATION_EMAIL_SUBJECT = "Activate your new credential";
public static final String ERROR_LOG_FORMAT = "[Error Instance ID: {}] Path: {}, Status: {}, Title: {}, Message: {}";
// ERROR MESSAGES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ private CredentialResponseErrorCodes() {
public static final String FORMAT_IS_NOT_SUPPORTED = "format_is_not_supported";
public static final String INSUFFICIENT_PERMISSION = "insufficient_permission";
public static final String MISSING_HEADER = "missing_header";
public static final String SAD_ERROR = "SAD_ERROR";

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public Mono<ResponseEntity<CredentialErrorResponse>> handleExpiredCacheException

return Mono.just(ResponseEntity.badRequest().body(errorResponse));
}

@ExceptionHandler(ExpiredPreAuthorizedCodeException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Mono<ResponseEntity<CredentialErrorResponse>> expiredPreAuthorizedCode(Exception ex) {
Expand Down Expand Up @@ -104,7 +105,7 @@ public Mono<ResponseEntity<CredentialErrorResponse>> handleInvalidToken(Exceptio
CredentialErrorResponse errorResponse = new CredentialErrorResponse(
CredentialResponseErrorCodes.INVALID_TOKEN,
description
);
);

return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse));
}
Expand Down Expand Up @@ -172,31 +173,37 @@ public Mono<ResponseEntity<Void>> handleSignedDataParsingException(AuthenticSour
log.error(ex.getMessage());
return Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}

@ExceptionHandler(ParseCredentialJsonException.class)
public Mono<ResponseEntity<Void>> handleParseCredentialJsonException(ParseCredentialJsonException ex) {
log.error(ex.getMessage());
return Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}

@ExceptionHandler(TemplateReadException.class)
public Mono<ResponseEntity<Void>> handleTemplateReadException(TemplateReadException ex) {
log.error(ex.getMessage());
return Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}

@ExceptionHandler(ProofValidationException.class)
public Mono<ResponseEntity<Void>> handleProofValidationException(ProofValidationException ex) {
log.error(ex.getMessage());
return Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}

@ExceptionHandler(NoCredentialFoundException.class)
public Mono<ResponseEntity<Void>> handleNoCredentialFoundException(NoCredentialFoundException ex) {
log.error(ex.getMessage());
return Mono.just(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

@ExceptionHandler(PreAuthorizationCodeGetException.class)
public Mono<ResponseEntity<Void>> handlePreAuthorizationCodeGetException(PreAuthorizationCodeGetException ex) {
log.error(ex.getMessage());
return Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}

@ExceptionHandler(CredentialOfferNotFoundException.class)
public Mono<ResponseEntity<Void>> handleCustomCredentialOfferNotFoundException(CredentialOfferNotFoundException ex) {
log.error(ex.getMessage());
Expand Down Expand Up @@ -407,4 +414,12 @@ public Mono<ResponseEntity<GlobalErrorMessage>> handleInvalidSignatureConfigurat

return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response));
}

@ExceptionHandler(SadException.class)
@ResponseStatus(HttpStatus.BAD_GATEWAY)
public Mono<CredentialErrorResponse> handleSadError(Exception ex) {
return Mono.just(new CredentialErrorResponse(
CredentialResponseErrorCodes.SAD_ERROR,
ex.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package es.in2.issuer.backend.backoffice.infrastructure.controller;

import es.in2.issuer.backend.shared.application.workflow.CredentialIssuanceWorkflow;
import es.in2.issuer.backend.shared.domain.model.dto.PreSubmittedCredentialRequest;
import es.in2.issuer.backend.shared.domain.model.dto.PreSubmittedCredentialDataRequest;
import es.in2.issuer.backend.shared.domain.service.AccessTokenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

Expand All @@ -24,20 +25,23 @@ public class IssuanceController {
@PostMapping("/backoffice/v1/issuances")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Void> internalIssueCredential(@RequestHeader(HttpHeaders.AUTHORIZATION) String bearerToken,
@RequestBody PreSubmittedCredentialRequest preSubmittedCredentialRequest) {
@RequestBody PreSubmittedCredentialDataRequest preSubmittedCredentialDataRequest) {
String processId = UUID.randomUUID().toString();
return accessTokenService.getCleanBearerToken(bearerToken).flatMap(
token -> credentialIssuanceWorkflow.execute(processId, preSubmittedCredentialRequest, token, null));
token -> credentialIssuanceWorkflow.execute(processId, preSubmittedCredentialDataRequest, token, null));
}

@PostMapping("/vci/v1/issuances")
@PostMapping(
value = "/vci/v1/issuances",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public Mono<Void> externalIssueCredential(@RequestHeader(HttpHeaders.AUTHORIZATION) String bearerToken,
@RequestHeader(name = "X-Id-Token", required = false) String idToken,
@RequestBody PreSubmittedCredentialRequest preSubmittedCredentialRequest) {
@RequestBody PreSubmittedCredentialDataRequest preSubmittedCredentialDataRequest) {
String processId = UUID.randomUUID().toString();
return accessTokenService.getCleanBearerToken(bearerToken).flatMap(
token -> credentialIssuanceWorkflow.execute(processId, preSubmittedCredentialRequest, token, idToken));
token -> credentialIssuanceWorkflow.execute(processId, preSubmittedCredentialDataRequest, token, idToken));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

@Builder
public record AuthorizationServerMetadata(
@JsonProperty("issuer") String issuer,
@JsonProperty("token_endpoint") String tokenEndpoint,
@JsonProperty("response_types_supported") Set<String> responseTypesSupported,
@JsonProperty("pre-authorized_grant_anonymous_access_supported") boolean preAuthorizedGrantAnonymousAccessSupported
@JsonProperty(value = "issuer", required = true) String issuer,
@JsonProperty(value = "token_endpoint", required = true) String tokenEndpoint,
@JsonProperty(value = "response_types_supported", required = true) Set<String> responseTypesSupported,
@JsonProperty(value = "pre-authorized_grant_anonymous_access_supported", required = true) boolean preAuthorizedGrantAnonymousAccessSupported
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

@Builder
public record CredentialIssuerMetadata(
@JsonProperty("credential_issuer") String credentialIssuer,
@JsonProperty("issuance_endpoint") String issuanceEndpoint,
@JsonProperty("credential_endpoint") String credentialEndpoint,
@JsonProperty(value = "credential_issuer", required = true) String credentialIssuer,
@JsonProperty(value = "issuance_endpoint", required = true) String issuanceEndpoint,
@JsonProperty(value = "credential_endpoint", required = true) String credentialEndpoint,
@JsonProperty("deferred_credential_endpoint") String deferredCredentialEndpoint,
@JsonProperty("credential_configurations_supported") Map<String, CredentialConfiguration> credentialConfigurationsSupported
@JsonProperty(value = "credential_configurations_supported", required = true) Map<String, CredentialConfiguration> credentialConfigurationsSupported
) {

@Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

@Builder
public record TokenResponse(
@JsonProperty("access_token") String accessToken,
@JsonProperty("token_type") String tokenType,
@JsonProperty("expires_in") long expiresIn,
@JsonProperty("c_nonce") String nonce,
@JsonProperty("c_nonce_expires_in") Long nonceExpiresIn) {
@JsonProperty(value = "access_token", required = true) String accessToken,
@JsonProperty(value = "token_type", required = true) String tokenType,
@JsonProperty(value = "expires_in", required = true) long expiresIn,
@JsonProperty(value = "refresh_token", required = true) String refreshToken) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;

import static es.in2.issuer.backend.oidc4vci.domain.util.Constants.ACCESS_TOKEN_EXPIRATION_TIME_DAYS;
import static es.in2.issuer.backend.shared.domain.util.Constants.GRANT_TYPE;
import static es.in2.issuer.backend.shared.domain.util.Constants.PRE_AUTH_CODE_EXPIRY_DURATION_MINUTES;
import static es.in2.issuer.backend.shared.domain.util.Utils.generateCustomNonce;

@Slf4j
Expand Down Expand Up @@ -49,16 +47,11 @@ public Mono<TokenResponse> generateTokenResponse(
String accessToken = generateAccessToken(preAuthorizedCode, issueTimeEpochSeconds, expirationTimeEpochSeconds);
String tokenType = "bearer";
long expiresIn = expirationTimeEpochSeconds - Instant.now().getEpochSecond();
long nonceExpiresIn = (int) TimeUnit.SECONDS.convert(
PRE_AUTH_CODE_EXPIRY_DURATION_MINUTES,
TimeUnit.MINUTES);

return TokenResponse.builder()
.accessToken(accessToken)
.tokenType(tokenType)
.expiresIn(expiresIn)
.nonce(nonce)
.nonceExpiresIn(nonceExpiresIn)
.build();
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import es.in2.issuer.backend.shared.application.workflow.CredentialIssuanceWorkflow;
import es.in2.issuer.backend.shared.domain.model.dto.CredentialRequest;
import es.in2.issuer.backend.shared.domain.model.dto.VerifiableCredentialResponse;
import es.in2.issuer.backend.shared.domain.model.dto.CredentialResponse;
import es.in2.issuer.backend.shared.domain.service.AccessTokenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -25,7 +25,8 @@ public class CredentialController {
private final AccessTokenService accessTokenService;

@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<VerifiableCredentialResponse>> createVerifiableCredential(
@ResponseStatus(HttpStatus.OK)
public Mono<ResponseEntity<CredentialResponse>> createVerifiableCredential(
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader,
@RequestBody CredentialRequest credentialRequest) {
String processId = UUID.randomUUID().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import es.in2.issuer.backend.shared.application.workflow.CredentialIssuanceWorkflow;
import es.in2.issuer.backend.shared.domain.model.dto.DeferredCredentialRequest;
import es.in2.issuer.backend.shared.domain.model.dto.VerifiableCredentialResponse;
import es.in2.issuer.backend.shared.domain.model.dto.CredentialResponse;
import es.in2.issuer.backend.shared.domain.model.dto.DeferredCredentialResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
Expand All @@ -23,7 +24,7 @@ public class DeferredCredentialController {

@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public Mono<VerifiableCredentialResponse> getCredential(
public Mono<DeferredCredentialResponse> getCredential(
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader,
@RequestBody DeferredCredentialRequest deferredCredentialRequest) {
// todo: Check if the authorization header is needed here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@

public interface CredentialIssuanceWorkflow {

Mono<Void> execute(String processId, PreSubmittedCredentialRequest preSubmittedCredentialRequest, String bearerToken, String idToken);
Mono<Void> execute(String processId, PreSubmittedCredentialDataRequest preSubmittedCredentialDataRequest, String bearerToken, String idToken);

// Refactor
Mono<VerifiableCredentialResponse> generateVerifiableCredentialResponse(String processId, CredentialRequest credentialRequest, String token);
Mono<CredentialResponse> generateVerifiableCredentialResponse(String processId, CredentialRequest credentialRequest, String token);

Mono<BatchCredentialResponse> generateVerifiableCredentialBatchResponse(String username, BatchCredentialRequest batchCredentialRequest, String token);

Mono<VerifiableCredentialResponse> generateVerifiableCredentialDeferredResponse(String processId, DeferredCredentialRequest deferredCredentialRequest);
Mono<DeferredCredentialResponse> generateVerifiableCredentialDeferredResponse(String processId, DeferredCredentialRequest deferredCredentialRequest);

Mono<Void> bindAccessTokenByPreAuthorizedCode(String processId, AuthServerNonceRequest authServerNonceRequest);
}
Loading