Skip to content

Commit

Permalink
refactor: introduce record to represent create participant response (e…
Browse files Browse the repository at this point in the history
  • Loading branch information
ndr-brt authored Dec 5, 2024
1 parent 76b8a79 commit a66ddd2
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
import org.eclipse.edc.identityhub.spi.participantcontext.model.CreateParticipantContextResponse;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContextState;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest;
Expand All @@ -28,9 +29,8 @@
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

import static org.eclipse.edc.spi.result.ServiceResult.conflict;
import static org.eclipse.edc.spi.result.ServiceResult.fromFailure;
Expand Down Expand Up @@ -70,24 +70,24 @@ public ParticipantContextServiceImpl(ParticipantContextStore participantContextS
}

@Override
public ServiceResult<Map<String, Object>> createParticipantContext(ParticipantManifest manifest) {
public ServiceResult<CreateParticipantContextResponse> createParticipantContext(ParticipantManifest manifest) {
return transactionContext.execute(() -> {
if (didResourceStore.findById(manifest.getDid()) != null) {
return ServiceResult.conflict("Another participant with the same DID '%s' already exists.".formatted(manifest.getDid()));
}
var response = new HashMap<String, Object>();
var context = convert(manifest);
var res = createParticipantContext(context)
.compose(u -> createTokenAndStoreInVault(context)).onSuccess(k -> response.put("apiKey", k))
.compose(apiKey -> stsAccountProvisioner.create(manifest))
.onSuccess(accountInfo -> {
if (accountInfo != null) {
response.put("clientId", accountInfo.clientId());
response.put("clientSecret", accountInfo.clientSecret());
}
})

return createParticipantContext(context)
.compose(this::createTokenAndStoreInVault)
.compose((Function<String, ServiceResult<CreateParticipantContextResponse>>) apiKey -> stsAccountProvisioner.create(manifest)
.map(accountInfo -> {
if (accountInfo == null) {
return new CreateParticipantContextResponse(apiKey, null, null);
} else {
return new CreateParticipantContextResponse(apiKey, accountInfo.clientId(), accountInfo.clientSecret());
}
}))
.onSuccess(apiToken -> observable.invokeForEach(l -> l.created(context, manifest)));
return res.map(u -> response);
});
}

Expand Down Expand Up @@ -160,11 +160,9 @@ private ServiceResult<String> createTokenAndStoreInVault(ParticipantContext part
}


private ServiceResult<Void> createParticipantContext(ParticipantContext context) {
var storeRes = participantContextStore.create(context);
return storeRes.succeeded() ?
success() :
fromFailure(storeRes);
private ServiceResult<ParticipantContext> createParticipantContext(ParticipantContext context) {
var result = participantContextStore.create(context);
return ServiceResult.from(result).map(it -> context);
}

private ParticipantContext findByIdInternal(String participantId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;
import org.assertj.core.api.Assertions;
import org.eclipse.edc.identithub.spi.did.model.DidResource;
import org.eclipse.edc.identithub.spi.did.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.participantcontext.AccountInfo;
import org.eclipse.edc.identityhub.spi.participantcontext.StsAccountProvisioner;
import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextObservable;
import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor;
Expand All @@ -45,6 +45,7 @@
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
Expand All @@ -62,15 +63,15 @@ class ParticipantContextServiceImplTest {
private final ParticipantContextStore participantContextStore = mock();
private final ParticipantContextObservable observableMock = mock();
private final DidResourceStore didResourceStore = mock();
private final StsAccountProvisioner provisionerMock = mock();
private final StsAccountProvisioner stsAccountProvisioner = mock();
private ParticipantContextServiceImpl participantContextService;

@BeforeEach
void setUp() {
var keyParserRegistry = new KeyParserRegistryImpl();
keyParserRegistry.register(new PemParser(mock()));
participantContextService = new ParticipantContextServiceImpl(participantContextStore, didResourceStore, vault, new NoopTransactionContext(), observableMock, provisionerMock);
when(provisionerMock.create(any())).thenReturn(ServiceResult.success());
participantContextService = new ParticipantContextServiceImpl(participantContextStore, didResourceStore, vault, new NoopTransactionContext(), observableMock, stsAccountProvisioner);
when(stsAccountProvisioner.create(any())).thenReturn(ServiceResult.success());
}

@ParameterizedTest(name = "isActive: {0}")
Expand All @@ -92,15 +93,48 @@ void createParticipantContext_withPublicKeyPem(boolean isActive) {
.publicKeyJwk(null)
.publicKeyPem(pem)
.build()).build();
assertThat(participantContextService.createParticipantContext(ctx))
.isSucceeded();

assertThat(participantContextService.createParticipantContext(ctx)).isSucceeded().satisfies(response -> {
assertThat(response.apiKey()).isNotBlank();
assertThat(response.clientId()).isNull();
assertThat(response.clientSecret()).isNull();
});

verify(participantContextStore).create(any());
verify(vault).storeSecret(eq(ctx.getParticipantId() + "-apikey"), anyString());
verifyNoMoreInteractions(vault, participantContextStore);
verify(observableMock).invokeForEach(any());
}

@ParameterizedTest(name = "isActive: {0}")
@ValueSource(booleans = { true, false })
void shouldCreateParticipantContext_withAccountInfo(boolean isActive) {
when(participantContextStore.create(any())).thenReturn(StoreResult.success());
when(vault.storeSecret(anyString(), anyString())).thenReturn(Result.success());
when(stsAccountProvisioner.create(any())).thenReturn(ServiceResult.success(new AccountInfo("clientId", "clientSecret")));

var pem = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE25DvKuU5+gvMdKkyiDDIsx3tcuPX
jgVyAjs1JcfFtvi9I0FemuqymDTu3WWdYmdaJQMJJx3qwEJGTVTxcKGtEg==
-----END PUBLIC KEY-----
""";

var ctx = createManifest()
.active(isActive)
.key(createKey()
.publicKeyJwk(null)
.publicKeyPem(pem)
.build()).build();

var result = participantContextService.createParticipantContext(ctx);

assertThat(result).isSucceeded().satisfies(response -> {
assertThat(response.apiKey()).isNotBlank();
assertThat(response.clientId()).isEqualTo("clientId");
assertThat(response.clientSecret()).isEqualTo("clientSecret");
});
}

@ParameterizedTest(name = "isActive: {0}")
@ValueSource(booleans = { true, false })
Expand Down Expand Up @@ -161,7 +195,7 @@ void createParticipantContext_whenExists() {
var ctx = createManifest().build();
assertThat(participantContextService.createParticipantContext(ctx))
.isFailed()
.satisfies(f -> Assertions.assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.CONFLICT));
.satisfies(f -> assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.CONFLICT));
verify(participantContextStore).create(any());
verifyNoMoreInteractions(vault, participantContextStore, observableMock);

Expand Down Expand Up @@ -199,8 +233,8 @@ void getParticipantContext_whenNotExists() {
assertThat(participantContextService.getParticipantContext("test-id"))
.isFailed()
.satisfies(f -> {
Assertions.assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.NOT_FOUND);
Assertions.assertThat(f.getFailureDetail()).isEqualTo("foo");
assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.NOT_FOUND);
assertThat(f.getFailureDetail()).isEqualTo("foo");
});

verify(participantContextStore).findById(anyString());
Expand All @@ -213,8 +247,8 @@ void getParticipantContext_whenStorageFails() {
assertThat(participantContextService.getParticipantContext("test-id"))
.isFailed()
.satisfies(f -> {
Assertions.assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.NOT_FOUND);
Assertions.assertThat(f.getFailureDetail()).isEqualTo("foo bar");
assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.NOT_FOUND);
assertThat(f.getFailureDetail()).isEqualTo("foo bar");
});

verify(participantContextStore).findById(anyString());
Expand Down Expand Up @@ -244,8 +278,8 @@ void deleteParticipantContext_whenNotExists() {
assertThat(participantContextService.deleteParticipantContext("test-id"))
.isFailed()
.satisfies(f -> {
Assertions.assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.NOT_FOUND);
Assertions.assertThat(f.getFailureDetail()).isEqualTo("foo bar");
assertThat(f.getReason()).isEqualTo(ServiceFailure.Reason.NOT_FOUND);
assertThat(f.getFailureDetail()).isEqualTo("foo bar");
});

verify(observableMock, times(2)).invokeForEach(any()); //deleting
Expand Down Expand Up @@ -333,7 +367,7 @@ void query() {

assertThat(participantContextService.query(QuerySpec.max()))
.isSucceeded()
.satisfies(res -> Assertions.assertThat(res).hasSize(3));
.satisfies(res -> assertThat(res).hasSize(3));

verify(participantContextStore).query(any());
verifyNoMoreInteractions(vault);
Expand Down Expand Up @@ -372,4 +406,4 @@ private Map<String, Object> createJwk() {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ public String createParticipant(String participantId, List<String> roles, boolea
.build())
.build();
var srv = runtime.getService(ParticipantContextService.class);
return srv.createParticipantContext(manifest).orElseThrow(f -> new EdcException(f.getFailureDetail())).get("apiKey").toString();
return srv.createParticipantContext(manifest)
.orElseThrow(f -> new EdcException(f.getFailureDetail()))
.apiKey();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void stop() {
}

private void createDatabase() {
var postgres = new PostgresqlLocalInstance(PostgresqlEndToEndInstance.USER, PostgresqlEndToEndInstance.PASSWORD, baseJdbcUrl(hostPort), dbName);
postgres.createDatabase();
var postgres = new PostgresqlLocalInstance(PostgresqlEndToEndInstance.USER, PostgresqlEndToEndInstance.PASSWORD, baseJdbcUrl(hostPort));
postgres.createDatabase(dbName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
"version": "1.0.0-alpha",
"urlPath": "/v1alpha",
"lastUpdated": "2024-10-02T12:00:00Z",
"lastUpdated": "2024-12-05T14:13:00Z",
"maturity": null
}
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.core.SecurityContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.CreateParticipantContextResponse;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest;
import org.eclipse.edc.web.spi.ApiErrorDetail;

import java.util.Collection;
import java.util.List;
import java.util.Map;

@OpenAPIDefinition(info = @Info(description = "This is the Identity API for manipulating ParticipantContexts", title = "ParticipantContext Management API", version = "1"))
@Tag(name = "Participant Context")
Expand All @@ -42,7 +42,8 @@ public interface ParticipantContextApi {
operationId = "createParticipant",
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = ParticipantManifest.class), mediaType = "application/json")),
responses = {
@ApiResponse(responseCode = "200", description = "The ParticipantContext was created successfully, its API token is returned in the response body."),
@ApiResponse(responseCode = "200", description = "The ParticipantContext was created successfully, its API token is returned in the response body.",
content = @Content(schema = @Schema(implementation = CreateParticipantContextResponse.class))),
@ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")),
@ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.",
Expand All @@ -51,7 +52,7 @@ public interface ParticipantContextApi {
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json"))
}
)
Map<String, Object> createParticipant(ParticipantManifest manifest);
CreateParticipantContextResponse createParticipant(ParticipantManifest manifest);


@Operation(description = "Gets ParticipantContexts by ID.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.eclipse.edc.identityhub.spi.AuthorizationService;
import org.eclipse.edc.identityhub.spi.authentication.ServicePrincipal;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.model.CreateParticipantContextResponse;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest;
import org.eclipse.edc.spi.query.QuerySpec;
Expand All @@ -40,7 +41,6 @@

import java.util.Collection;
import java.util.List;
import java.util.Map;

import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.edc.identityhub.spi.AuthorizationResultHandler.exceptionMapper;
Expand All @@ -64,7 +64,7 @@ public ParticipantContextApiController(ParticipantManifestValidator participantM
@Override
@POST
@RolesAllowed(ServicePrincipal.ROLE_ADMIN)
public Map<String, Object> createParticipant(ParticipantManifest manifest) {
public CreateParticipantContextResponse createParticipant(ParticipantManifest manifest) {
participantManifestValidator.validate(manifest).orElseThrow(ValidationFailureException::new);
return participantContextService.createParticipantContext(manifest)
.orElseThrow(exceptionMapper(ParticipantManifest.class, manifest.getParticipantId()));
Expand Down
Loading

0 comments on commit a66ddd2

Please sign in to comment.