Skip to content
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

feat: add Issuer Service skeleton #524

Merged
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
27 changes: 26 additions & 1 deletion .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Run Javadoc
run: ./gradlew javadoc

Verify-Launcher:
Verify-Identityhub-Launcher:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -63,6 +63,31 @@ jobs:
container-name: identity-hub
timeout: 60

Verify-Issuer-Service-Launcher:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: eclipse-edc/.github/.github/actions/setup-build@main

- name: 'Build launcher'
run: ./gradlew :launcher:issuer-service:shadowJar

- name: 'Build Docker image'
run: docker build -t issuer-service ./launcher/issuer-service

- name: 'Start Identity Hub'
run: |
docker run -d --rm --name issuer-service \
-e "EDC_STS_ACCOUNT_API_URL=https://sts.com" \
-e "EDC_STS_ACCOUNTS_API_AUTH_HEADER_VALUE=auth-header" \
issuer-service:latest

- name: 'Wait for Issuer-Service to be healthy'
uses: raschmitt/wait-for-healthy-container@v1
with:
container-name: issuer-service
timeout: 60

Test:
permissions:
checks: write
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package org.eclipse.edc.identityhub.participantcontext;

import org.eclipse.edc.identityhub.spi.did.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.keypair.KeyPairService;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.StsAccountProvisioner;
import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextObservable;
Expand Down Expand Up @@ -43,8 +42,6 @@ public class ParticipantContextExtension implements ServiceExtension {
@Inject
private TransactionContext transactionContext;
@Inject
private KeyPairService keyPairService;
@Inject
private Clock clock;
@Inject
private EventRouter eventRouter;
Expand Down
40 changes: 40 additions & 0 deletions dist/bom/issuerservice-base-bom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2025 Cofinity-X
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Cofinity-X - initial API and implementation
*
*/

plugins {
`java-library`
}

dependencies {
runtimeOnly(project(":core:identity-hub-did"))
runtimeOnly(project(":core:identity-hub-core"))
runtimeOnly(project(":core:identity-hub-participants"))
runtimeOnly(project(":core:identity-hub-keypairs"))
runtimeOnly(project(":extensions:did:local-did-publisher"))
// API modules
runtimeOnly(project(":extensions:protocols:dcp:issuer-api"))

runtimeOnly(project(":extensions:sts:sts-account-provisioner"))
runtimeOnly(libs.edc.identity.did.core)
runtimeOnly(libs.edc.core.token)
runtimeOnly(libs.edc.api.version)
runtimeOnly(libs.edc.transaction.local) // needed by the PresentationCreatorRegistry

runtimeOnly(libs.edc.identity.did.web)
runtimeOnly(libs.bundles.connector)
}

edcBuild {

}
27 changes: 27 additions & 0 deletions dist/bom/issuerservice-bom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Cofinity-X
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Cofinity-X - initial API and implementation
*
*/


plugins {
`java-library`
}

dependencies {
runtimeOnly(project(":dist:bom:issuerservice-base-bom"))
runtimeOnly(project(":extensions:sts:sts-account-service-remote"))
}

edcBuild {

}
39 changes: 39 additions & 0 deletions dist/bom/issuerservice-feature-sql-bom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Cofinity-X
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Cofinity-X - initial API and implementation
*
*/


plugins {
`java-library`
}

dependencies {
// sql modules
api(project(":extensions:store:sql:identity-hub-credentials-store-sql"))
api(project(":extensions:store:sql:identity-hub-did-store-sql"))
api(project(":extensions:store:sql:identity-hub-keypair-store-sql"))
api(project(":extensions:store:sql:identity-hub-participantcontext-store-sql"))

api(libs.edc.sql.core)
api(libs.edc.transaction.local)
api(libs.edc.sql.pool)
api(libs.edc.sql.bootstrapper)
api(libs.edc.sql.jtivdalidation)

// third-party deps
api(libs.postgres)
}

edcBuild {

}
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,28 @@ class IdentityHubWithSts extends SmokeTest {
":dist:bom:identityhub-with-sts-bom"
));
}

