Skip to content

Commit ad2086f

Browse files
Feature/configurable powers domain (#185)
* update version * feat: when fetching credential procedures, allow admins to fetch all issued procedures (from all organizations). * feat: add owner_email in ProcedureBasicInfo so it can be shown in UI * feat: add organization in ProcedureBasicInfo so it can be shown in UI * feat: remove owner email from ProcedureBasicInfo * fix: replace organizationIdentifier for organization in ProcedureBasicInfo * fix(CredentialProcedureServiceImpl): fix fetching of the decoded credential in toProcedureBasicInfo() * feat: include organizationIdentifier instead of organization in ProcedureBasicInfo. * feat(CredentialProcedureService): when getting one procedure by ID and org ID, if user is admin, get it only by ID * feat(CredentialProcedureService): include ownerEmail in credential details * test(CredentialProcedureServiceImplTest): tests for getCredentialProcedureById * feat(issuance): add onBehalf field to CredentialProcedure and CredentialProcedureCreationRequest; when issuing LEAR Credential Employee, add this field (so emails that are sent to the actual credential issuer) * Revert "feat(issuance): add onBehalf field to CredentialProcedure and CredentialProcedureCreationRequest; when issuing LEAR Credential Employee, add this field (so emails that are sent to the actual credential issuer)" This reverts commit 58d37fb. * feat(buildCredentialProcedureCreationRequest): when creating CredentialProcedure for Employee and Machine VCs, use always the mandator organization ID for the organizationIdentifier field. * refactor: change "owner email" for "subject email" * test commit: hardcode organization name for email info * feat: add auditing * fix(SecurityConfig): replace lambda Converter with explicit class to fix generic type inference error at startup * fix(CredentialProcedureServiceImpl): restore update in basic info and set it as Instant to make it compatible with new CredentialProcedure * core/fix: add jackson-datatype-jsr310 to support Instant * add test logs * fix(R2dbcAuditingConfig): use reactive security context holder * in ProcedureBasicInfo), change output organizationIdentifier for organization_identifier * style: remove and change comments, adjust tabs * tests * tests * restore comment in V7 migration file to avoid flyway error * upgrade coverage * sonar issues * avoid duplication creating a JwtPrincipalService * avoid duplication creating a JwtPrincipalService * change "subject_email" for "email" in CredentialProcedure-related models; update flyway scripts * enhance comments * fix(issuance): change remaining credential subject email references * fix(issuance): send email pending credential to admin email (from token mandatee) when acting on behalf * fix: create securityUtils to get the current access token from authentication context; use it to get org id and mandatee email when sending email on behalf * Revert "fix: create securityUtils to get the current access token from authentication context; use it to get org id and mandatee email when sending email on behalf" This reverts commit c0a5ce1. * wip fix(RemoteSignatureServiceImpl): get admin email from updated_at * fix(signature flow): make handlePostRecoverError admit email as optional parameter. This is needed because when retrying signature from retrySignUnsignedCredential, the update_at field of the procedure (from which the email was obtained normally) hasn't been updated yet with the current user's email. * fix(handleOperationMode): get email from procedure.email * fix: make createDetailedIsssuer and related methods admit email as parameter. This is needed because updatedAt in procedure is updated as system during the previous process * fix(generateVerifiableCredentialResponse): get email from udpatedBy, not from email * chivatos * fix test * test in JwtPrincipalServiceImpl: set hardcoded email * feat(security): for public endpoints, add logic to set the email of the id token as principal * chivatos * fix(JwtPrincipalService): adapt extractMandateeEmail to ID token structure * fix(JwtPrincipalService): if couldn't find email, return principal as "anonymous" instead of null to avoid error * refactor: remove JwtPrincipalService, move its methods to JWTService * remove credentialProcedureService.getSignerEmailFromDecodedCredentialByProcedureId method * remove unused comments and parameters * chivatos * temporary fix(VerifiableCredentialPolicyAuthorizationServiceImpl.isSignerIssuancePolicyValid): admit LEAR Credential Machine mandator check. * remove isLikelyEmail * remove logs * remove CredentialSignerController * add logs * upgrade coverage * update changelog * sonar issues * update changelog * remove unused parameter 'credentialType' in create...Issuer related methods * in activate-credential-email-es.html, replace: "Estimado/a [usuario]," > "Hola," * add test logs * fix(SignUnsignedCredentialController): get and use bearer token instead of authorization header * feat: make admin organization identifier configurable * fix tests * upgrade coverage * sonar issues * test: manually updated updated_by after sending reminder * Revert "test: manually updated updated_by after sending reminder" This reverts commit 082893f. * sonar issues * Revert "sonar issues" This reverts commit 5d34f25. * dockerfile: replace openjdk for bellsoft/liberica-openjdk-alpine-musl:17 * Reapply "sonar issues" This reverts commit cec49f3. * Sonar issues * Sonar issues * update chagnelog * sonar issue in SecurityConfig.convert * remove and enhance logs and comments * sonar issue: nonNull in SecurityConfig * sonar issue: nonNull in SecurityConfig * update Changelog * fix(SecurityConfig): avoid error in initialization for lambda causing type uncertainty * core(Dockerfile): use eclipse-temurin:17-jdk-alpine * fix(security): suppress S2638 in JwtToAuthConverter The convert package includes @NonNullApi, but our method overrides Converter interface which declares convert() as @nullable. This creates a nullability conflict detected by Sonar. Suppression is safe as the method always returns Mono.just(), never null. * update changelog * refactor: implmenet single responsibility principle in CustomAuthenticationManager and DualTokenAuthentication * feat: add "sys-tenant" env variable and use it instead of constant DEFAULT_ORGANIZATION_NAME (remove this) * update version and changelog * update version and changelog * fix tests * add logs * upgrade coverage * small arrangements before revision (remove logs and comments)
1 parent 8e2c722 commit ad2086f

