diff --git a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java index cb6ade77..99decc09 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java @@ -50,6 +50,9 @@ public abstract class HsCredentialsContext implements Stringifyable, BaseEntity< @Column(name = "qualifier", length = 80) private String qualifier; + @Column(name = "only_for_natural_persons") + private boolean onlyForNaturalPersons; + @Override public String toShortString() { return toString(); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java index 7ad6b681..96882f6d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java @@ -17,6 +17,7 @@ import net.hostsharing.hsadminng.accounts.generated.api.v1.model.CredentialsPatc import net.hostsharing.hsadminng.accounts.generated.api.v1.model.CredentialsResource; import net.hostsharing.hsadminng.accounts.generated.api.v1.model.HsOfficePersonResource; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacRepository; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import net.hostsharing.hsadminng.mapper.StrictMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity; @@ -190,12 +191,21 @@ public class HsCredentialsController implements CredentialsApi { ); of(entity.getPerson()).ifPresent( person -> resource.setPerson( - mapper.map(person, HsOfficePersonResource.class) - ) + mapper.map(person, HsOfficePersonResource.class) + ) ); - resource.setContexts(mapper.mapList(entity.getLoginContexts().stream().toList(), ContextResource.class)); + + resource.setContexts(mapToValidContextResources(entity)); }; + private List mapToValidContextResources(final HsCredentialsEntity entity) { + var allContexts = mapper.mapList(entity.getLoginContexts().stream().toList(), ContextResource.class); + return allContexts.stream() + .filter(context -> !context.getOnlyForNaturalPersons() || + entity.getPerson().getPersonType() == HsOfficePersonType.NATURAL_PERSON) + .toList(); + } + final BiConsumer RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { // TODO.impl: we need to make sure that the current subject is OWNER (or ADMIN?) of the person diff --git a/src/main/resources/api-definition/accounts/context-schemas.yaml b/src/main/resources/api-definition/accounts/context-schemas.yaml index d47016e4..236db188 100644 --- a/src/main/resources/api-definition/accounts/context-schemas.yaml +++ b/src/main/resources/api-definition/accounts/context-schemas.yaml @@ -15,6 +15,8 @@ components: qualifier: type: string maxLength: 80 + onlyForNaturalPersons: + type: boolean required: - uuid - type diff --git a/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql b/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql index 95bc03d4..5938607e 100644 --- a/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql +++ b/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql @@ -32,11 +32,13 @@ create table hs_accounts.credentials create table hs_accounts.context ( - uuid uuid PRIMARY KEY, - version int not null default 0, + uuid uuid PRIMARY KEY, + version int not null default 0, - type varchar(16), - qualifier varchar(80), + type varchar(16), + qualifier varchar(80), + + only_for_natural_persons boolean default false, unique (type, qualifier) ); diff --git a/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql b/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql index 64331640..27b38c14 100644 --- a/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql +++ b/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql @@ -26,15 +26,17 @@ begin personFranUuid = (SELECT uuid FROM hs_office.person WHERE givenName='Fran'); -- Add test contexts - INSERT INTO hs_accounts.context (uuid, type, qualifier) VALUES - ('11111111-1111-1111-1111-111111111111', 'HSADMIN', 'prod') + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES + ('11111111-1111-1111-1111-111111111111', 'HSADMIN', 'prod', true) RETURNING * INTO context_HSADMIN_prod; - INSERT INTO hs_accounts.context (uuid, type, qualifier) VALUES - ('22222222-2222-2222-2222-222222222222', 'SSH', 'internal') + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES + ('22222222-2222-2222-2222-222222222222', 'SSH', 'internal', true) RETURNING * INTO context_SSH_internal; - INSERT INTO hs_accounts.context (uuid, type, qualifier) VALUES - ('33333333-3333-3333-3333-333333333333', 'MATRIX', 'internal') + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES + ('33333333-3333-3333-3333-333333333333', 'MATRIX', 'internal', true) RETURNING * INTO context_MATRIX_internal; + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES + ('44444444-4444-4444-4444-444444444444', 'MASTODON', 'external', false); -- grant general access to public credential contexts -- TODO_impl: RBAC rules for _rv do not yet work properly diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java index a5b266b7..eece0999 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java @@ -60,7 +60,7 @@ class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest final var rowsAfter = query.getResultList(); // then - assertThat(rowsAfter).as("hs_accounts.context_hv should now contain the test-data rows for the current timestamp").hasSize(3); + assertThat(rowsAfter).as("hs_accounts.context_hv should now contain the test-data rows for the current timestamp").hasSize(4); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsControllerRestTest.java index 14db063e..8f2ee944 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsControllerRestTest.java @@ -27,8 +27,10 @@ import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Optional; +import java.util.Set; import java.util.UUID; +import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL_PERSON; import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -77,6 +79,49 @@ class HsCredentialsControllerRestTest { @MockitoBean CredentialContextResourceToEntityMapper contextMapper; + @Test + void shouldFilterInvalidContextsRegardingNonNaturalPerson() throws Exception { + // given + final var givenCredentialsUuid = UUID.randomUUID(); + final var contextForNP = HsCredentialsContextRealEntity.builder() + .uuid(UUID.randomUUID()) + .type("HSADMIN") + .qualifier("prod") + .onlyForNaturalPersons(true) + .build(); + final var contextForAll = HsCredentialsContextRealEntity.builder() + .uuid(UUID.randomUUID()) + .type("SSH") + .qualifier("prod") + .onlyForNaturalPersons(false) + .build(); + final var credentialsEntity = HsCredentialsEntity.builder() + .uuid(givenCredentialsUuid) + .person(HsOfficePersonRbacEntity.builder() + .uuid(PERSON_UUID) + .personType(LEGAL_PERSON) + .build()) + .subject(RbacSubjectEntity.builder().name("some-nickname").build()) + .loginContexts(Set.of(contextForNP, contextForAll)) + .build(); + when(credentialsRepo.findByUuid(givenCredentialsUuid)) + .thenReturn(Optional.of(credentialsEntity)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/accounts/credentials/" + givenCredentialsUuid) + .header("Authorization", "Bearer test") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.contexts.length()").value(1)) + .andExpect(jsonPath("$.contexts[0].type").value("SSH")) + .andExpect(jsonPath("$.contexts[0].qualifier").value("prod")) + .andExpect(jsonPath("$.contexts[0].onlyForNaturalPersons").value(false)); + } + @Test void patchCredentialsUsed() throws Exception {