optionally limit account-context to natural persons (#187)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/187 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
		| @@ -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(); | ||||
|   | ||||
| @@ -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; | ||||
| @@ -193,9 +194,18 @@ public class HsCredentialsController implements CredentialsApi { | ||||
|                     mapper.map(person, HsOfficePersonResource.class) | ||||
|             ) | ||||
|         ); | ||||
|         resource.setContexts(mapper.mapList(entity.getLoginContexts().stream().toList(), ContextResource.class)); | ||||
|  | ||||
|         resource.setContexts(mapToValidContextResources(entity)); | ||||
|     }; | ||||
|  | ||||
|     private List<ContextResource> 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<CredentialsInsertResource, HsCredentialsEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { | ||||
|  | ||||
|         // TODO.impl: we need to make sure that the current subject is OWNER (or ADMIN?) of the person | ||||
|   | ||||
| @@ -15,6 +15,8 @@ components: | ||||
|                 qualifier: | ||||
|                     type: string | ||||
|                     maxLength: 80 | ||||
|                 onlyForNaturalPersons: | ||||
|                     type: boolean | ||||
|             required: | ||||
|                 - uuid | ||||
|                 - type | ||||
|   | ||||
| @@ -38,6 +38,8 @@ create table hs_accounts.context | ||||
|     type                        varchar(16), | ||||
|     qualifier                   varchar(80), | ||||
|  | ||||
|     only_for_natural_persons    boolean default false, | ||||
|  | ||||
|     unique (type, qualifier) | ||||
| ); | ||||
| --// | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 { | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user