create relation with holder- and contact-data, and search for contact emailAddress + relation mark #136

Merged
hsh-michaelhoennig merged 12 commits from feature/create-relation-with-holder-and-contact-data into master 2024-12-13 14:09:03 +01:00
6 changed files with 14 additions and 55 deletions
Showing only changes of commit c1c6f8b92d - Show all commits

View File

@ -40,12 +40,12 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final String caption, final String caption,
final String emailAddressRegEx) { final String emailAddress) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
validate("caption, emailAddress").atMaxOne(caption, emailAddressRegEx); validate("caption, emailAddress").atMaxOne(caption, emailAddress);
final var entities = emailAddressRegEx != null final var entities = emailAddress != null
? contactRepo.findContactByEmailAddressRegEx(emailAddressRegEx) ? contactRepo.findContactByEmailAddress(emailAddress)
: contactRepo.findContactByOptionalCaptionLike(caption); : contactRepo.findContactByOptionalCaptionLike(caption);
final var resources = mapper.mapList(entities, HsOfficeContactResource.class); final var resources = mapper.mapList(entities, HsOfficeContactResource.class);

View File

@ -4,8 +4,6 @@ import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -25,23 +23,13 @@ public interface HsOfficeContactRbacRepository extends Repository<HsOfficeContac
@Query(value = """ @Query(value = """
select c.* from hs_office.contact_rv c select c.* from hs_office.contact_rv c
where jsonb_path_exists(c.emailaddresses, cast(:emailAddressExpression as jsonpath)) where exists (
SELECT 1 FROM jsonb_each_text(c.emailAddresses) AS kv(key, value)
WHERE kv.value LIKE :emailAddressRegEx
)
""", nativeQuery = true) """, nativeQuery = true)
@Timed("app.office.contacts.repo.findContactByEmailAddressImpl.rbac") @Timed("app.office.contacts.repo.findContactByEmailAddress.rbac")
List<HsOfficeContactRbacEntity> findContactByEmailAddressImpl(final String emailAddressExpression); List<HsOfficeContactRbacEntity> findContactByEmailAddress(final String emailAddressRegEx);
default List<HsOfficeContactRbacEntity> findContactByEmailAddressRegEx(@NotNull final String emailAddress) {
return findContactByEmailAddressImpl("$.** ? (@ like_regex \"" + emailRegEx(emailAddress) + "\")");
}
static String emailRegEx(@NotNull String emailAddress) {
// TODO.impl: find more secure solution, maybe we substitute a placeholder with the whole expression?
if (emailAddress.contains("'") || emailAddress.contains("\"") || emailAddress.endsWith("\\") ) {
throw new ValidationException(
"emailAddressRegEx contains invalid characters: " + emailAddress);
}
return emailAddress.replace("%", ".*"); // the JSON-matcher in PostgreSQL needs a wildcard
}
@Timed("app.office.contacts.repo.save.rbac") @Timed("app.office.contacts.repo.save.rbac")
HsOfficeContactRbacEntity save(final HsOfficeContactRbacEntity entity); HsOfficeContactRbacEntity save(final HsOfficeContactRbacEntity entity);

View File

@ -13,13 +13,13 @@ get:
schema: schema:
type: string type: string
description: Beginning of caption to filter the results. description: Beginning of caption to filter the results.
- name: emailAddressRegEx - name: emailAddress
in: query in: query
required: false required: false
schema: schema:
type: string type: string
description: description:
Regular-expression for an email-address to filter the results. Email-address to filter the results, use '%' as wildcard.
responses: responses:
"200": "200":
description: OK description: OK

View File

@ -21,7 +21,7 @@ get:
required: false required: false
schema: schema:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationType' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationType'
description: Prefix of name properties from holder or contact to filter the results. description: Beginning of name properties from holder or contact to filter the results.
- name: mark - name: mark
in: query in: query
required: false required: false

View File

@ -193,7 +193,7 @@ class HsOfficeContactRbacRepositoryIntegrationTest extends ContextBasedTestWithC
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
// when // when
final var result = contactRepo.findContactByEmailAddressRegEx("@secondcontact.example.com"); final var result = contactRepo.findContactByEmailAddress("%@secondcontact.example.com");
// then // then
exactlyTheseContactsAreReturned(result, "second contact"); exactlyTheseContactsAreReturned(result, "second contact");

View File

@ -1,29 +0,0 @@
package net.hostsharing.hsadminng.hs.office.contact;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class HsOfficeContactRbacRepositoryUnitTest {
@Test
void rejectsSingleQuoteInEmailAddressRegEx() {
final var throwable = catchThrowable( () ->
HsOfficeContactRbacRepository.emailRegEx("target@'example.org")
);
assertThat(throwable).isInstanceOf(ValidationException.class);
}
@Test
void rejectsTrailingBackslashInEmailAddressRegEx() {
final var throwable = catchThrowable( () ->
HsOfficeContactRbacRepository.emailRegEx("target@example.org\\")
);
assertThat(throwable).isInstanceOf(ValidationException.class);
}
}