From c756a4ae94442370de21a78ccd342f9959b03acf Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Fri, 1 Dec 2023 15:45:57 +0100 Subject: [PATCH] feat: adds verification context (#3677) * feat: add verification context * Update spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/VerificationContext.java Co-authored-by: Jim Marino * Update spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/VerificationContext.java Co-authored-by: Jim Marino * pr remarks --------- Co-authored-by: Jim Marino --- .../ContractNegotiationIntegrationTest.java | 3 +- .../service/protocol/BaseProtocolService.java | 10 +- .../CatalogProtocolServiceImplTest.java | 12 ++- .../ContractNegotiationEventDispatchTest.java | 4 +- ...actNegotiationProtocolServiceImplTest.java | 30 +++--- .../TransferProcessEventDispatchTest.java | 6 +- ...ransferProcessProtocolServiceImplTest.java | 40 ++++---- .../service/DecentralizedIdentityService.java | 5 +- .../BaseDecentralizedIdentityServiceTest.java | 17 +++- .../edc/iam/mock/MockIdentityService.java | 3 +- .../IdentityAndTrustService.java | 3 +- .../service/IdentityAndTrustServiceTest.java | 31 +++--- .../oauth2/identity/Oauth2ServiceImpl.java | 3 +- .../identity/Oauth2ServiceImplTest.java | 17 +++- .../iam/oauth2/daps/DapsIntegrationTest.java | 9 +- .../HttpProvisionerExtensionEndToEndTest.java | 4 +- .../eclipse/edc/spi/iam/IdentityService.java | 23 ++++- .../edc/spi/iam/VerificationContext.java | 96 +++++++++++++++++++ .../edc/spi/iam/VerificationContextTest.java | 55 +++++++++++ 19 files changed, 299 insertions(+), 72 deletions(-) create mode 100644 spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/VerificationContext.java create mode 100644 spi/common/core-spi/src/test/java/org/eclipse/edc/spi/iam/VerificationContextTest.java diff --git a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ContractNegotiationIntegrationTest.java b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ContractNegotiationIntegrationTest.java index b0bb0e12df4..aa19176e78d 100644 --- a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ContractNegotiationIntegrationTest.java +++ b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ContractNegotiationIntegrationTest.java @@ -39,6 +39,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; import org.eclipse.edc.spi.monitor.ConsoleMonitor; import org.eclipse.edc.spi.protocol.ProtocolWebhook; @@ -123,7 +124,7 @@ void init() { .protocolWebhook(protocolWebhook) .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(token)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(token)); consumerService = new ContractNegotiationProtocolServiceImpl(consumerStore, new NoopTransactionContext(), validationService, identityService, new ContractNegotiationObservableImpl(), monitor, mock(Telemetry.class)); providerService = new ContractNegotiationProtocolServiceImpl(providerStore, new NoopTransactionContext(), validationService, identityService, new ContractNegotiationObservableImpl(), monitor, mock(Telemetry.class)); } diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/protocol/BaseProtocolService.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/protocol/BaseProtocolService.java index 63ee9989749..0161e07a77d 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/protocol/BaseProtocolService.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/protocol/BaseProtocolService.java @@ -14,9 +14,11 @@ package org.eclipse.edc.connector.service.protocol; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.ServiceResult; @@ -45,7 +47,13 @@ public ServiceResult verifyToken(TokenRepresentation tokenRepresenta // TODO: since we are pushing here the invocation of the IdentityService we don't know the audience here // The audience removal will be tackle next. IdentityService that relies on this parameter would not work // for the time being. - var result = identityService.verifyJwtToken(tokenRepresentation, null); + + // TODO: policy extractors will be handled next + var verificationContext = VerificationContext.Builder.newInstance() + .policy(Policy.Builder.newInstance().build()) + .build(); + + var result = identityService.verifyJwtToken(tokenRepresentation, verificationContext); if (result.failed()) { monitor.debug(() -> "Unauthorized: %s".formatted(result.getFailureDetail())); diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/catalog/CatalogProtocolServiceImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/catalog/CatalogProtocolServiceImplTest.java index 22257b46426..89ac0abb105 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/catalog/CatalogProtocolServiceImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/catalog/CatalogProtocolServiceImplTest.java @@ -26,6 +26,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceFailure; @@ -44,6 +45,7 @@ import static org.eclipse.edc.spi.result.ServiceFailure.Reason.UNAUTHORIZED; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -70,7 +72,7 @@ void getCatalog_shouldReturnCatalogWithConnectorDataServiceAndItsDataset() { var participantAgent = createParticipantAgent(); var dataService = DataService.Builder.newInstance().build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(token)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(token)); when(dataServiceRegistry.getDataServices()).thenReturn(List.of(dataService)); when(datasetResolver.query(any(), any())).thenReturn(Stream.of(createDataset())); when(participantAgentService.createFor(any())).thenReturn(participantAgent); @@ -92,7 +94,7 @@ void getCatalog_shouldFail_whenTokenValidationFails() { var message = CatalogRequestMessage.Builder.newInstance().protocol("protocol").querySpec(querySpec).build(); var tokenRepresentation = createTokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.failure("unauthorized")); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.failure("unauthorized")); var result = service.getCatalog(message, tokenRepresentation); @@ -107,7 +109,7 @@ void getDataset_shouldReturnDataset() { var participantAgent = createParticipantAgent(); var dataset = createDataset(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(participantAgentService.createFor(any())).thenReturn(participantAgent); when(datasetResolver.getById(any(), any())).thenReturn(dataset); @@ -125,7 +127,7 @@ void getDataset_shouldFail_whenDatasetIsNull() { var tokenRepresentation = createTokenRepresentation(); var participantAgent = createParticipantAgent(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(participantAgentService.createFor(any())).thenReturn(participantAgent); when(datasetResolver.getById(any(), any())).thenReturn(null); @@ -139,7 +141,7 @@ void getDataset_shouldFail_whenTokenValidationFails() { var querySpec = QuerySpec.none(); var tokenRepresentation = createTokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.failure("unauthorized")); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.failure("unauthorized")); var result = service.getDataset("datasetId", tokenRepresentation); diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationEventDispatchTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationEventDispatchTest.java index 41fd671e05c..29a7494b304 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationEventDispatchTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationEventDispatchTest.java @@ -35,6 +35,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.message.RemoteMessageDispatcher; import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; import org.eclipse.edc.spi.protocol.ProtocolWebhook; @@ -59,6 +60,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -97,7 +99,7 @@ void shouldDispatchEventsOnProviderContractNegotiationStateChanges(EventRouter e AssetIndex assetIndex) { dispatcherRegistry.register(succeedingDispatcher()); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(token)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(token)); eventRouter.register(ContractNegotiationEvent.class, eventSubscriber); var policy = Policy.Builder.newInstance().build(); var contractDefinition = ContractDefinition.Builder.newInstance() diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java index b86a6481e77..8f620f1788f 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java @@ -32,6 +32,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceFailure; import org.eclipse.edc.spi.result.ServiceResult; @@ -71,6 +72,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -111,7 +113,7 @@ void notifyRequested_shouldInitiateNegotiation_whenNegotiationDoesNotExist() { .processId("processId") .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); when(validationService.validateInitialOffer(claimToken, contractOffer)).thenReturn(Result.success(validatedOffer)); @@ -147,7 +149,7 @@ void notifyRequested_shouldTransitionToRequested_whenNegotiationFound() { .processId("processId") .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); when(validationService.validateInitialOffer(claimToken, contractOffer)).thenReturn(Result.success(validatedOffer)); @@ -185,7 +187,7 @@ void notifyOffered_shouldTransitionToOffered_whenNegotiationFound() { .build(); var negotiation = createContractNegotiationRequested(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease(processId)).thenReturn(StoreResult.success(negotiation)); when(validationService.validateRequest(claimToken, negotiation)).thenReturn(Result.success()); @@ -213,7 +215,7 @@ void notifyAccepted_shouldTransitionToAccepted() { .policy(Policy.Builder.newInstance().build()) .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("processId")).thenReturn(StoreResult.success(contractNegotiation)); when(validationService.validateRequest(eq(claimToken), any(ContractNegotiation.class))).thenReturn(Result.success()); @@ -241,7 +243,7 @@ void notifyAgreed_shouldTransitionToAgreed() { .contractAgreement(contractAgreement) .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("processId")).thenReturn(StoreResult.success(negotiationConsumerRequested)); when(validationService.validateConfirmed(eq(claimToken), eq(contractAgreement), any(ContractOffer.class))).thenReturn(Result.success()); @@ -268,7 +270,7 @@ void notifyVerified_shouldTransitionToVerified() { .processId("processId") .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("processId")).thenReturn(StoreResult.success(negotiation)); when(validationService.validateRequest(any(), any(ContractNegotiation.class))).thenReturn(Result.success()); @@ -293,7 +295,7 @@ void notifyFinalized_shouldTransitionToFinalized() { var claimToken = ClaimToken.Builder.newInstance().build(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("processId")).thenReturn(StoreResult.success(negotiation)); when(validationService.validateRequest(any(), any(ContractNegotiation.class))).thenReturn(Result.success()); @@ -318,7 +320,7 @@ void notifyTerminated_shouldTransitionToTerminated() { var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("processId")).thenReturn(StoreResult.success(negotiation)); when(validationService.validateRequest(any(), any(ContractNegotiation.class))).thenReturn(Result.success()); @@ -338,7 +340,7 @@ void findById_shouldReturnNegotiation_whenValidCounterParty() { var tokenRepresentation = tokenRepresentation(); var negotiation = contractNegotiationBuilder().id(id).type(PROVIDER).state(VERIFIED.code()).build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findById(id)).thenReturn(negotiation); when(validationService.validateRequest(claimToken, negotiation)).thenReturn(Result.success()); @@ -354,7 +356,7 @@ void findById_shouldReturnNotFound_whenNegotiationNotFound() { var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findById(any())).thenReturn(null); var result = service.findById("invalidId", tokenRepresentation); @@ -370,7 +372,7 @@ void findById_shouldReturnBadRequest_whenCounterPartyUnauthorized() { var id = "negotiationId"; var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); var negotiation = contractNegotiationBuilder().id(id).type(PROVIDER).state(VERIFIED.code()).build(); @@ -390,7 +392,7 @@ void findById_shouldReturnBadRequest_whenCounterPartyUnauthorized() { void notify_shouldReturnNotFound_whenNotFound(MethodCall methodCall, M message) { var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); // currently ContractRequestMessage cannot happen on an already existing negotiation @@ -409,7 +411,7 @@ void notify_shouldReturnBadRequest_whenValidationFails var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease(any())).thenReturn(StoreResult.success(createContractNegotiationOffered())); when(validationService.validateRequest(any(), any(ContractNegotiation.class))).thenReturn(Result.failure("validation error")); when(validationService.validateInitialOffer(any(), any(ContractOffer.class))).thenReturn(Result.failure("error")); @@ -427,7 +429,7 @@ void notify_shouldReturnBadRequest_whenValidationFails void notify_shouldReturnBadRequest_whenTokenValidationFails(MethodCall methodCall, M message) { var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.failure("unauthorized")); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.failure("unauthorized")); var result = methodCall.call(service, message, tokenRepresentation); diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessEventDispatchTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessEventDispatchTest.java index 82fab9f4614..0a2e4a16660 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessEventDispatchTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessEventDispatchTest.java @@ -45,6 +45,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.message.RemoteMessageDispatcher; import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; import org.eclipse.edc.spi.protocol.ProtocolWebhook; @@ -73,6 +74,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -131,8 +133,8 @@ void shouldDispatchEventsOnTransferProcessStateChanges(TransferProcessService se var token = ClaimToken.Builder.newInstance().build(); var tokenRepresentation = TokenRepresentation.Builder.newInstance().token(UUID.randomUUID().toString()).build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(token)); - + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(token)); + var agent = mock(ParticipantAgent.class); var agreement = mock(ContractAgreement.class); var providerId = "ProviderId"; diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessProtocolServiceImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessProtocolServiceImplTest.java index 4b874440449..99a2baef6f9 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessProtocolServiceImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/transferprocess/TransferProcessProtocolServiceImplTest.java @@ -33,6 +33,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceFailure; import org.eclipse.edc.spi.result.ServiceResult; @@ -72,6 +73,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -113,7 +115,7 @@ void notifyRequested_validAgreement_shouldInitiateTransfer() { .dataDestination(DataAddress.Builder.newInstance().type("any").build()) .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(), any())).thenReturn(Result.success(null)); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.success()); @@ -143,7 +145,7 @@ void notifyRequested_doNothingIfProcessAlreadyExist() { var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(), any())).thenReturn(Result.success(null)); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.success()); @@ -167,7 +169,7 @@ void notifyRequested_invalidAgreement_shouldNotInitiateTransfer() { var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(), any())).thenReturn(Result.failure("error")); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.success()); @@ -190,7 +192,7 @@ void notifyRequested_invalidDestination_shouldNotInitiateTransfer() { .dataDestination(DataAddress.Builder.newInstance().type("any").build()) .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.failure(violation("invalid data address", "path"))); @@ -212,7 +214,7 @@ void notifyRequested_missingDestination_shouldInitiateTransfer() { .callbackAddress("http://any") .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(), any())).thenReturn(Result.success(null)); @@ -242,7 +244,7 @@ void notifyStarted_shouldTransitionToStarted() { .build(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess(REQUESTED, "transferProcessId"))); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -272,7 +274,7 @@ void notifyStarted_shouldReturnConflict_whenStatusIsNotValid() { .build(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -297,7 +299,7 @@ void notifyStarted_shouldReturnNotFound_whenCounterPartyUnauthorized() { .build(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess(REQUESTED, "transferProcessId"))); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.failure("error")); @@ -324,7 +326,7 @@ void notifyCompleted_shouldTransitionToCompleted() { .build(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess(STARTED, "transferProcessId"))); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -350,7 +352,7 @@ void notifyCompleted_shouldReturnConflict_whenStatusIsNotValid() { .build(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -375,7 +377,7 @@ void notifyCompleted_shouldReturnNotFound_whenCounterPartyUnauthorized() { var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess(STARTED, "transferProcessId"))); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.failure("error")); @@ -404,7 +406,7 @@ void notifyTerminated_shouldTransitionToTerminated() { .build(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess(STARTED, "transferProcessId"))); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -431,7 +433,7 @@ void notifyTerminated_shouldReturnConflict_whenStatusIsNotValid() { .reason("TestReason") .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -458,7 +460,7 @@ void notifyTerminated_shouldReturnNotFound_whenCounterPartyUnauthorized() { .reason("TestReason") .build(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.failure("error")); @@ -482,7 +484,7 @@ void findById_shouldReturnTransferProcess_whenValidCounterParty() { var transferProcess = transferProcess(INITIAL, processId); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findById(processId)).thenReturn(transferProcess); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.success()); @@ -499,7 +501,7 @@ void findById_shouldReturnNotFound_whenNegotiationNotFound() { var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findById(any())).thenReturn(null); var result = service.findById("invalidId", tokenRepresentation); @@ -518,7 +520,7 @@ void findById_shouldReturnNotFound_whenCounterPartyUnauthorized() { var tokenRepresentation = tokenRepresentation(); var agreement = contractAgreement(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findById(processId)).thenReturn(transferProcess); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(claimToken, agreement)).thenReturn(Result.failure("error")); @@ -537,7 +539,7 @@ void notify_shouldFail_whenTransferProcessNotFound(Met var claimToken = claimToken(); var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.success(claimToken)); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.success(claimToken)); when(store.findByCorrelationIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); var result = methodCall.call(service, message, tokenRepresentation); @@ -552,7 +554,7 @@ void notify_shouldFail_whenTransferProcessNotFound(Met void notify_shouldFail_whenTokenValidationFails(MethodCall methodCall, M message) { var tokenRepresentation = tokenRepresentation(); - when(identityService.verifyJwtToken(eq(tokenRepresentation), any())).thenReturn(Result.failure("unauthorized")); + when(identityService.verifyJwtToken(eq(tokenRepresentation), isA(VerificationContext.class))).thenReturn(Result.failure("unauthorized")); var result = methodCall.call(service, message, tokenRepresentation); diff --git a/extensions/common/iam/decentralized-identity/identity-did-service/src/main/java/org/eclipse/edc/iam/did/service/DecentralizedIdentityService.java b/extensions/common/iam/decentralized-identity/identity-did-service/src/main/java/org/eclipse/edc/iam/did/service/DecentralizedIdentityService.java index 02b354e3608..c5b4644a96c 100644 --- a/extensions/common/iam/decentralized-identity/identity-did-service/src/main/java/org/eclipse/edc/iam/did/service/DecentralizedIdentityService.java +++ b/extensions/common/iam/decentralized-identity/identity-did-service/src/main/java/org/eclipse/edc/iam/did/service/DecentralizedIdentityService.java @@ -29,6 +29,7 @@ import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import org.jetbrains.annotations.NotNull; @@ -65,7 +66,7 @@ public Result obtainClientCredentials(TokenParameters param } @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, String audience) { + public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext context) { try { var jwt = SignedJWT.parse(tokenRepresentation.getToken()); monitor.debug("Starting verification..."); @@ -92,7 +93,7 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation } monitor.debug("Verifying JWT with public key..."); - var verified = JwtUtils.verify(jwt, publicKeyWrapperResult.getContent(), audience); + var verified = JwtUtils.verify(jwt, publicKeyWrapperResult.getContent(), context.getAudience()); if (verified.failed()) { monitor.debug(() -> "Failure in token verification: " + verified.getFailureDetail()); return Result.failure("Token could not be verified!"); diff --git a/extensions/common/iam/decentralized-identity/identity-did-service/src/test/java/org/eclipse/edc/iam/did/service/BaseDecentralizedIdentityServiceTest.java b/extensions/common/iam/decentralized-identity/identity-did-service/src/test/java/org/eclipse/edc/iam/did/service/BaseDecentralizedIdentityServiceTest.java index 2abd7a3b541..411d7860f21 100644 --- a/extensions/common/iam/decentralized-identity/identity-did-service/src/test/java/org/eclipse/edc/iam/did/service/BaseDecentralizedIdentityServiceTest.java +++ b/extensions/common/iam/decentralized-identity/identity-did-service/src/test/java/org/eclipse/edc/iam/did/service/BaseDecentralizedIdentityServiceTest.java @@ -22,7 +22,9 @@ import org.eclipse.edc.iam.did.spi.document.VerificationMethod; import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.iam.TokenParameters; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.monitor.ConsoleMonitor; import org.eclipse.edc.spi.result.Result; import org.jetbrains.annotations.NotNull; @@ -79,7 +81,7 @@ void generateAndVerifyJwtToken_valid() { var result = identityService.obtainClientCredentials(defaultTokenParameters()); assertThat(result.succeeded()).isTrue(); - var verificationResult = identityService.verifyJwtToken(result.getContent(), "Bar"); + var verificationResult = identityService.verifyJwtToken(result.getContent(), verificationContext("Bar")); assertThat(verificationResult.succeeded()).isTrue(); var claimToken = verificationResult.getContent(); @@ -96,7 +98,7 @@ void generateAndVerifyJwtToken_wrongPublicKey() { var result = identityService.obtainClientCredentials(defaultTokenParameters()); assertThat(result.succeeded()).isTrue(); - var verificationResult = identityService.verifyJwtToken(result.getContent(), "Bar"); + var verificationResult = identityService.verifyJwtToken(result.getContent(), verificationContext("Bar")); assertThat(verificationResult.failed()).isTrue(); assertThat(verificationResult.getFailureMessages()).contains("Token could not be verified!"); } @@ -107,7 +109,7 @@ void generateAndVerifyJwtToken_wrongAudience() { var result = identityService.obtainClientCredentials(defaultTokenParameters()); - var verificationResult = identityService.verifyJwtToken(result.getContent(), "Bar2"); + var verificationResult = identityService.verifyJwtToken(result.getContent(), verificationContext("Bar2")); assertThat(verificationResult.failed()).isTrue(); } @@ -120,7 +122,7 @@ void generateAndVerifyJwtToken_getVerifiedCredentialsFailed() { var result = identityService.obtainClientCredentials(defaultTokenParameters()); assertThat(result.succeeded()).isTrue(); - var verificationResult = identityService.verifyJwtToken(result.getContent(), "Bar"); + var verificationResult = identityService.verifyJwtToken(result.getContent(), verificationContext("Bar")); assertThat(verificationResult.failed()).isTrue(); assertThat(verificationResult.getFailureDetail()).contains(errorMsg); } @@ -145,4 +147,11 @@ private DidDocument createDidDocument(JWK keyPair) { throw new AssertionError(e); } } + + private VerificationContext verificationContext(String audience) { + return VerificationContext.Builder.newInstance() + .audience(audience) + .policy(Policy.Builder.newInstance().build()) + .build(); + } } diff --git a/extensions/common/iam/iam-mock/src/main/java/org/eclipse/edc/iam/mock/MockIdentityService.java b/extensions/common/iam/iam-mock/src/main/java/org/eclipse/edc/iam/mock/MockIdentityService.java index f0f0c793eca..ce289e2a8d1 100644 --- a/extensions/common/iam/iam-mock/src/main/java/org/eclipse/edc/iam/mock/MockIdentityService.java +++ b/extensions/common/iam/iam-mock/src/main/java/org/eclipse/edc/iam/mock/MockIdentityService.java @@ -20,6 +20,7 @@ import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.types.TypeManager; @@ -47,7 +48,7 @@ public Result obtainClientCredentials(TokenParameters param } @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, String audience) { + public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext context) { var token = typeManager.readValue(tokenRepresentation.getToken(), MockToken.class); return Result.success(ClaimToken.Builder.newInstance() .claim("region", token.region) diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java index a556a500775..fc9af1103cd 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java @@ -35,6 +35,7 @@ import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.util.string.StringUtils; import org.jetbrains.annotations.NotNull; @@ -136,7 +137,7 @@ public Result obtainClientCredentials(TokenParameters param } @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, String audience) { + public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext context) { // verify and validate incoming SI Token var claimTokenResult = jwtVerifier.verify(tokenRepresentation.getToken(), participantId) diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java index 902f3c2e60d..2db0b879685 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java @@ -29,9 +29,11 @@ import org.eclipse.edc.identitytrust.validation.JwtValidator; import org.eclipse.edc.identitytrust.verification.JwtVerifier; import org.eclipse.edc.identitytrust.verification.PresentationVerifier; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -94,6 +96,13 @@ void setup() { when(mockedSts.createToken(any(), any())).thenReturn(success(TokenRepresentation.Builder.newInstance().build())); } + private VerificationContext verificationContext() { + return VerificationContext.Builder.newInstance() + .audience("test-audience") + .policy(Policy.Builder.newInstance().build()) + .build(); + } + @Nested class ObtainClientCredentials { @ParameterizedTest(name = "{0}") @@ -151,7 +160,7 @@ class VerifyJwtToken { void presentationRequestFails() { when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(failure("test-failure")); var token = createJwt(); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isFailed().detail().isEqualTo("test-failure"); verifyNoInteractions(mockedVerifier); verify(mockedClient).requestPresentation(any(), any(), any()); @@ -163,7 +172,7 @@ void cryptographicError() { when(mockedVerifier.verifyPresentation(any())).thenReturn(Result.failure("Cryptographic error")); when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(createPresentationContainer()))); var token = createJwt(); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isFailed().detail().isEqualTo("Cryptographic error"); } @@ -179,7 +188,7 @@ void notYetValid() { when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isFailed().messages() .hasSizeGreaterThanOrEqualTo(1) .contains("Credential is not yet valid."); @@ -200,7 +209,7 @@ void oneInvalidSubjectId() { when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isFailed().messages() .hasSizeGreaterThanOrEqualTo(1) .contains("Not all subject IDs match the expected subject ID %s".formatted(CONSUMER_DID)); @@ -225,7 +234,7 @@ void credentialHasInvalidIssuer_issuerIsUrl() { when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); var token = createJwt(consumerDid, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isFailed().messages() .hasSizeGreaterThanOrEqualTo(1) .contains("Issuer 'invalid-issuer' is not in the list of trusted issuers"); @@ -235,7 +244,7 @@ void credentialHasInvalidIssuer_issuerIsUrl() { void jwtTokenNotValid() { when(jwtValidatorMock.validateToken(any(), any())).thenReturn(failure("test failure")); var token = createJwt(); - assertThat(service.verifyJwtToken(token, "test-audience")) + assertThat(service.verifyJwtToken(token, verificationContext())) .isFailed() .messages().hasSize(1) .containsExactly("test failure"); @@ -245,7 +254,7 @@ void jwtTokenNotValid() { void jwtTokenNotVerified() { when(jwtVerfierMock.verify(any(), any())).thenReturn(failure("test-failure")); var token = createJwt(); - assertThat(service.verifyJwtToken(token, "test-audience")) + assertThat(service.verifyJwtToken(token, verificationContext())) .isFailed() .messages().hasSize(1) .containsExactly("test-failure"); @@ -254,7 +263,7 @@ void jwtTokenNotVerified() { @Test void cannotResolveCredentialServiceUrl() { when(credentialServiceUrlResolverMock.resolve(any())).thenReturn(Result.failure("test-failure")); - assertThat(service.verifyJwtToken(createJwt(), "test-audience")) + assertThat(service.verifyJwtToken(createJwt(), verificationContext())) .isFailed() .detail() .isEqualTo("test-failure"); @@ -278,7 +287,7 @@ void verify_singlePresentation_singleCredential() { when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isSucceeded() .satisfies(ct -> Assertions.assertThat(ct.getClaims()).containsEntry("some-claim", "some-val")); } @@ -305,7 +314,7 @@ void verify_singlePresentation_multipleCredentials() { when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); when(trustedIssuerRegistryMock.getTrustedIssuers()).thenReturn(Set.of(TRUSTED_ISSUER)); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isSucceeded() .satisfies(ct -> Assertions.assertThat(ct.getClaims()) .containsEntry("some-claim", "some-val") @@ -354,7 +363,7 @@ void verify_multiplePresentations_multipleCredentialsEach() { var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); - var result = service.verifyJwtToken(token, "test-audience"); + var result = service.verifyJwtToken(token, verificationContext()); assertThat(result).isSucceeded() .satisfies(ct -> Assertions.assertThat(ct.getClaims()) .containsEntry("some-claim", "some-val") diff --git a/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImpl.java b/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImpl.java index 93eb9bdb961..1ab5e194a98 100644 --- a/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImpl.java +++ b/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImpl.java @@ -30,6 +30,7 @@ import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.jetbrains.annotations.NotNull; @@ -76,7 +77,7 @@ public Result obtainClientCredentials(TokenParameters param } @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, String audience) { + public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext context) { return tokenValidationService.validate(tokenRepresentation); } diff --git a/extensions/common/iam/oauth2/oauth2-core/src/test/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImplTest.java b/extensions/common/iam/oauth2/oauth2-core/src/test/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImplTest.java index 1c3a157eb4b..89473287579 100644 --- a/extensions/common/iam/oauth2/oauth2-core/src/test/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImplTest.java +++ b/extensions/common/iam/oauth2/oauth2-core/src/test/java/org/eclipse/edc/iam/oauth2/identity/Oauth2ServiceImplTest.java @@ -37,9 +37,11 @@ import org.eclipse.edc.jwt.TokenValidationServiceImpl; import org.eclipse.edc.jwt.spi.JwtDecorator; import org.eclipse.edc.jwt.spi.TokenGenerationService; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.iam.PublicKeyResolver; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.security.CertificateResolver; import org.eclipse.edc.spi.security.PrivateKeyResolver; @@ -72,6 +74,11 @@ class Oauth2ServiceImplTest { private static final String PUBLIC_CERTIFICATE_ALIAS = "cert-test"; private static final String PROVIDER_AUDIENCE = "audience-test"; private static final String ENDPOINT_AUDIENCE = "endpoint-audience-test"; + + private static final VerificationContext VERIFICATION_CONTEXT = VerificationContext.Builder.newInstance() + .audience(ENDPOINT_AUDIENCE) + .policy(Policy.Builder.newInstance().build()) + .build(); private static final String OAUTH2_SERVER_URL = "http://oauth2-server.com"; private final Instant now = Instant.now(); @@ -191,7 +198,7 @@ void obtainClientCredentials_verifyReturnsFailureIfOauth2ClientFails() { void verifyNoAudienceToken() { var jwt = createJwt(null, Date.from(now.minusSeconds(1000)), Date.from(now.plusSeconds(1000))); - var result = authService.verifyJwtToken(jwt, ENDPOINT_AUDIENCE); + var result = authService.verifyJwtToken(jwt, VERIFICATION_CONTEXT); assertThat(result.succeeded()).isFalse(); assertThat(result.getFailureMessages()).isNotEmpty(); @@ -201,7 +208,7 @@ void verifyNoAudienceToken() { void verifyInvalidAudienceToken() { var jwt = createJwt("different.audience", Date.from(now.minusSeconds(1000)), Date.from(now.plusSeconds(1000))); - var result = authService.verifyJwtToken(jwt, ENDPOINT_AUDIENCE); + var result = authService.verifyJwtToken(jwt, VERIFICATION_CONTEXT); assertThat(result.succeeded()).isFalse(); assertThat(result.getFailureMessages()).isNotEmpty(); @@ -211,7 +218,7 @@ void verifyInvalidAudienceToken() { void verifyInvalidAttemptUseNotBeforeToken() { var jwt = createJwt(PROVIDER_AUDIENCE, Date.from(now.plusSeconds(1000)), Date.from(now.plusSeconds(1000))); - var result = authService.verifyJwtToken(jwt, ENDPOINT_AUDIENCE); + var result = authService.verifyJwtToken(jwt, VERIFICATION_CONTEXT); assertThat(result.succeeded()).isFalse(); assertThat(result.getFailureMessages()).isNotEmpty(); @@ -221,7 +228,7 @@ void verifyInvalidAttemptUseNotBeforeToken() { void verifyExpiredToken() { var jwt = createJwt(PROVIDER_AUDIENCE, Date.from(now.minusSeconds(1000)), Date.from(now.minusSeconds(1000))); - var result = authService.verifyJwtToken(jwt, ENDPOINT_AUDIENCE); + var result = authService.verifyJwtToken(jwt, VERIFICATION_CONTEXT); assertThat(result.succeeded()).isFalse(); assertThat(result.getFailureMessages()).isNotEmpty(); @@ -231,7 +238,7 @@ void verifyExpiredToken() { void verifyValidJwt() { var jwt = createJwt(ENDPOINT_AUDIENCE, Date.from(now.minusSeconds(1000)), new Date(System.currentTimeMillis() + 1000000)); - var result = authService.verifyJwtToken(jwt, ENDPOINT_AUDIENCE); + var result = authService.verifyJwtToken(jwt, VERIFICATION_CONTEXT); assertThat(result.succeeded()).isTrue(); assertThat(result.getContent().getClaims()).hasSize(3).containsKeys(AUDIENCE, NOT_BEFORE, EXPIRATION_TIME); diff --git a/extensions/common/iam/oauth2/oauth2-daps/src/test/java/org/eclipse/edc/iam/oauth2/daps/DapsIntegrationTest.java b/extensions/common/iam/oauth2/oauth2-daps/src/test/java/org/eclipse/edc/iam/oauth2/daps/DapsIntegrationTest.java index 245df021ca5..6577039cde4 100644 --- a/extensions/common/iam/oauth2/oauth2-daps/src/test/java/org/eclipse/edc/iam/oauth2/daps/DapsIntegrationTest.java +++ b/extensions/common/iam/oauth2/oauth2-daps/src/test/java/org/eclipse/edc/iam/oauth2/daps/DapsIntegrationTest.java @@ -17,8 +17,10 @@ import org.eclipse.edc.junit.annotations.ComponentTest; import org.eclipse.edc.junit.extensions.EdcExtension; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenParameters; +import org.eclipse.edc.spi.iam.VerificationContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -82,7 +84,12 @@ void retrieveTokenAndValidate(IdentityService identityService) { assertThat(tokenResult.succeeded()).withFailMessage(tokenResult::getFailureDetail).isTrue(); - var verificationResult = identityService.verifyJwtToken(tokenResult.getContent(), "audience"); + var verificationContext = VerificationContext.Builder.newInstance() + .audience("audience") + .policy(Policy.Builder.newInstance().build()) + .build(); + + var verificationResult = identityService.verifyJwtToken(tokenResult.getContent(), verificationContext); assertThat(verificationResult.succeeded()).withFailMessage(verificationResult::getFailureDetail).isTrue(); } diff --git a/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/provision/http/impl/HttpProvisionerExtensionEndToEndTest.java b/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/provision/http/impl/HttpProvisionerExtensionEndToEndTest.java index af3f32ba4d7..7af3635a7cb 100644 --- a/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/provision/http/impl/HttpProvisionerExtensionEndToEndTest.java +++ b/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/provision/http/impl/HttpProvisionerExtensionEndToEndTest.java @@ -36,6 +36,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; import org.eclipse.edc.spi.protocol.ProtocolWebhook; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.system.ServiceExtension; @@ -68,6 +69,7 @@ import static org.eclipse.edc.junit.testfixtures.TestUtils.testHttpClient; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -125,7 +127,7 @@ void processProviderRequestRetry(TransferProcessProtocolService protocolService, .thenAnswer(invocation -> createResponse(503, invocation)) .thenAnswer(invocation -> createResponse(200, invocation)); - when(identityService.verifyJwtToken(any(), any())).thenReturn(Result.success(ClaimToken.Builder.newInstance().build())); + when(identityService.verifyJwtToken(any(), isA(VerificationContext.class))).thenReturn(Result.success(ClaimToken.Builder.newInstance().build())); var result = protocolService.notifyRequested(createTransferRequestMessage(), TokenRepresentation.Builder.newInstance().build()); diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/IdentityService.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/IdentityService.java index 678f18c4b2a..6398f4097af 100644 --- a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/IdentityService.java +++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/IdentityService.java @@ -16,6 +16,7 @@ package org.eclipse.edc.spi.iam; +import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.result.Result; @@ -38,8 +39,26 @@ public interface IdentityService { * Verifies a JWT bearer token. * * @param tokenRepresentation A token representation including the token to verify. - * @param audience Expected audience. + * @param context The {@link VerificationContext}. * @return Result of the validation. */ - Result verifyJwtToken(TokenRepresentation tokenRepresentation, String audience); + Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext context); + + /** + * Verifies a JWT bearer token. + * + * @param tokenRepresentation A token representation including the token to verify. + * @param audience The audience. + * @return Result of the validation. + * @deprecated please use {@link #verifyJwtToken(TokenRepresentation, VerificationContext)} + */ + + @Deprecated(since = "0.4.2", forRemoval = true) + default Result verifyJwtToken(TokenRepresentation tokenRepresentation, String audience) { + var context = VerificationContext.Builder.newInstance() + .audience(audience) + .policy(Policy.Builder.newInstance().build()) + .build(); + return verifyJwtToken(tokenRepresentation, context); + } } diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/VerificationContext.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/VerificationContext.java new file mode 100644 index 00000000000..9f451f7d2b8 --- /dev/null +++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/VerificationContext.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.spi.iam; + +import org.eclipse.edc.policy.model.Policy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Provides additional context for {@link IdentityService} verifiers. + */ +public class VerificationContext { + + // TODO it will be removed with the TokenGenerator/Verification refactor + @Deprecated + private String audience; + private Policy policy; + + private final Map, Object> additional; + + private VerificationContext() { + additional = new HashMap<>(); + } + + /** + * Returns the audience or null if not available. + */ + public String getAudience() { + return audience; + } + + /** + * Returns the {@link Policy} associated with the verification context + */ + public Policy getPolicy() { + return policy; + } + + /** + * Gets additional data from the context by type. + * + * @param type the type class. + * @param the type of data. + * @return the object associated with the type, or null. + */ + @SuppressWarnings("unchecked") + public T getContextData(Class type) { + return (T) additional.get(type); + } + + public static class Builder { + private final VerificationContext context; + + private Builder() { + context = new VerificationContext(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder audience(String audience) { + context.audience = audience; + return this; + } + + public Builder data(Class clazz, Object object) { + context.additional.put(clazz, object); + return this; + } + + public Builder policy(Policy policy) { + context.policy = policy; + return this; + } + + public VerificationContext build() { + Objects.requireNonNull(this.context.policy, "Policy cannot be null"); + return context; + } + } +} diff --git a/spi/common/core-spi/src/test/java/org/eclipse/edc/spi/iam/VerificationContextTest.java b/spi/common/core-spi/src/test/java/org/eclipse/edc/spi/iam/VerificationContextTest.java new file mode 100644 index 00000000000..ec9bbe2b3ca --- /dev/null +++ b/spi/common/core-spi/src/test/java/org/eclipse/edc/spi/iam/VerificationContextTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.spi.iam; + +import org.eclipse.edc.policy.model.Policy; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; + +public class VerificationContextTest { + + @Test + void assertMandatoryPolicy() { + assertThatNullPointerException().isThrownBy(() -> VerificationContext.Builder.newInstance() + .build()) + .withMessageContaining("Policy"); + } + + @Test + void buildVerificationContext() { + assertThatNoException().isThrownBy(() -> VerificationContext.Builder.newInstance() + .policy(Policy.Builder.newInstance().build()) + .build()); + } + + @Test + void buildVerificationContext_withAdditionalData() { + + var data = new TestData(); + + var context = VerificationContext.Builder.newInstance() + .policy(Policy.Builder.newInstance().build()) + .data(TestData.class, data) + .build(); + + assertThat(context.getContextData(TestData.class)).isEqualTo(data); + } + + private static class TestData { + } +}