Skip to content

Commit

Permalink
feat: add Issuer Service skeleton (eclipse-edc#524)
Browse files Browse the repository at this point in the history
* feat: add Credential Request API + issuer service skeleton

* added issuer-service launcher

* added issuer APIs (skeletons)

* removed yaml folder

* license headers

* update api version files

* fix API version file
  • Loading branch information
paullatzelsperger authored Jan 17, 2025
1 parent 09b6026 commit 05e2398
Show file tree
Hide file tree
Showing 30 changed files with 939 additions and 10 deletions.
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

0 comments on commit 05e2398

Please sign in to comment.