From edeb394f4dc82946b275fc48826863a6f962ba4d Mon Sep 17 00:00:00 2001 From: Antonio Tarricone <110115827+antoniotarricone@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:57:03 +0200 Subject: [PATCH] feat: Introduced REST client proxies for Microsoft Entra ID. (#19) --- dep-sha256.json | 294 +++++++++--------- pom.xml | 7 +- ...veClient.java => AzureIdentityClient.java} | 6 +- .../AzureIdentityReactiveClientFactory.java | 137 -------- .../AzureSystemManagedIdentityClient.java | 62 ++++ ...AzureSystemManagedIdentityRestClient.java} | 9 +- .../workload/AzureWorkloadIdentityClient.java | 64 ++++ .../AzureWorkloadIdentityRestClient.java} | 6 +- .../service/AzureIdentityReactiveService.java | 61 +++- ...zureIdentityReactiveClientFactoryTest.java | 128 -------- .../AzureSystemManagedIdentityClientTest.java | 94 ++++++ .../AzureWorkloadIdentityClientTest.java | 96 ++++++ .../AzureWorkloadIdentityRestClientTest.java} | 24 +- .../AzureIdentityReactiveServiceTest.java | 183 ++++++++++- src/test/resources/application.properties | 6 +- 15 files changed, 715 insertions(+), 462 deletions(-) rename src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/{AzureIdentityReactiveClient.java => AzureIdentityClient.java} (78%) delete mode 100644 src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactory.java create mode 100644 src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClient.java rename src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/{AzureSystemManagedIdentityReactiveClient.java => systemmanaged/AzureSystemManagedIdentityRestClient.java} (82%) create mode 100644 src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClient.java rename src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/{AzureWorkloadIdentityReactiveClient.java => workload/AzureWorkloadIdentityRestClient.java} (91%) delete mode 100644 src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactoryTest.java create mode 100644 src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClientTest.java create mode 100644 src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClientTest.java rename src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/{AzureWorkloadIdentityReactiveClientTest.java => workload/AzureWorkloadIdentityRestClientTest.java} (69%) diff --git a/dep-sha256.json b/dep-sha256.json index 8ad0e25..18308f1 100644 --- a/dep-sha256.json +++ b/dep-sha256.json @@ -281,116 +281,18 @@ "sha256": "aczARIfod3nUlwqlDGc8w0qd8IDBwOjY6rLotG-CXPQ=" }, { - "id": "io.quarkus:quarkus-rest-client-jackson:jar:3.13.0", - "artifactId": "quarkus-rest-client-jackson", - "groupId": "io.quarkus", - "version": "3.13.0", - "sha256": "zxHuQ-UuHRT0t1wGZd-9u5RXZaqHkt2H3odP4shGerI=" - }, - { - "id": "io.quarkus.resteasy.reactive:resteasy-reactive-jackson:jar:3.13.0", - "artifactId": "resteasy-reactive-jackson", - "groupId": "io.quarkus.resteasy.reactive", - "version": "3.13.0", - "sha256": "P3N0HThUiFZvIZHdNB_tPGjkHnigfpnwGffuPp7yc44=" - }, - { - "id": "io.quarkus.resteasy.reactive:resteasy-reactive:jar:3.13.0", - "artifactId": "resteasy-reactive", - "groupId": "io.quarkus.resteasy.reactive", - "version": "3.13.0", - "sha256": "hB36oSImbWlar3jPhEscObYxtil1KA4E34mCyomFBCE=" - }, - { - "id": "io.quarkus.resteasy.reactive:resteasy-reactive-common:jar:3.13.0", - "artifactId": "resteasy-reactive-common", - "groupId": "io.quarkus.resteasy.reactive", - "version": "3.13.0", - "sha256": "uUpkpRXTlZv02Iwi_7rxrflZYxdeEwhOd0tWaM4rses=" - }, - { - "id": "io.quarkus.resteasy.reactive:resteasy-reactive-common-types:jar:3.13.0", - "artifactId": "resteasy-reactive-common-types", - "groupId": "io.quarkus.resteasy.reactive", - "version": "3.13.0", - "sha256": "lomo8inmaQP-jg-AVsMP35NmuAtNbthO4xjdoeM4Fsk=" - }, - { - "id": "org.reactivestreams:reactive-streams:jar:1.0.4", - "artifactId": "reactive-streams", - "groupId": "org.reactivestreams", - "version": "1.0.4", - "sha256": "91yll3ibPaxY9hhXuawuEDSmj6Zy2zUFWo-0UJ4yXyg=" - }, - { - "id": "io.smallrye.reactive:mutiny-zero-flow-adapters:jar:1.1.0", - "artifactId": "mutiny-zero-flow-adapters", - "groupId": "io.smallrye.reactive", - "version": "1.1.0", - "sha256": "iSFg510nP6mrzprx8nZ6vwDjwn6L_neTdXZKRnFhSNo=" - }, - { - "id": "jakarta.ws.rs:jakarta.ws.rs-api:jar:3.1.0", - "artifactId": "jakarta.ws.rs-api", - "groupId": "jakarta.ws.rs", - "version": "3.1.0", - "sha256": "azs2KLi0rt2g0kwzVDNemFSX2O88UQuPMCjpINW4Zj0=" - }, - { - "id": "com.fasterxml.jackson.core:jackson-databind:jar:2.17.2", - "artifactId": "jackson-databind", - "groupId": "com.fasterxml.jackson.core", - "version": "2.17.2", - "sha256": "wEmT8zwPhFNCZTeE8U84Nz0AUoDmNZ21-AhwHPrnPAw=" - }, - { - "id": "com.fasterxml.jackson.core:jackson-annotations:jar:2.17.2", - "artifactId": "jackson-annotations", - "groupId": "com.fasterxml.jackson.core", - "version": "2.17.2", - "sha256": "hzpgbiNQeWn5u76pOdXhknSoh3XqWhabp-LXlapRVuE=" - }, - { - "id": "com.fasterxml.jackson.core:jackson-core:jar:2.17.2", - "artifactId": "jackson-core", - "groupId": "com.fasterxml.jackson.core", - "version": "2.17.2", - "sha256": "choYkkHasFJdnoWOXLYE0-zA7eCB4t531vNPpXeaW0Y=" - }, - { - "id": "org.jboss.logging:commons-logging-jboss-logging:jar:1.0.0.Final", - "artifactId": "commons-logging-jboss-logging", - "groupId": "org.jboss.logging", - "version": "1.0.0.Final", - "sha256": "8SF2Jj6iX054u0-ks20zWilzjd5qgSPhttqJplXRUP8=" - }, - { - "id": "jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.2", - "artifactId": "jakarta.xml.bind-api", - "groupId": "jakarta.xml.bind", - "version": "4.0.2", - "sha256": "DWvP5Hdj6FBHrPfDmDNtyE_4XrytCny287nT6YEkVAY=" - }, - { - "id": "jakarta.activation:jakarta.activation-api:jar:2.1.3", - "artifactId": "jakarta.activation-api", - "groupId": "jakarta.activation", - "version": "2.1.3", - "sha256": "AbF21xihaSY-eCkGkfxHmXcYa8xrMzSHMlCE1lhvRic=" - }, - { - "id": "io.quarkus:quarkus-rest-jackson-common:jar:3.13.0", - "artifactId": "quarkus-rest-jackson-common", + "id": "io.quarkus:quarkus-rest-client:jar:3.13.0", + "artifactId": "quarkus-rest-client", "groupId": "io.quarkus", "version": "3.13.0", - "sha256": "pocU1Fr4HfKgHenLueNIgj0e-MJaemLemJOnPOumT5M=" + "sha256": "WrjWnOLgctmacyLzPRUj1-QZMI-79EwEUk2NwWTspNM=" }, { - "id": "io.quarkus:quarkus-rest-common:jar:3.13.0", - "artifactId": "quarkus-rest-common", + "id": "io.quarkus:quarkus-rest-client-jaxrs:jar:3.13.0", + "artifactId": "quarkus-rest-client-jaxrs", "groupId": "io.quarkus", "version": "3.13.0", - "sha256": "x8s7Igi4BclyytE6VRPqUXJ7qoqlov0IH4mI1sOseAI=" + "sha256": "GQVxnkYkNUqyjt4sMYRGV3j8ANJpO0PTkLnwSNAkUcQ=" }, { "id": "io.quarkus:quarkus-vertx:jar:3.13.0", @@ -540,53 +442,39 @@ "sha256": "lpHULcnS0Q93_vv5tRuSHCCbkcY4a1xYGZI4uVPsXpE=" }, { - "id": "io.quarkus:quarkus-jackson:jar:3.13.0", - "artifactId": "quarkus-jackson", + "id": "io.quarkus:quarkus-rest-common:jar:3.13.0", + "artifactId": "quarkus-rest-common", "groupId": "io.quarkus", "version": "3.13.0", - "sha256": "qzfMkNp3GcxvpZ-bYNpbCTSVjYzDq6gbtym612VnhmI=" - }, - { - "id": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.17.2", - "artifactId": "jackson-datatype-jsr310", - "groupId": "com.fasterxml.jackson.datatype", - "version": "2.17.2", - "sha256": "m4ACSpgi5wsH9rsTgkx2wTfBBkobXrUYN0qxQYcP28w=" - }, - { - "id": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.17.2", - "artifactId": "jackson-datatype-jdk8", - "groupId": "com.fasterxml.jackson.datatype", - "version": "2.17.2", - "sha256": "qqmNPtq_UEJr2CL60UQvva2m5HCWkybLyrXCeY8XONk=" + "sha256": "x8s7Igi4BclyytE6VRPqUXJ7qoqlov0IH4mI1sOseAI=" }, { - "id": "com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.17.2", - "artifactId": "jackson-module-parameter-names", - "groupId": "com.fasterxml.jackson.module", - "version": "2.17.2", - "sha256": "HuXi81k9RHJrkAhoxvZNGlBjalaD1tQEJ_WYSmHeU8A=" + "id": "io.quarkus.resteasy.reactive:resteasy-reactive-common:jar:3.13.0", + "artifactId": "resteasy-reactive-common", + "groupId": "io.quarkus.resteasy.reactive", + "version": "3.13.0", + "sha256": "uUpkpRXTlZv02Iwi_7rxrflZYxdeEwhOd0tWaM4rses=" }, { - "id": "io.quarkus:quarkus-jsonp:jar:3.13.0", - "artifactId": "quarkus-jsonp", - "groupId": "io.quarkus", + "id": "io.quarkus.resteasy.reactive:resteasy-reactive-common-types:jar:3.13.0", + "artifactId": "resteasy-reactive-common-types", + "groupId": "io.quarkus.resteasy.reactive", "version": "3.13.0", - "sha256": "FRdota5fRp-teuRt35RmWNedI8O5Nu959bYNQeXTnug=" + "sha256": "lomo8inmaQP-jg-AVsMP35NmuAtNbthO4xjdoeM4Fsk=" }, { - "id": "io.quarkus:quarkus-rest-client:jar:3.13.0", - "artifactId": "quarkus-rest-client", - "groupId": "io.quarkus", - "version": "3.13.0", - "sha256": "WrjWnOLgctmacyLzPRUj1-QZMI-79EwEUk2NwWTspNM=" + "id": "org.reactivestreams:reactive-streams:jar:1.0.4", + "artifactId": "reactive-streams", + "groupId": "org.reactivestreams", + "version": "1.0.4", + "sha256": "91yll3ibPaxY9hhXuawuEDSmj6Zy2zUFWo-0UJ4yXyg=" }, { - "id": "io.quarkus:quarkus-rest-client-jaxrs:jar:3.13.0", - "artifactId": "quarkus-rest-client-jaxrs", - "groupId": "io.quarkus", - "version": "3.13.0", - "sha256": "GQVxnkYkNUqyjt4sMYRGV3j8ANJpO0PTkLnwSNAkUcQ=" + "id": "io.smallrye.reactive:mutiny-zero-flow-adapters:jar:1.1.0", + "artifactId": "mutiny-zero-flow-adapters", + "groupId": "io.smallrye.reactive", + "version": "1.1.0", + "sha256": "iSFg510nP6mrzprx8nZ6vwDjwn6L_neTdXZKRnFhSNo=" }, { "id": "io.quarkus.resteasy.reactive:resteasy-reactive-client:jar:3.13.0", @@ -595,6 +483,13 @@ "version": "3.13.0", "sha256": "JYDxslXer-Spavvsptc8FVwWB6ZcZq0qZ8Gxd1-v38Y=" }, + { + "id": "jakarta.ws.rs:jakarta.ws.rs-api:jar:3.1.0", + "artifactId": "jakarta.ws.rs-api", + "groupId": "jakarta.ws.rs", + "version": "3.1.0", + "sha256": "azs2KLi0rt2g0kwzVDNemFSX2O88UQuPMCjpINW4Zj0=" + }, { "id": "io.vertx:vertx-core:jar:4.5.8", "artifactId": "vertx-core", @@ -686,6 +581,13 @@ "version": "3.13.0", "sha256": "45SaixKYt0ago1tvjBZ-2_lkCjQcsLSRfUQqu9SHhNc=" }, + { + "id": "io.smallrye:jandex:jar:3.2.0", + "artifactId": "jandex", + "groupId": "io.smallrye", + "version": "3.2.0", + "sha256": "baPpzo0MCkM_PnzmEKPGasywDHH-5nqg_z5ahBOVrBU=" + }, { "id": "io.quarkus:quarkus-tls-registry:jar:3.13.0", "artifactId": "quarkus-tls-registry", @@ -721,6 +623,111 @@ "version": "3.0.1", "sha256": "K3Fpi5XIksxlkjX0_-ZjteLxFRzrTncmiyP_KwxjIcs=" }, + { + "id": "io.quarkus:quarkus-rest-client-jackson:jar:3.13.0", + "artifactId": "quarkus-rest-client-jackson", + "groupId": "io.quarkus", + "version": "3.13.0", + "sha256": "zxHuQ-UuHRT0t1wGZd-9u5RXZaqHkt2H3odP4shGerI=" + }, + { + "id": "io.quarkus.resteasy.reactive:resteasy-reactive-jackson:jar:3.13.0", + "artifactId": "resteasy-reactive-jackson", + "groupId": "io.quarkus.resteasy.reactive", + "version": "3.13.0", + "sha256": "P3N0HThUiFZvIZHdNB_tPGjkHnigfpnwGffuPp7yc44=" + }, + { + "id": "io.quarkus.resteasy.reactive:resteasy-reactive:jar:3.13.0", + "artifactId": "resteasy-reactive", + "groupId": "io.quarkus.resteasy.reactive", + "version": "3.13.0", + "sha256": "hB36oSImbWlar3jPhEscObYxtil1KA4E34mCyomFBCE=" + }, + { + "id": "com.fasterxml.jackson.core:jackson-databind:jar:2.17.2", + "artifactId": "jackson-databind", + "groupId": "com.fasterxml.jackson.core", + "version": "2.17.2", + "sha256": "wEmT8zwPhFNCZTeE8U84Nz0AUoDmNZ21-AhwHPrnPAw=" + }, + { + "id": "com.fasterxml.jackson.core:jackson-annotations:jar:2.17.2", + "artifactId": "jackson-annotations", + "groupId": "com.fasterxml.jackson.core", + "version": "2.17.2", + "sha256": "hzpgbiNQeWn5u76pOdXhknSoh3XqWhabp-LXlapRVuE=" + }, + { + "id": "com.fasterxml.jackson.core:jackson-core:jar:2.17.2", + "artifactId": "jackson-core", + "groupId": "com.fasterxml.jackson.core", + "version": "2.17.2", + "sha256": "choYkkHasFJdnoWOXLYE0-zA7eCB4t531vNPpXeaW0Y=" + }, + { + "id": "org.jboss.logging:commons-logging-jboss-logging:jar:1.0.0.Final", + "artifactId": "commons-logging-jboss-logging", + "groupId": "org.jboss.logging", + "version": "1.0.0.Final", + "sha256": "8SF2Jj6iX054u0-ks20zWilzjd5qgSPhttqJplXRUP8=" + }, + { + "id": "jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.2", + "artifactId": "jakarta.xml.bind-api", + "groupId": "jakarta.xml.bind", + "version": "4.0.2", + "sha256": "DWvP5Hdj6FBHrPfDmDNtyE_4XrytCny287nT6YEkVAY=" + }, + { + "id": "jakarta.activation:jakarta.activation-api:jar:2.1.3", + "artifactId": "jakarta.activation-api", + "groupId": "jakarta.activation", + "version": "2.1.3", + "sha256": "AbF21xihaSY-eCkGkfxHmXcYa8xrMzSHMlCE1lhvRic=" + }, + { + "id": "io.quarkus:quarkus-rest-jackson-common:jar:3.13.0", + "artifactId": "quarkus-rest-jackson-common", + "groupId": "io.quarkus", + "version": "3.13.0", + "sha256": "pocU1Fr4HfKgHenLueNIgj0e-MJaemLemJOnPOumT5M=" + }, + { + "id": "io.quarkus:quarkus-jackson:jar:3.13.0", + "artifactId": "quarkus-jackson", + "groupId": "io.quarkus", + "version": "3.13.0", + "sha256": "qzfMkNp3GcxvpZ-bYNpbCTSVjYzDq6gbtym612VnhmI=" + }, + { + "id": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.17.2", + "artifactId": "jackson-datatype-jsr310", + "groupId": "com.fasterxml.jackson.datatype", + "version": "2.17.2", + "sha256": "m4ACSpgi5wsH9rsTgkx2wTfBBkobXrUYN0qxQYcP28w=" + }, + { + "id": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.17.2", + "artifactId": "jackson-datatype-jdk8", + "groupId": "com.fasterxml.jackson.datatype", + "version": "2.17.2", + "sha256": "qqmNPtq_UEJr2CL60UQvva2m5HCWkybLyrXCeY8XONk=" + }, + { + "id": "com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.17.2", + "artifactId": "jackson-module-parameter-names", + "groupId": "com.fasterxml.jackson.module", + "version": "2.17.2", + "sha256": "HuXi81k9RHJrkAhoxvZNGlBjalaD1tQEJ_WYSmHeU8A=" + }, + { + "id": "io.quarkus:quarkus-jsonp:jar:3.13.0", + "artifactId": "quarkus-jsonp", + "groupId": "io.quarkus", + "version": "3.13.0", + "sha256": "FRdota5fRp-teuRt35RmWNedI8O5Nu959bYNQeXTnug=" + }, { "id": "org.projectlombok:lombok:jar:1.18.34", "artifactId": "lombok", @@ -1232,13 +1239,6 @@ "version": "3.13.0", "sha256": "N49Ux3jkGlyB2wzgBCldQu01OCLIdM_IDJBSxyZYW-0=" }, - { - "id": "io.smallrye:jandex:jar:3.2.0", - "artifactId": "jandex", - "groupId": "io.smallrye", - "version": "3.2.0", - "sha256": "baPpzo0MCkM_PnzmEKPGasywDHH-5nqg_z5ahBOVrBU=" - }, { "id": "commons-io:commons-io:jar:2.16.1", "artifactId": "commons-io", diff --git a/pom.xml b/pom.xml index 0d3695c..a824d66 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 it.pagopa.swclient.mil azure-services - 5.3.0 + 5.3.0-pluto mil-azure-services Library which provides a set of clients and utilities based on @@ -91,6 +91,10 @@ io.quarkus quarkus-arc + + io.quarkus + quarkus-rest-client + io.quarkus quarkus-rest-client-jackson @@ -150,6 +154,7 @@ build generate-code generate-code-tests + native-image-agent diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClient.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityClient.java similarity index 78% rename from src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClient.java rename to src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityClient.java index 35ae68e..c5f8888 100644 --- a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClient.java +++ b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityClient.java @@ -1,5 +1,5 @@ /* - * AzureIdentityReactiveClient.java + * AzureIdentityClient.java * * 17 mag 2024 */ @@ -10,12 +10,12 @@ /** *

- * Reactive REST client to get access token from Microsoft Entra ID. + * Reactive client to get access token from Microsoft Entra ID. *

* * @author Antonio Tarricone */ -public interface AzureIdentityReactiveClient { +public interface AzureIdentityClient { /** *

* Retrieves an access token for an Azure resource. diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactory.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactory.java deleted file mode 100644 index 56e7969..0000000 --- a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactory.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * AzureIdentityReactiveClientFactory.java - * - * 4 ago 2024 - */ -package it.pagopa.swclient.mil.azureservices.identity.client; - -import java.net.URI; -import java.util.Optional; - -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import io.quarkus.logging.Log; -import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; -import jakarta.enterprise.inject.Produces; -import jakarta.enterprise.inject.spi.DeploymentException; -import jakarta.inject.Inject; -import jakarta.ws.rs.ext.Provider; - -/** - *

- * Initializes the right Azure Identity REST client depending on environment variables found. - *

- * - *

- * If environment variables IDENTITY_ENDPOINT and IDENTITY_HEADER are set, - * System Assigned Managed Identity will be used. - *

- * - *

- * If environment variables AZURE_FEDERATED_TOKEN_FILE, AZURE_TENANT_ID, - * AZURE_CLIENT_ID and AZURE_AUTHORITY_HOST are set, Workload - * Identity will be used. - *

- * - * @author Antonio Tarricone - */ -@Provider -public class AzureIdentityReactiveClientFactory { - /** - *

- * Endpoint to get access token by means of system managed identity. - *

- */ - private Optional identityEndpoint; - - /** - *

- * Value to use to set x-identity-header. - *

- */ - private Optional identityHeader; - - /** - *

- * Endpoint to get access token by means of workload identity. - *

- */ - private Optional authorityHost; - - /** - *

- * Tenant ID. - *

- */ - private Optional tenantId; - - /** - *

- * Client ID. - *

- */ - private Optional clientId; - - /** - *

- * Token file with client assertion. - *

- */ - private Optional federatedTokenFile; - - /** - *

- * Constructor. - *

- * - * @param identityEndpoint Endpoint to get access token by means of system managed identity - * @param identityHeader Value to use to set x-identity-header - * @param authorityHost Endpoint to get access token by means of workload identity - * @param tenantId Tenant ID - * @param clientId Client ID - * @param federatedTokenFile Token file with client assertion - */ - @Inject - AzureIdentityReactiveClientFactory( - @ConfigProperty(name = "IDENTITY_ENDPOINT") Optional identityEndpoint, - @ConfigProperty(name = "IDENTITY_HEADER") Optional identityHeader, - @ConfigProperty(name = "AZURE_AUTHORITY_HOST") Optional authorityHost, - @ConfigProperty(name = "AZURE_TENANT_ID") Optional tenantId, - @ConfigProperty(name = "AZURE_CLIENT_ID") Optional clientId, - @ConfigProperty(name = "AZURE_FEDERATED_TOKEN_FILE") Optional federatedTokenFile) { - this.identityEndpoint = identityEndpoint; - this.identityHeader = identityHeader; - this.authorityHost = authorityHost; - this.tenantId = tenantId; - this.clientId = clientId; - this.federatedTokenFile = federatedTokenFile; - } - - /** - *

- * Initializes the right Azure Identity REST client depending on environment variables found. - *

- * - * @return {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClient - * AzureIdentityReactiveClient} - */ - @Produces - AzureIdentityReactiveClient get() { - Log.trace("Azure Identity REST Client factory invoked!"); - - if (identityEndpoint.isPresent() && identityHeader.isPresent()) { - Log.debug("Azure System Managed Identity will be use"); - return QuarkusRestClientBuilder.newBuilder() - .baseUri(URI.create(identityEndpoint.get())) - .build(AzureSystemManagedIdentityReactiveClient.class); - } else if (authorityHost.isPresent() && tenantId.isPresent() && clientId.isPresent() && federatedTokenFile.isPresent()) { - Log.debug("Azure Workload Identity will be use"); - return QuarkusRestClientBuilder.newBuilder() - .baseUri(URI.create(authorityHost.get() + tenantId.get())) - .build(AzureWorkloadIdentityReactiveClient.class); - } else { - Log.fatal("IDENTITY_ENDPOINT and IDENTITY_HEADER must not be null or AZURE_AUTHORITY_HOST and AZURE_TENANT_ID and AZURE_CLIENT_ID and AZURE_FEDERATED_TOKEN_FILE must not be null"); - throw new DeploymentException("IDENTITY_ENDPOINT and IDENTITY_HEADER must not be null or AZURE_AUTHORITY_HOST and AZURE_TENANT_ID and AZURE_CLIENT_ID and AZURE_FEDERATED_TOKEN_FILE must not be null"); - } - } -} diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClient.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClient.java new file mode 100644 index 0000000..567038c --- /dev/null +++ b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClient.java @@ -0,0 +1,62 @@ +/* + * AzureSystemManagedIdentityClient.java + * + * 7 ago 2024 + */ +package it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged; + +import java.net.URI; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.logging.Log; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.smallrye.mutiny.Uni; +import it.pagopa.swclient.mil.azureservices.identity.bean.AccessToken; +import it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityClient; +import jakarta.enterprise.context.ApplicationScoped; + +/** + *

+ * Reactive client (it's a proxy of REST client) to get access token from Microsoft Entra ID by + * means of System Managed Identity. + *

+ * + * @author Antonio Tarricone + */ +@ApplicationScoped +public class AzureSystemManagedIdentityClient implements AzureIdentityClient { + /** + *

+ * Reactive REST client to get access token from Microsoft Entra ID by means of System Managed + * Identity. + *

+ * + * @see it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged.AzureSystemManagedIdentityRestClient + * AzureSystemManagedIdentityRestClient + */ + private AzureSystemManagedIdentityRestClient restClient; + + /** + *

+ * Constructor. + *

+ * + * @param identityEndpoint Endpoint to get access token by means of system managed identity + */ + AzureSystemManagedIdentityClient(@ConfigProperty(name = "IDENTITY_ENDPOINT", defaultValue = "") String identityEndpoint) { + Log.trace("Azure System Managed Identity client initialization"); + restClient = QuarkusRestClientBuilder.newBuilder() + .baseUri(URI.create(identityEndpoint)) + .build(AzureSystemManagedIdentityRestClient.class); + } + + /** + * @see it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged.AzureSystemManagedIdentityRestClient#getAccessToken(String) + */ + @Override + public Uni getAccessToken(String scope) { + Log.tracef("Get access token with System Managed Identity for %s", scope); + return restClient.getAccessToken(scope); + } +} diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureSystemManagedIdentityReactiveClient.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityRestClient.java similarity index 82% rename from src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureSystemManagedIdentityReactiveClient.java rename to src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityRestClient.java index 015bd7b..1a32def 100644 --- a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureSystemManagedIdentityReactiveClient.java +++ b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityRestClient.java @@ -1,9 +1,9 @@ /* - * AzureIdentityReactiveClient.java + * AzureSystemManagedIdentityRestClient.java * * 17 mag 2024 */ -package it.pagopa.swclient.mil.azureservices.identity.client; +package it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged; import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; @@ -17,12 +17,13 @@ /** *

- * Reactive REST client to get access token from Microsoft Entra ID. + * Reactive REST client to get access token from Microsoft Entra ID by means of System Managed + * Identity. *

* * @author Antonio Tarricone */ -public interface AzureSystemManagedIdentityReactiveClient extends AzureIdentityReactiveClient { +public interface AzureSystemManagedIdentityRestClient { /** *

* Retrieves an access token for an Azure resource. diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClient.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClient.java new file mode 100644 index 0000000..a8712cf --- /dev/null +++ b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClient.java @@ -0,0 +1,64 @@ +/* + * AzureWorkloadIdentityClient.java + * + * 7 ago 2024 + */ +package it.pagopa.swclient.mil.azureservices.identity.client.workload; + +import java.net.URI; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.logging.Log; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.smallrye.mutiny.Uni; +import it.pagopa.swclient.mil.azureservices.identity.bean.AccessToken; +import it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityClient; +import jakarta.enterprise.context.ApplicationScoped; + +/** + *

+ * Reactive client (it's a proxy of REST client) to get access token from Microsoft Entra ID by + * means of Workload Identity. + *

+ * + * @author Antonio Tarricone + */ +@ApplicationScoped +public class AzureWorkloadIdentityClient implements AzureIdentityClient { + /** + *

+ * Reactive REST client to get access token from Microsoft Entra ID by means of Workload Identity. + *

+ * + * @see it.pagopa.swclient.mil.azureservices.identity.client.workload.AzureWorkloadIdentityRestClient + * AzureWorkloadIdentityRestClient + */ + private AzureWorkloadIdentityRestClient restClient; + + /** + *

+ * Constructor. + *

+ * + * @param authorityHost Endpoint to get access token by means of workload identity + * @param tenantId Tenant ID + */ + AzureWorkloadIdentityClient( + @ConfigProperty(name = "AZURE_AUTHORITY_HOST", defaultValue = "") String authorityHost, + @ConfigProperty(name = "AZURE_TENANT_ID", defaultValue = "") String tenantId) { + Log.trace("Azure Workload Identity client initialization"); + restClient = QuarkusRestClientBuilder.newBuilder() + .baseUri(URI.create(authorityHost + tenantId)) + .build(AzureWorkloadIdentityRestClient.class); + } + + /** + * @see it.pagopa.swclient.mil.azureservices.identity.client.workload.AzureWorkloadIdentityRestClient#getAccessToken(String) + */ + @Override + public Uni getAccessToken(String scope) { + Log.tracef("Get access token with Workload Identity for %s", scope); + return restClient.getAccessToken(scope); + } +} \ No newline at end of file diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureWorkloadIdentityReactiveClient.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityRestClient.java similarity index 91% rename from src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureWorkloadIdentityReactiveClient.java rename to src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityRestClient.java index aed9fc3..7a980c3 100644 --- a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureWorkloadIdentityReactiveClient.java +++ b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityRestClient.java @@ -1,9 +1,9 @@ /* - * AzureWorkloadIdentityReactiveClient.java + * AzureWorkloadIdentityRestClient.java * * 3 ago 2024 */ -package it.pagopa.swclient.mil.azureservices.identity.client; +package it.pagopa.swclient.mil.azureservices.identity.client.workload; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -32,7 +32,7 @@ * @author Antonio Tarricone */ @Path("/oauth2/v2.0/token") -public interface AzureWorkloadIdentityReactiveClient extends AzureIdentityReactiveClient { +public interface AzureWorkloadIdentityRestClient { /** *

* Retrieves an access token for an Azure resource. diff --git a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveService.java b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveService.java index 8df6952..3fe15ff 100644 --- a/src/main/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveService.java +++ b/src/main/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveService.java @@ -8,12 +8,20 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; +import java.util.Optional; + +import org.eclipse.microprofile.config.inject.ConfigProperty; import io.quarkus.logging.Log; import io.smallrye.mutiny.Uni; import it.pagopa.swclient.mil.azureservices.identity.bean.AccessToken; -import it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClient; +import it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityClient; +import it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged.AzureSystemManagedIdentityClient; +import it.pagopa.swclient.mil.azureservices.identity.client.workload.AzureWorkloadIdentityClient; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.DeploymentException; import jakarta.inject.Inject; /** @@ -28,10 +36,10 @@ public class AzureIdentityReactiveService { /** *

- * Reactive rest client to retrieve an access token from Microsoft Entra ID. + * Reactive client to retrieve an access token from Microsoft Entra ID. *

*/ - AzureIdentityReactiveClient identityClient; + private AzureIdentityClient identityClient; /** *

@@ -46,20 +54,41 @@ public class AzureIdentityReactiveService { * Constructor. *

* - * @param identityClient {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClient - * AzureIdentityReactiveClient}. + * @param identityEndpoint Endpoint to get access token by means of system managed identity + * @param identityHeader Value to use to set x-identity-header + * @param authorityHost Endpoint to get access token by means of workload identity + * @param tenantId Tenant ID + * @param clientId Client ID + * @param federatedTokenFile Token file with client assertion + * @param anyIdentityClient Any identity client */ @Inject - AzureIdentityReactiveService(AzureIdentityReactiveClient identityClient) { + AzureIdentityReactiveService( + @ConfigProperty(name = "IDENTITY_ENDPOINT") Optional identityEndpoint, + @ConfigProperty(name = "IDENTITY_HEADER") Optional identityHeader, + @ConfigProperty(name = "AZURE_AUTHORITY_HOST") Optional authorityHost, + @ConfigProperty(name = "AZURE_TENANT_ID") Optional tenantId, + @ConfigProperty(name = "AZURE_CLIENT_ID") Optional clientId, + @ConfigProperty(name = "AZURE_FEDERATED_TOKEN_FILE") Optional federatedTokenFile, + @Any Instance anyIdentityClient) { /* - * Initialize access token cache. + * Initialize identity client. */ - cache = new HashMap<>(); + if (identityEndpoint.isPresent() && identityHeader.isPresent()) { + Log.debug("Azure System Managed Identity will be use"); + identityClient = anyIdentityClient.select(AzureSystemManagedIdentityClient.class).get(); + } else if (authorityHost.isPresent() && tenantId.isPresent() && clientId.isPresent() && federatedTokenFile.isPresent()) { + Log.debug("Azure Workload Identity will be use"); + identityClient = anyIdentityClient.select(AzureWorkloadIdentityClient.class).get(); + } else { + Log.fatal("IDENTITY_ENDPOINT and IDENTITY_HEADER must not be null or AZURE_AUTHORITY_HOST and AZURE_TENANT_ID and AZURE_CLIENT_ID and AZURE_FEDERATED_TOKEN_FILE must not be null"); + throw new DeploymentException("IDENTITY_ENDPOINT and IDENTITY_HEADER must not be null or AZURE_AUTHORITY_HOST and AZURE_TENANT_ID and AZURE_CLIENT_ID and AZURE_FEDERATED_TOKEN_FILE must not be null"); + } /* - * Initialize of the REST client. + * Initialize access token cache. */ - this.identityClient = identityClient; + cache = new HashMap<>(); } /** @@ -106,4 +135,16 @@ public Uni getAccessToken(String scope) { public void clearAccessTokenCache() { cache.clear(); } + + /** + *

+ * Returns identity client in use. + *

+ * + * @return {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityClient + * AzureIdentityClient} + */ + public AzureIdentityClient getIdentityClient() { + return identityClient; + } } \ No newline at end of file diff --git a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactoryTest.java b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactoryTest.java deleted file mode 100644 index e400fad..0000000 --- a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureIdentityReactiveClientFactoryTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * AzureIdentityReactiveClientFactoryTest.java - * - * 5 ago 2024 - */ -package it.pagopa.swclient.mil.azureservices.identity.client; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Optional; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; - -import io.quarkus.test.junit.QuarkusTest; -import jakarta.enterprise.inject.spi.DeploymentException; - -/** - * - * @author Antonio Tarricone - */ -@QuarkusTest -class AzureIdentityReactiveClientFactoryTest { - /** - * - * @param testInfo - */ - @BeforeEach - void init(TestInfo testInfo) { - String frame = "*".repeat(testInfo.getDisplayName().length() + 11); - System.out.println(frame); - System.out.printf("* %s: START *%n", testInfo.getDisplayName()); - System.out.println(frame); - } - - /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClientFactory#get()}. - */ - @Test - void given_systemManagedIdEnvironment_when_invokeGet_then_returnSuitableClient() { - AzureIdentityReactiveClientFactory factory = new AzureIdentityReactiveClientFactory( - Optional.of("https://login.microsoftonline.com/"), - Optional.of("45ed57a0-ec26-41c9-8333-29daf37697d3"), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty()); - - assertTrue(factory.get() instanceof AzureSystemManagedIdentityReactiveClient); - } - - /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClientFactory#get()}. - */ - @Test - void given_partialsystemManagedIdEnvironment_when_invokeGet_then_throwException() { - AzureIdentityReactiveClientFactory factory = new AzureIdentityReactiveClientFactory( - Optional.of("https://login.microsoftonline.com/"), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty()); - - assertThrows( - DeploymentException.class, - () -> factory.get()); - } - - /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClientFactory#get()}. - */ - @Test - void given_workloadIdEnvironment_when_invokeGet_then_returnSuitableClient() { - AzureIdentityReactiveClientFactory factory = new AzureIdentityReactiveClientFactory( - Optional.empty(), - Optional.empty(), - Optional.of("https://login.microsoftonline.com/"), - Optional.of("da795842-fa15-4fd4-b556-f371ac9bafed"), - Optional.of("aeeb30a1-2d89-42bd-832c-69dc15a53d36"), - Optional.of("/var/run/secrets/azure/tokens/azure-identity-token")); - - assertTrue(factory.get() instanceof AzureWorkloadIdentityReactiveClient); - } - - /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClientFactory#get()}. - */ - @Test - void given_partialWorkloadIdEnvironment_when_invokeGet_then_throwException() { - AzureIdentityReactiveClientFactory factory = new AzureIdentityReactiveClientFactory( - Optional.empty(), - Optional.empty(), - Optional.of("https://login.microsoftonline.com/"), - Optional.empty(), - Optional.empty(), - Optional.empty()); - - assertThrows( - DeploymentException.class, - () -> factory.get()); - } - - /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClientFactory#get()}. - */ - @Test - void given_noIdentityEnvironment_when_invokeGet_then_throwException() { - AzureIdentityReactiveClientFactory factory = new AzureIdentityReactiveClientFactory( - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty()); - - assertThrows( - DeploymentException.class, - () -> factory.get()); - } -} diff --git a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClientTest.java b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClientTest.java new file mode 100644 index 0000000..25d10e8 --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/systemmanaged/AzureSystemManagedIdentityClientTest.java @@ -0,0 +1,94 @@ +/* + * AzureSystemManagedIdentityClientTest.java + * + * 7 ago 2024 + */ +package it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.mockito.MockedStatic; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.test.junit.QuarkusTest; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; +import it.pagopa.swclient.mil.azureservices.identity.bean.AccessToken; +import it.pagopa.swclient.mil.azureservices.identity.bean.Scope; + +/** + * + * @author Antonio.tarricone + */ +@QuarkusTest +class AzureSystemManagedIdentityClientTest { + /** + * + * @param testInfo + */ + @BeforeEach + void init(TestInfo testInfo) { + String frame = "*".repeat(testInfo.getDisplayName().length() + 11); + System.out.println(frame); + System.out.printf("* %s: START *%n", testInfo.getDisplayName()); + System.out.println(frame); + } + + /** + * + */ + @Test + void given_requestToGetAccessToken_when_requestIsDone_then_returnAccessToken() { + /* + * Mocking of REST client. + */ + Instant now = Instant.now(); + AccessToken accessToken = new AccessToken() + .setExpiresOn(now.plus(5, ChronoUnit.MINUTES).getEpochSecond()) + .setValue("access_token_string"); + + AzureSystemManagedIdentityRestClient restClient = mock(AzureSystemManagedIdentityRestClient.class); + when(restClient.getAccessToken(Scope.STORAGE)) + .thenReturn(Uni.createFrom() + .item(accessToken)); + + /* + * Mocking of QuarkusRestClientBuilder. + */ + QuarkusRestClientBuilder clientBuilder = mock(QuarkusRestClientBuilder.class); + + when(clientBuilder.build(AzureSystemManagedIdentityRestClient.class)) + .thenReturn(restClient); + + when(clientBuilder.baseUri(any(URI.class))) + .thenReturn(clientBuilder); + + /* + * Mocking of QuarkusRestClientBuilder factory. + */ + try (MockedStatic restClientBuilderFactory = mockStatic(QuarkusRestClientBuilder.class)) { + restClientBuilderFactory.when(() -> QuarkusRestClientBuilder.newBuilder()) + .thenReturn(clientBuilder); + + /* + * Test. + */ + AzureSystemManagedIdentityClient client = new AzureSystemManagedIdentityClient("https://login.microsoftonline.com/"); + client.getAccessToken(Scope.STORAGE) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .assertItem(accessToken); + } + } +} diff --git a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClientTest.java b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClientTest.java new file mode 100644 index 0000000..56dd1bd --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityClientTest.java @@ -0,0 +1,96 @@ +/* + * AzureWorkloadIdentityClientTest.java + * + * 7 ago 2024 + */ +package it.pagopa.swclient.mil.azureservices.identity.client.workload; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.mockito.MockedStatic; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.test.junit.QuarkusTest; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; +import it.pagopa.swclient.mil.azureservices.identity.bean.AccessToken; +import it.pagopa.swclient.mil.azureservices.identity.bean.Scope; + +/** + * + * @author Antonio.tarricone + */ +@QuarkusTest +class AzureWorkloadIdentityClientTest { + /** + * + * @param testInfo + */ + @BeforeEach + void init(TestInfo testInfo) { + String frame = "*".repeat(testInfo.getDisplayName().length() + 11); + System.out.println(frame); + System.out.printf("* %s: START *%n", testInfo.getDisplayName()); + System.out.println(frame); + } + + /** + * + */ + @Test + void given_requestToGetAccessToken_when_requestIsDone_then_returnAccessToken() { + /* + * Mocking of REST client. + */ + Instant now = Instant.now(); + AccessToken accessToken = new AccessToken() + .setExpiresOn(now.plus(5, ChronoUnit.MINUTES).getEpochSecond()) + .setValue("access_token_string"); + + AzureWorkloadIdentityRestClient restClient = mock(AzureWorkloadIdentityRestClient.class); + when(restClient.getAccessToken(Scope.STORAGE)) + .thenReturn(Uni.createFrom() + .item(accessToken)); + + /* + * Mocking of QuarkusRestClientBuilder. + */ + QuarkusRestClientBuilder clientBuilder = mock(QuarkusRestClientBuilder.class); + + when(clientBuilder.build(AzureWorkloadIdentityRestClient.class)) + .thenReturn(restClient); + + when(clientBuilder.baseUri(any(URI.class))) + .thenReturn(clientBuilder); + + /* + * Mocking of QuarkusRestClientBuilder factory. + */ + try (MockedStatic restClientBuilderFactory = mockStatic(QuarkusRestClientBuilder.class)) { + restClientBuilderFactory.when(() -> QuarkusRestClientBuilder.newBuilder()) + .thenReturn(clientBuilder); + + /* + * Test. + */ + AzureWorkloadIdentityClient client = new AzureWorkloadIdentityClient( + "https://login.microsoftonline.com/", + "da795842-fa15-4fd4-b556-f371ac9bafed"); + client.getAccessToken(Scope.STORAGE) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .assertItem(accessToken); + } + } +} diff --git a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureWorkloadIdentityReactiveClientTest.java b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityRestClientTest.java similarity index 69% rename from src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureWorkloadIdentityReactiveClientTest.java rename to src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityRestClientTest.java index b0af8d5..26fb8e4 100644 --- a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/AzureWorkloadIdentityReactiveClientTest.java +++ b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/client/workload/AzureWorkloadIdentityRestClientTest.java @@ -1,9 +1,9 @@ /* - * AzureWorkloadIdentityReactiveClientTest.java + * AzureWorkloadIdentityRestClientTest.java * - * 5 ago 2024 + * 7 ago 2024 */ -package it.pagopa.swclient.mil.azureservices.identity.client; +package it.pagopa.swclient.mil.azureservices.identity.client.workload; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -26,10 +26,10 @@ /** * - * @author antonio.tarricone + * @author Antonio Tarricone */ @QuarkusTest -class AzureWorkloadIdentityReactiveClientTest { +class AzureWorkloadIdentityRestClientTest { /** * * @param testInfo @@ -43,14 +43,13 @@ void init(TestInfo testInfo) { } /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureWorkloadIdentityReactiveClient#getClientAssertion(java.lang.String)}. + * */ @Test void given_tokenFile_when_invokeGetClientAssertion_then_returnFileContent() { - AzureWorkloadIdentityReactiveClient client = QuarkusRestClientBuilder.newBuilder() + AzureWorkloadIdentityRestClient client = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create("https://login.microsoftonline.com/da795842-fa15-4fd4-b556-f371ac9bafed")) - .build(AzureWorkloadIdentityReactiveClient.class); + .build(AzureWorkloadIdentityRestClient.class); assertEquals( "This is a test!", @@ -58,8 +57,7 @@ void given_tokenFile_when_invokeGetClientAssertion_then_returnFileContent() { } /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.client.AzureWorkloadIdentityReactiveClient#getClientAssertion(java.lang.String)}. + * */ @Test void given_ioExceptionReadingTokenFile_when_invokeGetClientAssertion_then_throwException() { @@ -72,9 +70,9 @@ void given_ioExceptionReadingTokenFile_when_invokeGetClientAssertion_then_throwE String.class)))) .thenThrow(IOException.class); - AzureWorkloadIdentityReactiveClient client = QuarkusRestClientBuilder.newBuilder() + AzureWorkloadIdentityRestClient client = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create("https://login.microsoftonline.com/da795842-fa15-4fd4-b556-f371ac9bafed")) - .build(AzureWorkloadIdentityReactiveClient.class); + .build(AzureWorkloadIdentityRestClient.class); assertThrows(DeploymentException.class, () -> client.getClientAssertion("client_assertion")); diff --git a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveServiceTest.java b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveServiceTest.java index a503c1e..bfb3372 100644 --- a/src/test/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveServiceTest.java +++ b/src/test/java/it/pagopa/swclient/mil/azureservices/identity/service/AzureIdentityReactiveServiceTest.java @@ -5,6 +5,8 @@ */ package it.pagopa.swclient.mil.azureservices.identity.service; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -13,6 +15,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,13 +26,18 @@ import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; import it.pagopa.swclient.mil.azureservices.identity.bean.AccessToken; import it.pagopa.swclient.mil.azureservices.identity.bean.Scope; -import it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityReactiveClient; +import it.pagopa.swclient.mil.azureservices.identity.client.AzureIdentityClient; +import it.pagopa.swclient.mil.azureservices.identity.client.systemmanaged.AzureSystemManagedIdentityClient; +import it.pagopa.swclient.mil.azureservices.identity.client.workload.AzureWorkloadIdentityClient; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.DeploymentException; /** * * @author Antonio Tarricone */ @QuarkusTest +@SuppressWarnings("unchecked") class AzureIdentityReactiveServiceTest { /** * @@ -44,8 +52,7 @@ void init(TestInfo testInfo) { } /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.service.AzureIdentityReactiveService#getAccessToken(it.pagopa.swclient.mil.azureservices.identity.bean.Scope)}. + * */ @Test void given_emptyCache_when_getAccessTokenInvoked_then_getNewOneCacheAndReturnIt() { @@ -57,14 +64,29 @@ void given_emptyCache_when_getAccessTokenInvoked_then_getNewOneCacheAndReturnIt( .setExpiresOn(now.plus(5, ChronoUnit.MINUTES).getEpochSecond()) .setValue("access_token_string"); - AzureIdentityReactiveClient identityClient = mock(AzureIdentityReactiveClient.class); + AzureWorkloadIdentityClient identityClient = mock(AzureWorkloadIdentityClient.class); when(identityClient.getAccessToken(Scope.VAULT)) .thenReturn(Uni.createFrom().item(accessToken)); + Instance identityClientInstance = mock(Instance.class); + when(identityClientInstance.get()) + .thenReturn(identityClient); + + Instance anyIdentityClient = mock(Instance.class); + when(anyIdentityClient.select(AzureWorkloadIdentityClient.class)) + .thenReturn(identityClientInstance); + /* * Test */ - AzureIdentityReactiveService identityService = spy(new AzureIdentityReactiveService(identityClient)); + AzureIdentityReactiveService identityService = spy(new AzureIdentityReactiveService( + Optional.empty(), + Optional.empty(), + Optional.of("https://login.microsoftonline.com/"), + Optional.of("da795842-fa15-4fd4-b556-f371ac9bafed"), + Optional.of("aeeb30a1-2d89-42bd-832c-69dc15a53d36"), + Optional.of("/var/run/secrets/azure/tokens/azure-identity-token"), + anyIdentityClient)); identityService.getAccessToken(Scope.VAULT) .subscribe() @@ -76,8 +98,7 @@ void given_emptyCache_when_getAccessTokenInvoked_then_getNewOneCacheAndReturnIt( } /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.service.AzureIdentityReactiveService#getAccessToken(it.pagopa.swclient.mil.azureservices.identity.bean.Scope)}. + * */ @Test void given_storedAccessToken_when_getAccessTokenInvoked_then_getReturnIt() { @@ -89,14 +110,29 @@ void given_storedAccessToken_when_getAccessTokenInvoked_then_getReturnIt() { .setExpiresOn(now.plus(5, ChronoUnit.MINUTES).getEpochSecond()) .setValue("access_token_string"); - AzureIdentityReactiveClient identityClient = mock(AzureIdentityReactiveClient.class); + AzureSystemManagedIdentityClient identityClient = mock(AzureSystemManagedIdentityClient.class); when(identityClient.getAccessToken(Scope.VAULT)) .thenReturn(Uni.createFrom().item(accessToken)); + Instance identityClientInstance = mock(Instance.class); + when(identityClientInstance.get()) + .thenReturn(identityClient); + + Instance anyIdentityClient = mock(Instance.class); + when(anyIdentityClient.select(AzureSystemManagedIdentityClient.class)) + .thenReturn(identityClientInstance); + /* * Test */ - AzureIdentityReactiveService identityService = spy(new AzureIdentityReactiveService(identityClient)); + AzureIdentityReactiveService identityService = spy(new AzureIdentityReactiveService( + Optional.of("https://login.microsoftonline.com/"), + Optional.of("45ed57a0-ec26-41c9-8333-29daf37697d3"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + anyIdentityClient)); identityService.getAccessToken(Scope.VAULT) .subscribe() @@ -114,10 +150,8 @@ void given_storedAccessToken_when_getAccessTokenInvoked_then_getReturnIt() { } /** - * Test method for - * {@link it.pagopa.swclient.mil.azureservices.identity.service.AzureIdentityReactiveService#getAccessToken(it.pagopa.swclient.mil.azureservices.identity.bean.Scope)}. + * */ - @SuppressWarnings("unchecked") @Test void given_expAccessTokenStored_when_getAccessTokenInvoked_then_getNewOneCacheAndReturnIt() { /* @@ -131,17 +165,31 @@ void given_expAccessTokenStored_when_getAccessTokenInvoked_then_getNewOneCacheAn .setExpiresOn(now.plus(5, ChronoUnit.MINUTES).getEpochSecond()) .setValue("access_token_string"); - AzureIdentityReactiveClient identityClient = mock(AzureIdentityReactiveClient.class); - + AzureWorkloadIdentityClient identityClient = mock(AzureWorkloadIdentityClient.class); when(identityClient.getAccessToken(Scope.VAULT)) .thenReturn( Uni.createFrom().item(expAccessToken), Uni.createFrom().item(accessToken)); + Instance identityClientInstance = mock(Instance.class); + when(identityClientInstance.get()) + .thenReturn(identityClient); + + Instance anyIdentityClient = mock(Instance.class); + when(anyIdentityClient.select(AzureWorkloadIdentityClient.class)) + .thenReturn(identityClientInstance); + /* * Test */ - AzureIdentityReactiveService identityService = spy(new AzureIdentityReactiveService(identityClient)); + AzureIdentityReactiveService identityService = spy(new AzureIdentityReactiveService( + Optional.empty(), + Optional.empty(), + Optional.of("https://login.microsoftonline.com/"), + Optional.of("da795842-fa15-4fd4-b556-f371ac9bafed"), + Optional.of("aeeb30a1-2d89-42bd-832c-69dc15a53d36"), + Optional.of("/var/run/secrets/azure/tokens/azure-identity-token"), + anyIdentityClient)); identityService.getAccessToken(Scope.VAULT) .subscribe() @@ -157,4 +205,109 @@ void given_expAccessTokenStored_when_getAccessTokenInvoked_then_getNewOneCacheAn verify(identityService, times(2)).getNewAccessTokenAndCacheIt(Scope.VAULT); } + + /** + * + */ + @Test + void given_systemManagedIdEnvironment_when_invokeGet_then_returnSuitableClient() { + AzureSystemManagedIdentityClient identityClient = mock(AzureSystemManagedIdentityClient.class); + + Instance identityClientInstance = mock(Instance.class); + when(identityClientInstance.get()) + .thenReturn(identityClient); + + Instance anyIdentityClient = mock(Instance.class); + when(anyIdentityClient.select(AzureSystemManagedIdentityClient.class)) + .thenReturn(identityClientInstance); + + AzureIdentityReactiveService service = new AzureIdentityReactiveService( + Optional.of("https://login.microsoftonline.com/"), + Optional.of("45ed57a0-ec26-41c9-8333-29daf37697d3"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + anyIdentityClient); + + assertTrue(service.getIdentityClient() instanceof AzureSystemManagedIdentityClient); + } + + /** + * + */ + @Test + void given_partialsystemManagedIdEnvironment_when_invokeGet_then_throwException() { + assertThrows( // NOSONAR + DeploymentException.class, + () -> new AzureIdentityReactiveService( + Optional.of("https://login.microsoftonline.com/"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + null)); + } + + /** + * + */ + @Test + void given_workloadIdEnvironment_when_invokeGet_then_returnSuitableClient() { + AzureWorkloadIdentityClient identityClient = mock(AzureWorkloadIdentityClient.class); + + Instance identityClientInstance = mock(Instance.class); + when(identityClientInstance.get()) + .thenReturn(identityClient); + + Instance anyIdentityClient = mock(Instance.class); + when(anyIdentityClient.select(AzureWorkloadIdentityClient.class)) + .thenReturn(identityClientInstance); + + AzureIdentityReactiveService service = new AzureIdentityReactiveService( + Optional.empty(), + Optional.empty(), + Optional.of("https://login.microsoftonline.com/"), + Optional.of("da795842-fa15-4fd4-b556-f371ac9bafed"), + Optional.of("aeeb30a1-2d89-42bd-832c-69dc15a53d36"), + Optional.of("/var/run/secrets/azure/tokens/azure-identity-token"), + anyIdentityClient); + + assertTrue(service.getIdentityClient() instanceof AzureWorkloadIdentityClient); + } + + /** + * + */ + @Test + void given_partialWorkloadIdEnvironment_when_invokeGet_then_throwException() { + assertThrows( // NOSONAR + DeploymentException.class, + () -> new AzureIdentityReactiveService( + Optional.empty(), + Optional.empty(), + Optional.of("https://login.microsoftonline.com/"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + null)); + } + + /** + * + */ + @Test + void given_noIdentityEnvironment_when_invokeGet_then_throwException() { + assertThrows( // NOSONAR + DeploymentException.class, + () -> new AzureIdentityReactiveService( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + null)); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index b027298..04300e7 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -8,4 +8,8 @@ azure-key-vault-keys.get-keys.maxresults=25 quarkus.rest-client.azure-storage-blob.url=https://dummy azure-storage-blob.api-version=dummy -AZURE_FEDERATED_TOKEN_FILE=src/test/resources/azure-identity-token \ No newline at end of file +AZURE_AUTHORITY_HOST=https://login.microsoftonline.com/ +AZURE_TENANT_ID=da795842-fa15-4fd4-b556-f371ac9bafed +AZURE_FEDERATED_TOKEN_FILE=src/test/resources/azure-identity-token + +IDENTITY_ENDPOINT=https://login.microsoftonline.com/ \ No newline at end of file