File tree

12 files changed

+187
-30
lines changed

12 files changed

+187
-30
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [v2.2.1](https://github.com/in2workspace/in2-issuer-api/releases/tag/v2.2.1)
8+
### Added
9+
- Add environment variable `sys-admin`, use it instead of constant DEFAULT_ORGANIZATION_NAME, which was used in email templates.
710

811
## [v2.2.0](https://github.com/in2workspace/in2-issuer-api/releases/tag/v2.2.0)
912
### Added

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ plugins {
1212

1313
group = 'es.in2'
1414

15-
version = '2.2.0'
15+
version = '2.2.1'
1616

1717
java {
1818
sourceCompatibility = '17'

src/main/java/es/in2/issuer/backend/shared/application/workflow/impl/CredentialIssuanceWorkflowImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ private CredentialOfferEmailNotificationInfo extractCredentialOfferEmailInfo(Pre
132132
throw new MissingEmailOwnerException("Email owner email is required for gx:LabelCredential schema");
133133
}
134134
String email = preSubmittedCredentialDataRequest.email();
135-
yield new CredentialOfferEmailNotificationInfo(email, DEFAULT_ORGANIZATION_NAME);
135+
yield new CredentialOfferEmailNotificationInfo(email, appConfig.getSysTenant());
136136
}
137137
default -> throw new FormatUnsupportedException(
138138
"Unknown schema: " + schema

src/main/java/es/in2/issuer/backend/shared/domain/service/impl/CredentialProcedureServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ public Mono<CredentialOfferEmailNotificationInfo> getCredentialOfferEmailInfoByP
379379
case LABEL_CREDENTIAL_TYPE -> Mono.just(
380380
new CredentialOfferEmailNotificationInfo(
381381
credentialProcedure.getEmail(),
382-
DEFAULT_ORGANIZATION_NAME
382+
appConfig.getSysTenant()
383383
)
384384
);
385385
default -> Mono.error(new FormatUnsupportedException(

src/main/java/es/in2/issuer/backend/shared/domain/util/Constants.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,4 @@ private Constants() {
3636
public static final String ENGLISH = "en";
3737
public static final String DEFAULT_USER_NAME = "Cloud Provider";
3838
public static final String LEAR_CREDENTIAL_MACHINE_DESCRIPTION = "Verifiable Credential for machines";
39-
public static final String DEFAULT_ORGANIZATION_NAME = "DOME";
4039
}

src/main/java/es/in2/issuer/backend/shared/infrastructure/config/AppConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ public String getDefaultLang() {
8787
public String getAdminOrganizationId() {
8888
return configAdapter.getConfiguration(appProperties.adminOrganizationId());
8989
}
90+
91+
public String getSysTenant(){
92+
return configAdapter.getConfiguration(appProperties.sysTenant());
93+
}
9094
}

src/main/java/es/in2/issuer/backend/shared/infrastructure/config/properties/AppProperties.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public record AppProperties(
1818
@NotBlank String configSource,
1919
@NotBlank @URL String walletUrl,
2020
@NotBlank String defaultLang,
21-
@NotBlank String adminOrganizationId
22-
21+
@NotBlank String adminOrganizationId,
22+
@NotBlank String sysTenant
2323
) {
2424

2525
@ConstructorBinding
@@ -32,7 +32,8 @@ public AppProperties(
3232
String configSource,
3333
String walletUrl,
3434
String defaultLang,
35-
String adminOrganizationId
35+
String adminOrganizationId,
36+
String sysTenant
3637
) {
3738
this.url = url;
3839
this.issuerFrontendUrl = issuerFrontendUrl;
@@ -43,6 +44,7 @@ public AppProperties(
4344
this.walletUrl = walletUrl;
4445
this.defaultLang = defaultLang;
4546
this.adminOrganizationId = adminOrganizationId;
47+
this.sysTenant = sysTenant;
4648
}
4749

4850
@Validated

src/main/resources/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ app:
135135
default-lang: en
136136
# The identifier of the admin organization (REQUIRED)
137137
admin-organization-id:
138+
# The system tenant name
139+
sys-tenant:
138140

139141
azure:
140142
endpoint: "https://myappconfig.azconfig.io"

src/test/java/es/in2/issuer/backend/shared/application/workflow/impl/CredentialIssuanceWorkflowImplTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import es.in2.issuer.backend.shared.domain.model.dto.VerifierOauth2AccessToken;
66
import es.in2.issuer.backend.shared.domain.model.entities.CredentialProcedure;
77
import org.mockito.ArgumentCaptor;
8+
9+
import static es.in2.issuer.backend.shared.domain.util.Constants.LABEL_CREDENTIAL;
810
import static org.mockito.ArgumentMatchers.*;
911
import static org.mockito.Mockito.*;
1012

@@ -913,5 +915,63 @@ void completeWithdrawLEARMachineProcessSyncSuccess() throws Exception {
913915
).verifyComplete();
914916
}
915917

918+
@Test
919+
void labelCredential_usesSysTenantAsOrganizationInEmail() throws Exception {
920+
// given
921+
String processId = "1234";
922+
String token = "token";
923+
String idToken = "idToken"; // required by execute() when schema is LABEL_CREDENTIAL
924+
String ownerEmail = "[email protected]";
925+
String issuerUiExternalDomain = "https://issuer.example.com";
926+
String knowledgebaseWalletUrl = "https://knowledgebase.com";
927+
String sysTenant = "my-sys-tenant";
928+
String tx = "tx-label-001";
929+
930+
// Minimal payload for label credential (email comes from request, not payload)
931+
ObjectMapper om = new ObjectMapper();
932+
JsonNode payload = om.readTree("{}");
933+
934+
PreSubmittedCredentialDataRequest req = PreSubmittedCredentialDataRequest.builder()
935+
.payload(payload)
936+
.schema(LABEL_CREDENTIAL)
937+
.format(JWT_VC_JSON)
938+
.operationMode("S")
939+
.email(ownerEmail)
940+
.build();
941+
942+
// when
943+
when(verifiableCredentialPolicyAuthorizationService.authorize(token, LABEL_CREDENTIAL, payload, idToken))
944+
.thenReturn(Mono.empty());
945+
when(verifiableCredentialService.generateVc(processId, req, ownerEmail))
946+
.thenReturn(Mono.just(tx));
947+
when(appConfig.getIssuerFrontendUrl()).thenReturn(issuerUiExternalDomain);
948+
when(appConfig.getKnowledgebaseWalletUrl()).thenReturn(knowledgebaseWalletUrl);
949+
when(appConfig.getSysTenant()).thenReturn(sysTenant);
950+
951+
when(emailService.sendCredentialActivationEmail(
952+
ownerEmail,
953+
"email.activation.subject",
954+
issuerUiExternalDomain + "/credential-offer?transaction_code=" + tx,
955+
knowledgebaseWalletUrl,
956+
sysTenant
957+
)).thenReturn(Mono.empty());
958+
959+
// then
960+
StepVerifier.create(
961+
verifiableCredentialIssuanceWorkflow.execute(processId, req, token, idToken)
962+
).verifyComplete();
963+
964+
// verify
965+
verify(emailService).sendCredentialActivationEmail(
966+
eq(ownerEmail),
967+
eq("email.activation.subject"),
968+
contains(tx),
969+
eq(knowledgebaseWalletUrl),
970+
eq(sysTenant)
971+
);
972+
}
973+
974+
975+
916976

917977
}

src/test/java/es/in2/issuer/backend/shared/domain/service/impl/CredentialProcedureServiceImplTest.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import es.in2.issuer.backend.shared.domain.exception.NoCredentialFoundException;
77
import es.in2.issuer.backend.shared.domain.exception.ParseCredentialJsonException;
8-
import es.in2.issuer.backend.shared.domain.model.dto.CredentialDetails;
9-
import es.in2.issuer.backend.shared.domain.model.dto.CredentialProcedureCreationRequest;
10-
import es.in2.issuer.backend.shared.domain.model.dto.CredentialProcedures;
11-
import es.in2.issuer.backend.shared.domain.model.dto.ProcedureBasicInfo;
8+
import es.in2.issuer.backend.shared.domain.model.dto.*;
129
import es.in2.issuer.backend.shared.domain.model.entities.CredentialProcedure;
1310
import es.in2.issuer.backend.shared.domain.model.enums.CredentialStatusEnum;
1411
import es.in2.issuer.backend.shared.domain.model.enums.CredentialType;
@@ -18,6 +15,7 @@
1815
import org.junit.jupiter.api.Test;
1916
import org.junit.jupiter.api.extension.ExtendWith;
2017

18+
import static es.in2.issuer.backend.shared.domain.util.Constants.LABEL_CREDENTIAL_TYPE;
2119
import static org.junit.jupiter.api.Assertions.*;
2220
import org.mockito.InjectMocks;
2321
import org.mockito.Mock;
@@ -770,4 +768,39 @@ void getAllProceduresBasicInfoByOrganizationId_shouldReturnEmptyList_whenReposit
770768

771769
verify(credentialProcedureRepository, times(1)).findAllByOrganizationIdentifier(orgId);
772770
}
771+
772+
@Test
773+
void getCredentialOfferEmailInfoByProcedureId_label_usesSysTenantForOrganization() {
774+
// given
775+
String procedureId = UUID.randomUUID().toString();
776+
String email = "[email protected]";
777+
String sysTenant = "my-sys-tenant-from-config";
778+
779+
CredentialProcedure cp = new CredentialProcedure();
780+
cp.setProcedureId(UUID.fromString(procedureId));
781+
cp.setCredentialType(LABEL_CREDENTIAL_TYPE);
782+
cp.setEmail(email);
783+
// For LABEL, decoded JSON is not used, so it can be null
784+
cp.setCredentialDecoded(null);
785+
786+
when(credentialProcedureRepository.findByProcedureId(UUID.fromString(procedureId)))
787+
.thenReturn(Mono.just(cp));
788+
when(appConfig.getSysTenant()).thenReturn(sysTenant);
789+
790+
// when
791+
Mono<CredentialOfferEmailNotificationInfo> mono =
792+
credentialProcedureService.getCredentialOfferEmailInfoByProcedureId(procedureId);
793+
794+
// then
795+
StepVerifier.create(mono)
796+
.expectNextMatches(info ->
797+
email.equals(info.email()) &&
798+
sysTenant.equals(info.organization()))
799+
.verifyComplete();
800+
801+
verify(credentialProcedureRepository, times(1))
802+
.findByProcedureId(UUID.fromString(procedureId));
803+
verify(appConfig, times(1)).getSysTenant();
804+
}
805+
773806
}

0 commit comments

Comments
 (0)