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)
|
@Column(name = "qualifier", length = 80)
|
||||||
private String qualifier;
|
private String qualifier;
|
||||||
|
|
||||||
|
@Column(name = "only_for_natural_persons")
|
||||||
|
private boolean onlyForNaturalPersons;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toShortString() {
|
public String toShortString() {
|
||||||
return toString();
|
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.CredentialsResource;
|
||||||
import net.hostsharing.hsadminng.accounts.generated.api.v1.model.HsOfficePersonResource;
|
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.HsOfficePersonRbacRepository;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
|
||||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
||||||
@@ -190,12 +191,21 @@ public class HsCredentialsController implements CredentialsApi {
|
|||||||
);
|
);
|
||||||
of(entity.getPerson()).ifPresent(
|
of(entity.getPerson()).ifPresent(
|
||||||
person -> resource.setPerson(
|
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<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) -> {
|
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
|
// TODO.impl: we need to make sure that the current subject is OWNER (or ADMIN?) of the person
|
||||||
|
@@ -15,6 +15,8 @@ components:
|
|||||||
qualifier:
|
qualifier:
|
||||||
type: string
|
type: string
|
||||||
maxLength: 80
|
maxLength: 80
|
||||||
|
onlyForNaturalPersons:
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- uuid
|
- uuid
|
||||||
- type
|
- type
|
||||||
|
@@ -32,11 +32,13 @@ create table hs_accounts.credentials
|
|||||||
|
|
||||||
create table hs_accounts.context
|
create table hs_accounts.context
|
||||||
(
|
(
|
||||||
uuid uuid PRIMARY KEY,
|
uuid uuid PRIMARY KEY,
|
||||||
version int not null default 0,
|
version int not null default 0,
|
||||||
|
|
||||||
type varchar(16),
|
type varchar(16),
|
||||||
qualifier varchar(80),
|
qualifier varchar(80),
|
||||||
|
|
||||||
|
only_for_natural_persons boolean default false,
|
||||||
|
|
||||||
unique (type, qualifier)
|
unique (type, qualifier)
|
||||||
);
|
);
|
||||||
|
@@ -26,15 +26,17 @@ begin
|
|||||||
personFranUuid = (SELECT uuid FROM hs_office.person WHERE givenName='Fran');
|
personFranUuid = (SELECT uuid FROM hs_office.person WHERE givenName='Fran');
|
||||||
|
|
||||||
-- Add test contexts
|
-- Add test contexts
|
||||||
INSERT INTO hs_accounts.context (uuid, type, qualifier) VALUES
|
INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES
|
||||||
('11111111-1111-1111-1111-111111111111', 'HSADMIN', 'prod')
|
('11111111-1111-1111-1111-111111111111', 'HSADMIN', 'prod', true)
|
||||||
RETURNING * INTO context_HSADMIN_prod;
|
RETURNING * INTO context_HSADMIN_prod;
|
||||||
INSERT INTO hs_accounts.context (uuid, type, qualifier) VALUES
|
INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES
|
||||||
('22222222-2222-2222-2222-222222222222', 'SSH', 'internal')
|
('22222222-2222-2222-2222-222222222222', 'SSH', 'internal', true)
|
||||||
RETURNING * INTO context_SSH_internal;
|
RETURNING * INTO context_SSH_internal;
|
||||||
INSERT INTO hs_accounts.context (uuid, type, qualifier) VALUES
|
INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES
|
||||||
('33333333-3333-3333-3333-333333333333', 'MATRIX', 'internal')
|
('33333333-3333-3333-3333-333333333333', 'MATRIX', 'internal', true)
|
||||||
RETURNING * INTO context_MATRIX_internal;
|
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
|
-- grant general access to public credential contexts
|
||||||
-- TODO_impl: RBAC rules for _rv do not yet work properly
|
-- TODO_impl: RBAC rules for _rv do not yet work properly
|
||||||
|
@@ -60,7 +60,7 @@ class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest
|
|||||||
final var rowsAfter = query.getResultList();
|
final var rowsAfter = query.getResultList();
|
||||||
|
|
||||||
// then
|
// 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
|
@Test
|
||||||
|
@@ -27,8 +27,10 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
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 net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -77,6 +79,49 @@ class HsCredentialsControllerRestTest {
|
|||||||
@MockitoBean
|
@MockitoBean
|
||||||
CredentialContextResourceToEntityMapper contextMapper;
|
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
|
@Test
|
||||||
void patchCredentialsUsed() throws Exception {
|
void patchCredentialsUsed() throws Exception {
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user