From 7423e94eea14207225c1fa15caa38afec4b1125c Mon Sep 17 00:00:00 2001 From: Sebastian Becker Date: Mon, 18 Nov 2024 15:49:26 +0100 Subject: [PATCH] feat(service)!: add authorization for using secrets Signed-off-by: Sebastian Becker --- .../amphora/service/opa/OpaService.java | 14 ++ .../persistence/metadata/StorageService.java | 20 +-- .../service/rest/IntraVcpController.java | 13 +- .../amphora/service/opa/OpaServiceTest.java | 28 +++- .../metadata/StorageServiceTest.java | 131 ++++++++++-------- .../service/rest/IntraVcpControllerTest.java | 26 ++-- 6 files changed, 145 insertions(+), 87 deletions(-) diff --git a/amphora-service/src/main/java/io/carbynestack/amphora/service/opa/OpaService.java b/amphora-service/src/main/java/io/carbynestack/amphora/service/opa/OpaService.java index 2ee6770..d8c5618 100644 --- a/amphora-service/src/main/java/io/carbynestack/amphora/service/opa/OpaService.java +++ b/amphora-service/src/main/java/io/carbynestack/amphora/service/opa/OpaService.java @@ -20,6 +20,7 @@ public class OpaService { public static final String OWNER_TAG_KEY = "owner"; static final String READ_SECRET_ACTION_NAME = "read"; + static final String USE_SECRET_ACTION_NAME = "use"; static final String DELETE_SECRET_ACTION_NAME = "delete"; static final String CREATE_TAG_ACTION_NAME = "tag/create"; static final String READ_TAG_ACTION_NAME = "tag/read"; @@ -46,6 +47,19 @@ public boolean canReadSecret(String subject, List tags) throws CsOpaExcepti return isAllowed(subject, READ_SECRET_ACTION_NAME, tags); } + /** + * Check if the subject can use the secret described by the given tags evaluating the OPA policy package. + * The policy package is extracted from the tags if present. If not present, the default policy package is used. + * + * @param subject The subject attempting to use the secret. + * @param tags The tags describing the referenced secret. + * @return True if the subject can use the secret, false otherwise. + * @throws CsOpaException If an error occurred while evaluating the policy. + */ + public boolean canUseSecret(String subject, List tags) throws CsOpaException { + return isAllowed(subject, USE_SECRET_ACTION_NAME, tags); + } + /** * Check if the subject can delete the secret described by the given tags evaluating the OPA policy package. * The policy package is extracted from the tags if present. If not present, the default policy package is used. diff --git a/amphora-service/src/main/java/io/carbynestack/amphora/service/persistence/metadata/StorageService.java b/amphora-service/src/main/java/io/carbynestack/amphora/service/persistence/metadata/StorageService.java index 98a5406..4997b2d 100644 --- a/amphora-service/src/main/java/io/carbynestack/amphora/service/persistence/metadata/StorageService.java +++ b/amphora-service/src/main/java/io/carbynestack/amphora/service/persistence/metadata/StorageService.java @@ -220,16 +220,20 @@ public Page getSecretList(List tagFilters, Sort sort) { * if no secret with the given id exists. * @throws AmphoraServiceException if an {@link SecretShare} exists but could not be retrieved. * @throws NotFoundException if no {@link SecretShare} with the given id exists + * @throws UnauthorizedException if the requesting program is not authorized to access the {@link SecretShare} */ @Transactional(readOnly = true) - public SecretShare getSecretShareAuthorized(UUID secretId) { - return secretEntityRepository - .findById(secretId.toString()) - .map(this::getSecretShareForEntity) - .orElseThrow( - () -> - new NotFoundException( - String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, secretId))); + public SecretShare useSecretShare(UUID secretId, String programId) throws UnauthorizedException, CsOpaException { + SecretEntity secretEntity = secretEntityRepository + .findById(secretId.toString()) + .orElseThrow( + () -> + new NotFoundException( + String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, secretId))); + if (!opaService.canUseSecret(programId, setToTagList(secretEntity.getTags()))) { + throw new UnauthorizedException("Requesting program is not authorized to access the secret"); + } + return getSecretShareForEntity(secretEntity); } /** diff --git a/amphora-service/src/main/java/io/carbynestack/amphora/service/rest/IntraVcpController.java b/amphora-service/src/main/java/io/carbynestack/amphora/service/rest/IntraVcpController.java index f8e6f8e..7120826 100644 --- a/amphora-service/src/main/java/io/carbynestack/amphora/service/rest/IntraVcpController.java +++ b/amphora-service/src/main/java/io/carbynestack/amphora/service/rest/IntraVcpController.java @@ -10,7 +10,9 @@ import io.carbynestack.amphora.common.Tag; import io.carbynestack.amphora.common.exceptions.AmphoraServiceException; import io.carbynestack.amphora.service.exceptions.AlreadyExistsException; +import io.carbynestack.amphora.service.exceptions.CsOpaException; import io.carbynestack.amphora.service.exceptions.NotFoundException; +import io.carbynestack.amphora.service.exceptions.UnauthorizedException; import io.carbynestack.amphora.service.persistence.metadata.StorageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,6 +28,7 @@ import java.util.UUID; import static io.carbynestack.amphora.common.rest.AmphoraRestApiEndpoints.*; +import static org.springframework.util.Assert.notNull; @Slf4j @RestController @@ -62,12 +65,18 @@ public ResponseEntity uploadSecretShare(@RequestBody SecretShare secretShar * Retrieves an {@link SecretShare} with a given id. * * @param secretId id of the {@link SecretShare} to retrieve + * @param programId id of the Program requesting the {@link SecretShare} * @return {@link HttpStatus#OK} with the {@link SecretShare} if successful. * @throws AmphoraServiceException if an {@link SecretShare} exists but could not be retrieved. * @throws NotFoundException if no {@link SecretShare} with the given id exists + * @throws UnauthorizedException if the requesting Program is not authorized to access the {@link SecretShare} + * @throws CsOpaException if an error occurred while evaluating the OPA policy */ @GetMapping(path = "/{" + SECRET_ID_PARAMETER + "}") - public ResponseEntity downloadSecretShare(@PathVariable UUID secretId) { - return new ResponseEntity<>(storageService.getSecretShareAuthorized(secretId), HttpStatus.OK); + public ResponseEntity downloadSecretShare(@PathVariable UUID secretId, + @RequestParam String programId) throws UnauthorizedException, CsOpaException { + + notNull(programId, "ProgramId must not be null"); + return new ResponseEntity<>(storageService.useSecretShare(secretId, programId), HttpStatus.OK); } } diff --git a/amphora-service/src/test/java/io/carbynestack/amphora/service/opa/OpaServiceTest.java b/amphora-service/src/test/java/io/carbynestack/amphora/service/opa/OpaServiceTest.java index 49f536b..6dad392 100644 --- a/amphora-service/src/test/java/io/carbynestack/amphora/service/opa/OpaServiceTest.java +++ b/amphora-service/src/test/java/io/carbynestack/amphora/service/opa/OpaServiceTest.java @@ -20,13 +20,14 @@ import java.util.List; import static io.carbynestack.amphora.service.opa.OpaService.READ_SECRET_ACTION_NAME; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static io.carbynestack.amphora.service.opa.OpaService.USE_SECRET_ACTION_NAME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -public class OpaServiceTest { +class OpaServiceTest { private static final String POLICY_PACKAGE = "play"; private static final String DEFAULT_POLICY_PACKAGE = "default"; @@ -54,7 +55,7 @@ public void setUp() { } @Test - public void givenPolicyDefinedInTag_whenIsAllowed_thenUsePolicyPackageProvided() throws CsOpaException { + void givenPolicyDefinedInTag_whenIsAllowed_thenUsePolicyPackageProvided() throws CsOpaException { ArgumentCaptor packageCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor subjectCaptor = ArgumentCaptor.forClass(String.class); @@ -68,7 +69,7 @@ public void givenPolicyDefinedInTag_whenIsAllowed_thenUsePolicyPackageProvided() testTags.add(POLICY_TAG); boolean result = service.isAllowed(SUBJECT, READ_SECRET_ACTION_NAME, testTags); - assertTrue("must be allowed", result); + assertTrue(result, "must be allowed"); String actualPackage = packageCaptor.getValue(); assertEquals(POLICY_TAG.getValue(), actualPackage); String actualAction = actionCaptor.getValue(); @@ -90,7 +91,7 @@ public void givenNoPolicyDefinedInTag_whenIsAllowed_thenUseDefaultPolicyPackage( OpaService service = new OpaService(opaClientMock); boolean result = service.isAllowed(READ_SECRET_ACTION_NAME, SUBJECT, TAGS); - assertTrue("must be allowed", result); + assertTrue(result, "must be allowed"); String actualPackage = packageCaptor.getValue(); assertEquals(DEFAULT_POLICY_PACKAGE, actualPackage); List actualTags = tagsCaptor.getValue(); @@ -112,6 +113,21 @@ public void whenCanReadSecret_thenUseProperAction() throws CsOpaException { } + @Test + public void whenCanUseSecret_thenUseProperAction() throws CsOpaException { + ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(String.class); + when(opaClientMock.isAllowed( + any(), actionCaptor.capture(), any(), any())) + .thenReturn(true); + + OpaService service = new OpaService(opaClientMock); + service.canUseSecret(SUBJECT, TAGS); + + String actualAction = actionCaptor.getValue(); + assertEquals(USE_SECRET_ACTION_NAME, actualAction); + + } + @Test public void whenCanDeleteSecret_thenUseProperAction() throws CsOpaException { ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(String.class); diff --git a/amphora-service/src/test/java/io/carbynestack/amphora/service/persistence/metadata/StorageServiceTest.java b/amphora-service/src/test/java/io/carbynestack/amphora/service/persistence/metadata/StorageServiceTest.java index 4eceeea..66b7d4f 100644 --- a/amphora-service/src/test/java/io/carbynestack/amphora/service/persistence/metadata/StorageServiceTest.java +++ b/amphora-service/src/test/java/io/carbynestack/amphora/service/persistence/metadata/StorageServiceTest.java @@ -16,7 +16,6 @@ import io.carbynestack.amphora.service.exceptions.CsOpaException; import io.carbynestack.amphora.service.exceptions.NotFoundException; import io.carbynestack.amphora.service.exceptions.UnauthorizedException; -import io.carbynestack.amphora.service.opa.OpaClient; import io.carbynestack.amphora.service.opa.OpaService; import io.carbynestack.amphora.service.persistence.cache.InputMaskCachingService; import io.carbynestack.amphora.service.persistence.datastore.SecretShareDataStore; @@ -25,7 +24,6 @@ import io.carbynestack.mpspdz.integration.MpSpdzIntegrationUtils; import org.apache.commons.lang3.RandomUtils; import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -37,7 +35,6 @@ import java.util.*; import java.util.stream.Collectors; -import static io.carbynestack.amphora.service.opa.OpaService.OWNER_TAG_KEY; import static io.carbynestack.amphora.service.persistence.metadata.StorageService.*; import static io.carbynestack.amphora.service.persistence.metadata.TagEntity.setToTagList; import static io.carbynestack.amphora.service.util.CreationDateTagMatchers.containsCreationDateTag; @@ -58,7 +55,7 @@ class StorageServiceTest { private final Tag testTag2 = Tag.builder().key("SUPER_KEY").value("MY#SUPER,VALUE").build(); private final Tag testTagReservedCreationDateKey = Tag.builder().key(StorageService.RESERVED_TAG_KEYS.get(0)).value("MY#SUPER,VALUE").build(); - private final String authorizedUserId ="afc0117f-c9cd-4d8c-acee-fa1433ca0fdd"; + private final String authorizedSubject ="afc0117f-c9cd-4d8c-acee-fa1433ca0fdd"; @Mock private SecretEntityRepository secretEntityRepository; @Mock private InputMaskCachingService inputMaskStore; @@ -86,7 +83,7 @@ void givenIdIsAlreadyInUse_whenCreateSecret_thenThrowAlreadyExistsException() { AlreadyExistsException aee = assertThrows( AlreadyExistsException.class, () -> - storageService.createSecret(testMaskedInput, authorizedUserId )); + storageService.createSecret(testMaskedInput, authorizedSubject)); assertEquals(SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, aee.getMessage()); } @@ -108,7 +105,7 @@ void givenMaskedInputHasTagsWithSameKey_whenCreateObject_thenThrowIllegalArgumen IllegalArgumentException iae = assertThrows( IllegalArgumentException.class, - () -> storageService.createSecret(maskedInputDuplicateTagKeys, authorizedUserId)); + () -> storageService.createSecret(maskedInputDuplicateTagKeys, authorizedSubject)); assertEquals(TAGS_WITH_THE_SAME_KEY_DEFINED_EXCEPTION_MSG, iae.getMessage()); } @@ -140,7 +137,7 @@ void givenMaskedInputWithReservedKey_whenCreateObject_thenReplaceReservedKey() { assertThrows( RuntimeException.class, - () -> storageService.createSecret(maskedInput, authorizedUserId), + () -> storageService.createSecret(maskedInput, authorizedSubject), expectedAbortTestException.getMessage()); SecretEntity capturedSecretEntity = secretEntityArgumentCaptor.getValue(); List actualTags = TagEntity.setToTagList(capturedSecretEntity.getTags()); @@ -183,7 +180,7 @@ void givenSuccessfulRequest_whenCreateObject_thenReturnSecretId() { when(secretEntityRepository.save(secretEntityArgumentCaptor.capture())) .thenReturn(persistedSecretEntity); - assertEquals(maskedInput.getSecretId().toString(), storageService.createSecret(maskedInput, authorizedUserId)); + assertEquals(maskedInput.getSecretId().toString(), storageService.createSecret(maskedInput, authorizedSubject)); verify(secretEntityRepository, times(1)).existsById(maskedInput.getSecretId().toString()); verify(inputMaskStore, times(1)).getCachedInputMasks(maskedInput.getSecretId()); verify(secretEntityRepository, times(1)).save(secretEntityArgumentCaptor.capture()); @@ -393,7 +390,7 @@ void givenNoSecretShareWithGivenIdInDatabase_whenGetSecretShare_thenThrowNotFoun when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.empty()); NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.getSecretShare(testSecretId, authorizedUserId)); + assertThrows(NotFoundException.class, () -> storageService.getSecretShare(testSecretId, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -403,10 +400,10 @@ void givenSubjectHasNoAccess_whenGetSecretShare_thenThrowUnauthorizedException() SecretEntity secretEntity = new SecretEntity(testSecretId.toString(), emptySet()); when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(secretEntity)); - when(opaService.canReadSecret(authorizedUserId, emptyList())).thenReturn(false); + when(opaService.canReadSecret(authorizedSubject, emptyList())).thenReturn(false); UnauthorizedException ue = - assertThrows(UnauthorizedException.class, () -> storageService.getSecretShare(testSecretId, authorizedUserId)); + assertThrows(UnauthorizedException.class, () -> storageService.getSecretShare(testSecretId, authorizedSubject)); assertEquals("User is not authorized to read this secret", ue.getMessage()); } @@ -415,14 +412,14 @@ void givenDataCannotBeRetrieved_whenGetSecretShare_thenThrowAmphoraServiceExcept AmphoraServiceException expectedAse = new AmphoraServiceException("Expected this one"); TagEntity tagEntity = TagEntity.fromTag(testTag); SecretEntity secretEntity = new SecretEntity(testSecretId.toString(), singleton(tagEntity)); - when(opaService.canReadSecret(authorizedUserId, setToTagList(secretEntity.getTags()))).thenReturn(true); + when(opaService.canReadSecret(authorizedSubject, setToTagList(secretEntity.getTags()))).thenReturn(true); when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(secretEntity)); when(secretShareDataStore.getSecretShareData(testSecretId)).thenThrow(expectedAse); assertThrows( AmphoraServiceException.class, - () -> storageService.getSecretShare(testSecretId, authorizedUserId), + () -> storageService.getSecretShare(testSecretId, authorizedSubject), expectedAse.getMessage()); } @@ -438,11 +435,11 @@ void givenSuccessfulRequest_whenGetSecretShare_thenReturnContent() throws CsOpaE when(secretShareDataStore.getSecretShareData( UUID.fromString(existingSecretEntity.getSecretId()))) .thenReturn(expectedData); - when(opaService.canReadSecret(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canReadSecret(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); SecretShare actualSecretShare = - storageService.getSecretShare(UUID.fromString(existingSecretEntity.getSecretId()), authorizedUserId); + storageService.getSecretShare(UUID.fromString(existingSecretEntity.getSecretId()), authorizedSubject); assertEquals( UUID.fromString(existingSecretEntity.getSecretId()), actualSecretShare.getSecretId()); assertEquals(expectedTags, actualSecretShare.getTags()); @@ -450,32 +447,44 @@ void givenSuccessfulRequest_whenGetSecretShare_thenReturnContent() throws CsOpaE } @Test - void givenNoSecretShareWithGivenIdInDatabase_whenGetSecretShareAuthorized_thenThrowNotFoundException() { + void givenNoSecretShareWithGivenIdInDatabase_whenUseSecretShare_thenThrowNotFoundException() { when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.empty()); NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.getSecretShareAuthorized(testSecretId)); + assertThrows(NotFoundException.class, () -> storageService.useSecretShare(testSecretId, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @Test - void givenDataCannotBeRetrieved_whenGetSecretShareAuthorized_thenThrowAmphoraServiceException() { + void givenSubjectIsNotAuthorized_whenUseSecretShare_thenThrowUnauthorizedException() { + TagEntity tagEntity = TagEntity.fromTag(testTag); + SecretEntity secretEntity = new SecretEntity(testSecretId.toString(), singleton(tagEntity)); + when(secretEntityRepository.findById(testSecretId.toString())) + .thenReturn(Optional.of(secretEntity)); + + assertThrows(UnauthorizedException.class, () -> storageService.useSecretShare(testSecretId, authorizedSubject)); + } + + @Test + void givenDataCannotBeRetrieved_whenUseSecretShare_thenThrowAmphoraServiceException() throws CsOpaException { AmphoraServiceException expectedAse = new AmphoraServiceException("Expected this one"); TagEntity tagEntity = TagEntity.fromTag(testTag); SecretEntity secretEntity = new SecretEntity(testSecretId.toString(), singleton(tagEntity)); when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(secretEntity)); + when(opaService.canUseSecret(authorizedSubject, setToTagList(secretEntity.getTags()))) + .thenReturn(true); when(secretShareDataStore.getSecretShareData(testSecretId)).thenThrow(expectedAse); assertThrows( AmphoraServiceException.class, - () -> storageService.getSecretShareAuthorized(testSecretId), + () -> storageService.useSecretShare(testSecretId, authorizedSubject), expectedAse.getMessage()); } @Test - void givenSuccessfulRequest_whenGetSecretShareAuthorized_thenReturnContent() { + void givenSuccessfulRequest_whenUseSecretShare_thenReturnContent() throws CsOpaException, UnauthorizedException { List expectedTags = singletonList(testTag); byte[] expectedData = RandomUtils.nextBytes(MpSpdzIntegrationUtils.SHARE_WIDTH); SecretEntity existingSecretEntity = @@ -483,12 +492,14 @@ void givenSuccessfulRequest_whenGetSecretShareAuthorized_thenReturnContent() { when(secretEntityRepository.findById(existingSecretEntity.getSecretId())) .thenReturn(Optional.of(existingSecretEntity)); + when(opaService.canUseSecret(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) + .thenReturn(true); when(secretShareDataStore.getSecretShareData( UUID.fromString(existingSecretEntity.getSecretId()))) .thenReturn(expectedData); SecretShare actualSecretShare = - storageService.getSecretShareAuthorized(UUID.fromString(existingSecretEntity.getSecretId())); + storageService.useSecretShare(UUID.fromString(existingSecretEntity.getSecretId()), authorizedSubject); assertEquals( UUID.fromString(existingSecretEntity.getSecretId()), actualSecretShare.getSecretId()); assertEquals(expectedTags, actualSecretShare.getTags()); @@ -497,7 +508,7 @@ void givenSuccessfulRequest_whenGetSecretShareAuthorized_thenReturnContent() { @Test void givenNoSecretShareWithGivenIdInDatabase_whenDeleteObject_thenThrowNotFoundException() { NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.deleteSecret(testSecretId, authorizedUserId)); + assertThrows(NotFoundException.class, () -> storageService.deleteSecret(testSecretId, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -509,7 +520,7 @@ void givenSubjectHasNoAccess_whenDeleteObject_thenThrowUnauthorizedException() t .thenReturn(Optional.of(secretEntity)); UnauthorizedException ue = - assertThrows(UnauthorizedException.class, () -> storageService.deleteSecret(testSecretId, authorizedUserId)); + assertThrows(UnauthorizedException.class, () -> storageService.deleteSecret(testSecretId, authorizedSubject)); assertEquals("User is not authorized to delete this secret", ue.getMessage()); } @@ -518,7 +529,7 @@ void givenDeleteObjectDataFails_whenDeleteObject_thenThrowGivenException() throw AmphoraServiceException expectedAse = new AmphoraServiceException("Expected this one"); TagEntity tagEntity = TagEntity.fromTag(testTag); SecretEntity secretEntity = new SecretEntity(testSecretId.toString(), singleton(tagEntity)); - when(opaService.canDeleteSecret(authorizedUserId, setToTagList(secretEntity.getTags()))).thenReturn(true); + when(opaService.canDeleteSecret(authorizedSubject, setToTagList(secretEntity.getTags()))).thenReturn(true); when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(secretEntity)); when(secretEntityRepository.deleteBySecretId(testSecretId.toString())).thenReturn(1L); @@ -526,7 +537,7 @@ void givenDeleteObjectDataFails_whenDeleteObject_thenThrowGivenException() throw assertThrows( AmphoraServiceException.class, - () -> storageService.deleteSecret(testSecretId, authorizedUserId), + () -> storageService.deleteSecret(testSecretId, authorizedSubject), expectedAse.getMessage()); } @@ -539,10 +550,10 @@ void givenSuccessfulRequest_whenDeleteObject_thenDeleteObjectAndData() throws Cs when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(existingSecretEntity)); when(secretEntityRepository.deleteBySecretId(testSecretId.toString())).thenReturn(1L); - when(opaService.canDeleteSecret(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canDeleteSecret(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); - storageService.deleteSecret(testSecretId, authorizedUserId); + storageService.deleteSecret(testSecretId, authorizedSubject); verify(secretShareDataStore, times(1)).deleteSecretShareData(testSecretId); } @@ -551,7 +562,7 @@ void givenTagHasReservedKey_whenStoreTag_thenThrowIllegalArgumentException() { IllegalArgumentException iae = assertThrows( IllegalArgumentException.class, - () -> storageService.storeTag(testSecretId, testTagReservedCreationDateKey, authorizedUserId)); + () -> storageService.storeTag(testSecretId, testTagReservedCreationDateKey, authorizedSubject)); assertEquals( String.format(IS_RESERVED_KEY_EXCEPTION_MSG, testTagReservedCreationDateKey.getKey()), iae.getMessage()); @@ -560,7 +571,7 @@ void givenTagHasReservedKey_whenStoreTag_thenThrowIllegalArgumentException() { @Test void givenNoSecretShareWithGivenIdInDatabase_whenStoreTag_thenThrowNotFoundException() { NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.storeTag(testSecretId, testTag, authorizedUserId)); + assertThrows(NotFoundException.class, () -> storageService.storeTag(testSecretId, testTag, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -572,11 +583,11 @@ void givenObjectAlreadyHasTagWithGivenKey_whenStoreTag_thenThrowAlreadyExistsExc when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.of(existingSecretEntity)); when(tagRepository.findBySecretAndKey(existingSecretEntity, testTag.getKey())) .thenReturn(Optional.of(existingTagEntity)); - when(opaService.canCreateTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canCreateTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); AlreadyExistsException aee = assertThrows( - AlreadyExistsException.class, () -> storageService.storeTag(testSecretId, testTag, authorizedUserId)); + AlreadyExistsException.class, () -> storageService.storeTag(testSecretId, testTag, authorizedSubject)); assertEquals( String.format( TAG_WITH_KEY_EXISTS_FOR_SECRET_EXCEPTION_MSG, @@ -593,7 +604,7 @@ void givenSubjectHasNoAccess_whenStoreTag_thenThrowUnauthorizedException() throw UnauthorizedException ue = assertThrows(UnauthorizedException.class, () -> - storageService.storeTag(UUID.fromString(secretEntity.getSecretId()), testTag, authorizedUserId)); + storageService.storeTag(UUID.fromString(secretEntity.getSecretId()), testTag, authorizedSubject)); assertEquals("User is not authorized to create tags for this secret", ue.getMessage()); } @@ -607,12 +618,12 @@ void givenSuccessfulRequest_whenStoreTag_thenPersistTag() throws CsOpaException, when(tagRepository.findBySecretAndKey(existingSecretEntity, testTag.getKey())) .thenReturn(Optional.empty()); when(tagRepository.save(any())).thenReturn(expectedTagEntity); - when(opaService.canCreateTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canCreateTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); assertEquals( expectedTagEntity.getKey(), - storageService.storeTag(UUID.fromString(existingSecretEntity.getSecretId()), testTag, authorizedUserId)); + storageService.storeTag(UUID.fromString(existingSecretEntity.getSecretId()), testTag, authorizedSubject)); verify(tagRepository, times(1)).save(tagEntityArgumentCaptor.capture()); TagEntity actualTagEntity = tagEntityArgumentCaptor.getValue(); @@ -633,7 +644,7 @@ void givenListHasTagsWithSameKey_whenReplaceTags_thenThrowIllegalArgumentExcepti IllegalArgumentException iae = assertThrows( IllegalArgumentException.class, - () -> storageService.replaceTags(testSecretId, invalidTagList, authorizedUserId)); + () -> storageService.replaceTags(testSecretId, invalidTagList, authorizedSubject)); assertEquals(TAGS_WITH_THE_SAME_KEY_DEFINED_EXCEPTION_MSG, iae.getMessage()); } @@ -642,7 +653,7 @@ void givenNoSecretShareWithGivenIdInDatabase_whenReplaceTags_thenThrowNotFoundEx List emptyTags = emptyList(); NotFoundException nfe = assertThrows( - NotFoundException.class, () -> storageService.replaceTags(testSecretId, emptyTags, authorizedUserId)); + NotFoundException.class, () -> storageService.replaceTags(testSecretId, emptyTags, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -657,7 +668,7 @@ void givenSubjectHasNoAccess_whenReplaceTags_thenThrowUnauthorizedException() th UnauthorizedException ue = assertThrows(UnauthorizedException.class, () -> storageService.replaceTags( - UUID.fromString(secretEntity.getSecretId()), tagListWithReservedKey, authorizedUserId)); + UUID.fromString(secretEntity.getSecretId()), tagListWithReservedKey, authorizedSubject)); assertEquals("User is not authorized to update tags for this secret", ue.getMessage()); } @@ -677,11 +688,11 @@ void givenListHasTagWithReservedKey_whenReplaceTags_thenReplaceByExistingTagAndP when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.of(existingSecretEntity)); when(tagRepository.findBySecretAndKey(existingSecretEntity, existingCreationTagEntity.getKey())) .thenReturn(Optional.of(existingCreationTagEntity)); - when(opaService.canUpdateTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canUpdateTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); storageService.replaceTags( - UUID.fromString(existingSecretEntity.getSecretId()), tagListWithReservedKey, authorizedUserId); + UUID.fromString(existingSecretEntity.getSecretId()), tagListWithReservedKey, authorizedSubject); verify(tagRepository, times(1)).deleteBySecret(existingSecretEntity); verify(tagRepository, times(1)).saveAll(tagEntitySetArgumentCaptor.capture()); @@ -699,7 +710,7 @@ void givenNoSecretShareWithGivenIdInDatabase_whenRetrieveTags_thenThrowNotFoundE when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.empty()); NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.retrieveTags(testSecretId, authorizedUserId)); + assertThrows(NotFoundException.class, () -> storageService.retrieveTags(testSecretId, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -712,7 +723,7 @@ void givenSubjectHasNoAccess_whenRetrieveTags_thenThrowUnauthorizedException() t UnauthorizedException ue = assertThrows(UnauthorizedException.class, () -> - storageService.retrieveTags(UUID.fromString(secretEntity.getSecretId()), authorizedUserId)); + storageService.retrieveTags(UUID.fromString(secretEntity.getSecretId()), authorizedSubject)); assertEquals("User is not authorized to read tags for this secret", ue.getMessage()); } @@ -723,11 +734,11 @@ void givenSuccessfulRequest_whenRetrieveTags_thenReturnContent() throws CsOpaExc new SecretEntity(testSecretId.toString(), TagEntity.setFromTagList(expectedTags)); when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(existingSecretEntity)); - when(opaService.canReadTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canReadTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); MatcherAssert.assertThat( - storageService.retrieveTags(UUID.fromString(existingSecretEntity.getSecretId()), authorizedUserId), + storageService.retrieveTags(UUID.fromString(existingSecretEntity.getSecretId()), authorizedSubject), containsInAnyOrder(expectedTags.toArray())); } @@ -735,7 +746,7 @@ void givenSuccessfulRequest_whenRetrieveTags_thenReturnContent() throws CsOpaExc void givenNoSecretShareWithGivenIdInDatabase_whenRetrieveTag_thenThrowNotFoundException() { String key = testTag.getKey(); NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.retrieveTag(testSecretId, key, authorizedUserId)); + assertThrows(NotFoundException.class, () -> storageService.retrieveTag(testSecretId, key, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -747,7 +758,7 @@ void givenNoTagWithGivenKeyInDatabaseForGivenObject_whenRetrieveTag_thenThrowNot assertThrows( NotFoundException.class, - () -> storageService.retrieveTag(testSecretId, key, authorizedUserId), + () -> storageService.retrieveTag(testSecretId, key, authorizedSubject), String.format( NO_TAG_WITH_KEY_EXISTS_FOR_SECRET_WITH_ID_EXCEPTION_MSG, testTag.getKey(), @@ -762,7 +773,7 @@ void givenSubjectHasNoAccess_whenRetrieveTag_thenThrowUnauthorizedException() th UnauthorizedException ue = assertThrows(UnauthorizedException.class, () -> - storageService.retrieveTag(testSecretId, testTag.getKey(), authorizedUserId)); + storageService.retrieveTag(testSecretId, testTag.getKey(), authorizedSubject)); assertEquals("User is not authorized to read tags for this secret", ue.getMessage()); } @@ -773,11 +784,11 @@ void givenSuccessfulRequest_whenRetrieveTag_thenReturnContent() throws CsOpaExce new SecretEntity(testSecretId.toString(), singleton(existingTagEntity)); when(secretEntityRepository.findById(testSecretId.toString())) .thenReturn(Optional.of(existingSecretEntity)); - when(opaService.canReadTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canReadTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); when(tagRepository.findBySecretAndKey(existingSecretEntity, existingTagEntity.getKey())) .thenReturn(Optional.of(existingTagEntity)); - assertEquals(testTag, storageService.retrieveTag(testSecretId, testTag.getKey(), authorizedUserId)); + assertEquals(testTag, storageService.retrieveTag(testSecretId, testTag.getKey(), authorizedSubject)); } @Test @@ -785,7 +796,7 @@ void givenTagHasReservedKey_whenUpdateTag_thenThrowIllegalArgumentException() { IllegalArgumentException iae = assertThrows( IllegalArgumentException.class, - () -> storageService.updateTag(testSecretId, testTagReservedCreationDateKey, authorizedUserId)); + () -> storageService.updateTag(testSecretId, testTagReservedCreationDateKey, authorizedSubject)); assertEquals( String.format(IS_RESERVED_KEY_EXCEPTION_MSG, testTagReservedCreationDateKey.getKey()), iae.getMessage()); @@ -795,7 +806,7 @@ void givenTagHasReservedKey_whenUpdateTag_thenThrowIllegalArgumentException() { void givenNoSecretShareWithGivenIdInDatabase_whenUpdateTag_thenThrowNotFoundException() { NotFoundException nfe = assertThrows( - NotFoundException.class, () -> storageService.updateTag(testSecretId, testTag, authorizedUserId)); + NotFoundException.class, () -> storageService.updateTag(testSecretId, testTag, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -806,7 +817,7 @@ void givenNoTagWithGivenKeyInDatabaseForGivenObject_whenUpdateTag_thenThrowNotFo assertThrows( NotFoundException.class, - () -> storageService.updateTag(testSecretId, testTag, authorizedUserId), + () -> storageService.updateTag(testSecretId, testTag, authorizedSubject), String.format( NO_TAG_WITH_KEY_EXISTS_FOR_SECRET_WITH_ID_EXCEPTION_MSG, testTag.getKey(), @@ -823,7 +834,7 @@ void givenSubjectHasNoAccess_whenUpdateTag_thenThrowUnauthorizedException() thro UnauthorizedException ue = assertThrows(UnauthorizedException.class, () -> - storageService.updateTag(UUID.fromString(secretEntity.getSecretId()), newTag, authorizedUserId)); + storageService.updateTag(UUID.fromString(secretEntity.getSecretId()), newTag, authorizedSubject)); assertEquals("User is not authorized to update tags for this secret", ue.getMessage()); } @@ -837,10 +848,10 @@ void givenSuccessfulRequest_whenUpdateTag_thenUpdateTag() throws CsOpaException, when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.of(existingSecretEntity)); when(tagRepository.findBySecretAndKey(existingSecretEntity, testTag.getKey())) .thenReturn(Optional.of(existingTagEntity)); - when(opaService.canUpdateTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canUpdateTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); - storageService.updateTag(UUID.fromString(existingSecretEntity.getSecretId()), newTag, authorizedUserId); + storageService.updateTag(UUID.fromString(existingSecretEntity.getSecretId()), newTag, authorizedSubject); ArgumentCaptor tagEntityArgumentCaptor = ArgumentCaptor.forClass(TagEntity.class); verify(tagRepository, times(1)).save(tagEntityArgumentCaptor.capture()); @@ -856,7 +867,7 @@ void givenTagHasReservedKey_whenDeleteTag_thenThrowIllegalArgumentException() { IllegalArgumentException iae = assertThrows( IllegalArgumentException.class, - () -> storageService.deleteTag(testSecretId, reservedKey, authorizedUserId)); + () -> storageService.deleteTag(testSecretId, reservedKey, authorizedSubject)); assertEquals(String.format(IS_RESERVED_KEY_EXCEPTION_MSG, reservedKey), iae.getMessage()); } @@ -865,7 +876,7 @@ void givenNoSecretShareWithGivenIdInDatabase_whenDeleteTag_thenThrowNotFoundExce String key = testTag.getKey(); NotFoundException nfe = - assertThrows(NotFoundException.class, () -> storageService.deleteTag(testSecretId, key, authorizedUserId)); + assertThrows(NotFoundException.class, () -> storageService.deleteTag(testSecretId, key, authorizedSubject)); assertEquals( String.format(NO_SECRET_WITH_ID_EXISTS_EXCEPTION_MSG, testSecretId), nfe.getMessage()); } @@ -877,7 +888,7 @@ void givenNoTagWithGivenKeyInDatabaseForGivenObject_whenDeleteTag_thenThrowNotFo assertThrows( NotFoundException.class, - () -> storageService.deleteTag(testSecretId, key, authorizedUserId), + () -> storageService.deleteTag(testSecretId, key, authorizedSubject), String.format( NO_TAG_WITH_KEY_EXISTS_FOR_SECRET_WITH_ID_EXCEPTION_MSG, testTag.getKey(), @@ -894,7 +905,7 @@ void givenSubjectHasNoAccess_whenDeleteTag_thenThrowUnauthorizedException() thro UnauthorizedException ue = assertThrows(UnauthorizedException.class, () -> storageService.deleteTag( - UUID.fromString(secretEntity.getSecretId()), tagEntityToDelete.getKey(), authorizedUserId)); + UUID.fromString(secretEntity.getSecretId()), tagEntityToDelete.getKey(), authorizedSubject)); assertEquals("User is not authorized to delete tags for this secret", ue.getMessage()); } @@ -906,12 +917,12 @@ void givenSuccessfulRequest_whenDeleteTag_thenDelete() throws CsOpaException, Un when(secretEntityRepository.findById(testSecretId.toString())).thenReturn(Optional.of(existingSecretEntity)); when(tagRepository.findBySecretAndKey(existingSecretEntity, tagEntityToDelete.getKey())) .thenReturn(Optional.of(tagEntityToDelete)); - when(opaService.canDeleteTags(authorizedUserId, setToTagList(existingSecretEntity.getTags()))) + when(opaService.canDeleteTags(authorizedSubject, setToTagList(existingSecretEntity.getTags()))) .thenReturn(true); assertEquals(1, existingSecretEntity.getTags().size()); storageService.deleteTag( - UUID.fromString(existingSecretEntity.getSecretId()), tagEntityToDelete.getKey(), authorizedUserId); + UUID.fromString(existingSecretEntity.getSecretId()), tagEntityToDelete.getKey(), authorizedSubject); assertEquals(0, existingSecretEntity.getTags().size()); } } diff --git a/amphora-service/src/test/java/io/carbynestack/amphora/service/rest/IntraVcpControllerTest.java b/amphora-service/src/test/java/io/carbynestack/amphora/service/rest/IntraVcpControllerTest.java index cccdeda..272fac8 100644 --- a/amphora-service/src/test/java/io/carbynestack/amphora/service/rest/IntraVcpControllerTest.java +++ b/amphora-service/src/test/java/io/carbynestack/amphora/service/rest/IntraVcpControllerTest.java @@ -6,16 +6,10 @@ */ package io.carbynestack.amphora.service.rest; -import static io.carbynestack.amphora.common.rest.AmphoraRestApiEndpoints.INTRA_VCP_OPERATIONS_SEGMENT; -import static io.carbynestack.amphora.service.util.ServletUriComponentsBuilderUtil.runInMockedHttpRequestContextForUri; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - import io.carbynestack.amphora.common.SecretShare; +import io.carbynestack.amphora.service.exceptions.CsOpaException; +import io.carbynestack.amphora.service.exceptions.UnauthorizedException; import io.carbynestack.amphora.service.persistence.metadata.StorageService; -import java.net.URI; -import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -24,8 +18,18 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import java.net.URI; +import java.util.UUID; + +import static io.carbynestack.amphora.common.rest.AmphoraRestApiEndpoints.INTRA_VCP_OPERATIONS_SEGMENT; +import static io.carbynestack.amphora.service.util.ServletUriComponentsBuilderUtil.runInMockedHttpRequestContextForUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) class IntraVcpControllerTest { + private final String authorizedSubjectId ="afc0117f-c9cd-4d8c-acee-fa1433ca0fdd"; @Mock private StorageService storageService; @@ -59,14 +63,14 @@ void givenSuccessfulRequest_whenUploadSecretShare_thenReturnCreatedWithExpectedC } @Test - void givenSuccessfulRequest_whenDownloadSecretShare_thenReturnOkWithExpectedContent() { + void givenSuccessfulRequest_whenDownloadSecretShare_thenReturnOkWithExpectedContent() throws CsOpaException, UnauthorizedException { UUID secretShareId = UUID.fromString("3bcf8308-8f50-4d24-a37b-b0075bb5e779"); SecretShare expectedSecretShare = SecretShare.builder().secretId(secretShareId).build(); - when(storageService.getSecretShareAuthorized(secretShareId)).thenReturn(expectedSecretShare); + when(storageService.useSecretShare(secretShareId, authorizedSubjectId)).thenReturn(expectedSecretShare); ResponseEntity actualResponse = - intraVcpController.downloadSecretShare(secretShareId); + intraVcpController.downloadSecretShare(secretShareId, authorizedSubjectId); assertEquals(HttpStatus.OK, actualResponse.getStatusCode()); assertEquals(expectedSecretShare, actualResponse.getBody());