Skip to content

Commit

Permalink
refactor: introduce record to represent create participant response
Browse files Browse the repository at this point in the history
  • Loading branch information
ndr-brt committed Dec 5, 2024
1 parent 1cd398c commit e11289d
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 104 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 @@ -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 Down Expand Up @@ -64,7 +65,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 e11289d

Please sign in to comment.