@Nested
@EndToEndTest
class IssuerService extends SmokeTest {
@RegisterExtension
protected RuntimeExtension runtime =
new RuntimePerMethodExtension(new EmbeddedRuntime("issuer-service-bom",
new HashMap<>() {
{
put("web.http.port", DEFAULT_PORT);
put("web.http.path", DEFAULT_PATH);
put("web.http.version.port", valueOf(getFreePort()));
put("web.http.version.path", "/api/version");
put("web.http.did.port", valueOf(getFreePort()));
put("web.http.did.path", "/api/did");
put("web.http.issuer-api.port", valueOf(getFreePort()));
put("web.http.issuer-api.path", "/api/issuer");
put("edc.sts.account.api.url", "https://sts.com/accounts");
put("edc.sts.accounts.api.auth.header.value", "password");
}
},
":dist:bom:issuerservice-bom"
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
"version": "1.0.0-alpha",
"urlPath": "/v1alpha",
"lastUpdated": "2025-01-16T12:00:00Z",
"lastUpdated": "2025-01-17T12:00:00Z",
"maturity": null
}
]
45 changes: 45 additions & 0 deletions extensions/protocols/dcp/issuer-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2025 Cofinity-X
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Cofinity-X - initial API and implementation
*
*/


plugins {
`java-library`
`maven-publish`
id("io.swagger.core.v3.swagger-gradle-plugin")
}

dependencies {
api(project(":spi:identity-hub-spi"))
api(project(":spi:verifiable-credential-spi"))
api(libs.edc.spi.jsonld)
api(libs.edc.spi.jwt)
api(libs.edc.spi.core)
implementation(libs.edc.spi.web)
implementation(libs.edc.spi.dcp)
implementation(libs.edc.lib.jerseyproviders)
implementation(libs.edc.lib.transform)
implementation(libs.edc.dcp.transform)
implementation(libs.jakarta.rsApi)
testImplementation(libs.edc.junit)
testImplementation(libs.edc.jsonld)
testImplementation(testFixtures(libs.edc.core.jersey))
testImplementation(testFixtures(project(":spi:verifiable-credential-spi")))
testImplementation(libs.nimbus.jwt)
}

edcBuild {
swagger {
apiGroup.set("issuer-api")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2025 Cofinity-X
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Cofinity-X - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.protocols.dcp.issuer;

import com.fasterxml.jackson.databind.DeserializationFeature;
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.credentialrequest.CredentialRequestApiController;
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.credentialrequeststatus.CredentialRequestStatusApiController;
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.issuermetadata.IssuerMetadataApiController;
import org.eclipse.edc.runtime.metamodel.annotation.Configuration;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Setting;
import org.eclipse.edc.runtime.metamodel.annotation.Settings;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.system.apiversion.ApiVersionService;
import org.eclipse.edc.spi.system.apiversion.VersionRecord;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.web.spi.WebService;
import org.eclipse.edc.web.spi.configuration.PortMapping;
import org.eclipse.edc.web.spi.configuration.PortMappingRegistry;

import java.io.IOException;
import java.util.stream.Stream;

import static org.eclipse.edc.identityhub.protocols.dcp.issuer.IssuerApiExtension.NAME;
import static org.eclipse.edc.identityhub.spi.webcontext.IdentityHubApiContext.ISSUER_API;

@Extension(value = NAME)
public class IssuerApiExtension implements ServiceExtension {
public static final String NAME = "Issuer API extension";

private static final String API_VERSION_JSON_FILE = "issuer-api-version.json";

@Inject
private TypeManager typeManager;
@Inject
private ApiVersionService apiVersionService;
@Inject
private WebService webService;
@Inject
private PortMappingRegistry portMappingRegistry;

@Configuration
private CredentialRequestApiConfiguration apiConfiguration;

@Override
public void initialize(ServiceExtensionContext context) {

portMappingRegistry.register(new PortMapping(ISSUER_API, apiConfiguration.port(), apiConfiguration.path()));

webService.registerResource(ISSUER_API, new CredentialRequestApiController());
webService.registerResource(ISSUER_API, new CredentialRequestStatusApiController());
webService.registerResource(ISSUER_API, new IssuerMetadataApiController());

registerVersionInfo(getClass().getClassLoader());
}

private void registerVersionInfo(ClassLoader resourceClassLoader) {
try (var versionContent = resourceClassLoader.getResourceAsStream(API_VERSION_JSON_FILE)) {
if (versionContent == null) {
throw new EdcException("Version file '%s' not found or not readable.".formatted(API_VERSION_JSON_FILE));
}
Stream.of(typeManager.getMapper()
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.readValue(versionContent, VersionRecord[].class))
.forEach(vr -> apiVersionService.addRecord("issuer-api", vr));
} catch (IOException e) {
throw new EdcException(e);
}
}

@Settings
record CredentialRequestApiConfiguration(
@Setting(key = "web.http." + ISSUER_API + ".port", description = "Port for " + ISSUER_API + " api context", defaultValue = 13132 + "")
int port,
@Setting(key = "web.http." + ISSUER_API + ".path", description = "Path for " + ISSUER_API + " api context", defaultValue = "/api/issuer")
String path
) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2025 Cofinity-X
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Cofinity-X - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha;

import io.swagger.v3.oas.annotations.media.Schema;

public interface ApiSchema {
@Schema(name = "ApiErrorDetail", example = ApiErrorDetailSchema.API_ERROR_EXAMPLE)
record ApiErrorDetailSchema(
String message,
String type,
String path,
String invalidValue
) {
public static final String API_ERROR_EXAMPLE = """
{
"message": "error message",
"type": "ErrorType",
"path": "object.error.path",
"invalidValue": "this value is not valid"
}
""";
}
}
Loading
Loading