audit log with context per task instead of just transaction
This commit is contained in:
parent
2506acc531
commit
560cd9cf9f
@ -0,0 +1,12 @@
|
|||||||
|
--liquibase formatted sql
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- NUMERIC-HASH-FUNCTIONS
|
||||||
|
--changeset hash:1 endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
create function bigIntHash(text) returns bigint as $$
|
||||||
|
select ('x'||substr(md5($1),1,16))::bit(64)::bigint;
|
||||||
|
$$ language sql;
|
||||||
|
--//
|
@ -23,7 +23,8 @@ do $$
|
|||||||
*/
|
*/
|
||||||
create table tx_context
|
create table tx_context
|
||||||
(
|
(
|
||||||
txId bigint primary key not null,
|
contextId bigint primary key not null,
|
||||||
|
txId bigint not null,
|
||||||
txTimestamp timestamp not null,
|
txTimestamp timestamp not null,
|
||||||
currentUser varchar(63) not null, -- not the uuid, because users can be deleted
|
currentUser varchar(63) not null, -- not the uuid, because users can be deleted
|
||||||
assumedRoles varchar not null, -- not the uuids, because roles can be deleted
|
assumedRoles varchar not null, -- not the uuids, because roles can be deleted
|
||||||
@ -42,7 +43,7 @@ create index on tx_context using brin (txTimestamp);
|
|||||||
*/
|
*/
|
||||||
create table tx_journal
|
create table tx_journal
|
||||||
(
|
(
|
||||||
txId bigint not null references tx_context (txId),
|
contextId bigint not null references tx_context (contextId),
|
||||||
targetTable text not null,
|
targetTable text not null,
|
||||||
targetUuid uuid not null, -- Assumes that all audited tables have a uuid column.
|
targetUuid uuid not null, -- Assumes that all audited tables have a uuid column.
|
||||||
targetOp operation not null,
|
targetOp operation not null,
|
||||||
@ -61,28 +62,33 @@ create index on tx_journal (targetTable, targetUuid);
|
|||||||
create or replace function tx_journal_trigger()
|
create or replace function tx_journal_trigger()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
curTask text;
|
||||||
|
curContextId bigint;
|
||||||
begin
|
begin
|
||||||
|
curTask := currentTask();
|
||||||
|
curContextId := txid_current()+bigIntHash(curTask);
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into tx_context
|
into tx_context (contextId, txId, txTimestamp, currentUser, assumedRoles, currentTask, currentRequest)
|
||||||
values (txid_current(), now(),
|
values (curContextId, txid_current(), now(),
|
||||||
currentUser(), assumedRoles(), currentTask(), currentRequest())
|
currentUser(), assumedRoles(), curTask, currentRequest())
|
||||||
on conflict do nothing;
|
on conflict do nothing;
|
||||||
|
|
||||||
case tg_op
|
case tg_op
|
||||||
when 'INSERT' then insert
|
when 'INSERT' then insert
|
||||||
into tx_journal
|
into tx_journal
|
||||||
values (txid_current(),
|
values (curContextId,
|
||||||
tg_table_name, new.uuid, tg_op::operation,
|
tg_table_name, new.uuid, tg_op::operation,
|
||||||
to_jsonb(new));
|
to_jsonb(new));
|
||||||
when 'UPDATE' then insert
|
when 'UPDATE' then insert
|
||||||
into tx_journal
|
into tx_journal
|
||||||
values (txid_current(),
|
values (curContextId,
|
||||||
tg_table_name, old.uuid, tg_op::operation,
|
tg_table_name, old.uuid, tg_op::operation,
|
||||||
jsonb_changes_delta(to_jsonb(old), to_jsonb(new)));
|
jsonb_changes_delta(to_jsonb(old), to_jsonb(new)));
|
||||||
when 'DELETE' then insert
|
when 'DELETE' then insert
|
||||||
into tx_journal
|
into tx_journal
|
||||||
values (txid_current(),
|
values (curContextId,
|
||||||
tg_table_name, old.uuid, 'DELETE'::operation,
|
tg_table_name, old.uuid, 'DELETE'::operation,
|
||||||
null::jsonb);
|
null::jsonb);
|
||||||
else raise exception 'Trigger op % not supported for %.', tg_op, tg_table_name;
|
else raise exception 'Trigger op % not supported for %.', tg_op, tg_table_name;
|
||||||
|
@ -14,7 +14,7 @@ declare
|
|||||||
currentTask varchar;
|
currentTask varchar;
|
||||||
emailAddr varchar;
|
emailAddr varchar;
|
||||||
begin
|
begin
|
||||||
currentTask = 'creating RBAC test contact ' || contLabel;
|
currentTask = 'creating contact test-data ' || contLabel;
|
||||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||||
|
|
||||||
emailAddr = 'contact-admin@' || cleanIdentifier(contLabel) || '.example.com';
|
emailAddr = 'contact-admin@' || cleanIdentifier(contLabel) || '.example.com';
|
||||||
|
@ -21,7 +21,7 @@ declare
|
|||||||
emailAddr varchar;
|
emailAddr varchar;
|
||||||
begin
|
begin
|
||||||
fullName := concat_ws(', ', newTradeName, newFamilyName, newGivenName);
|
fullName := concat_ws(', ', newTradeName, newFamilyName, newGivenName);
|
||||||
currentTask = 'creating RBAC test person ' || fullName;
|
currentTask = 'creating person test-data ' || fullName;
|
||||||
emailAddr = 'person-' || left(cleanIdentifier(fullName), 32) || '@example.com';
|
emailAddr = 'person-' || left(cleanIdentifier(fullName), 32) || '@example.com';
|
||||||
call defineContext(currentTask);
|
call defineContext(currentTask);
|
||||||
perform createRbacUser(emailAddr);
|
perform createRbacUser(emailAddr);
|
||||||
|
@ -17,7 +17,7 @@ declare
|
|||||||
relatedContact hs_office_contact;
|
relatedContact hs_office_contact;
|
||||||
begin
|
begin
|
||||||
idName := cleanIdentifier( personTradeName|| '-' || contactLabel);
|
idName := cleanIdentifier( personTradeName|| '-' || contactLabel);
|
||||||
currentTask := 'creating RBAC test partner ' || idName;
|
currentTask := 'creating partner test-data ' || idName;
|
||||||
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
||||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ declare
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonFamilyName);
|
idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonFamilyName);
|
||||||
currentTask := 'creating RBAC test relationship ' || idName;
|
currentTask := 'creating relationship test-data ' || idName;
|
||||||
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
||||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ declare
|
|||||||
currentTask varchar;
|
currentTask varchar;
|
||||||
emailAddr varchar;
|
emailAddr varchar;
|
||||||
begin
|
begin
|
||||||
currentTask = 'creating RBAC test bankaccount ' || givenHolder;
|
currentTask = 'creating bankaccount test-data ' || givenHolder;
|
||||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||||
|
|
||||||
emailAddr = 'bankaccount-admin@' || cleanIdentifier(givenHolder) || '.example.com';
|
emailAddr = 'bankaccount-admin@' || cleanIdentifier(givenHolder) || '.example.com';
|
||||||
|
@ -19,7 +19,7 @@ declare
|
|||||||
newDebitorNumber numeric(6);
|
newDebitorNumber numeric(6);
|
||||||
begin
|
begin
|
||||||
idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel);
|
idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel);
|
||||||
currentTask := 'creating RBAC test debitor ' || idName;
|
currentTask := 'creating debitor test-data ' || idName;
|
||||||
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
||||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ databaseChangeLog:
|
|||||||
file: db/changelog/004-jsonb-changes-delta.sql
|
file: db/changelog/004-jsonb-changes-delta.sql
|
||||||
- include:
|
- include:
|
||||||
file: db/changelog/005-uuid-ossp-extension.sql
|
file: db/changelog/005-uuid-ossp-extension.sql
|
||||||
|
- include:
|
||||||
|
file: db/changelog/006-numeric-hash-functions.sql
|
||||||
- include:
|
- include:
|
||||||
file: db/changelog/010-context.sql
|
file: db/changelog/010-context.sql
|
||||||
- include:
|
- include:
|
||||||
|
@ -293,14 +293,13 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
}).assertSuccessful().returnedValue();
|
}).assertSuccessful().returnedValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void auditJournalLogIsAvailable() {
|
public void auditJournalLogIsAvailable() {
|
||||||
// given
|
// given
|
||||||
final var query = em.createNativeQuery("""
|
final var query = em.createNativeQuery("""
|
||||||
select c.currenttask, j.targettable, j.targetop
|
select c.currenttask, j.targettable, j.targetop
|
||||||
from tx_journal j
|
from tx_journal j
|
||||||
join tx_context c on j.txid = c.txid
|
join tx_context c on j.contextId = c.contextId
|
||||||
where targettable = 'hs_office_bankaccount';
|
where targettable = 'hs_office_bankaccount';
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -308,8 +307,9 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(customerLogEntries).map(Arrays::toString)
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
.contains("[creating RBAC test bankaccount First GmbH, hs_office_bankaccount, INSERT]");
|
"[creating bankaccount test-data First GmbH, hs_office_bankaccount, INSERT]",
|
||||||
|
"[creating bankaccount test-data Second e.K., hs_office_bankaccount, INSERT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -271,7 +271,7 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var query = em.createNativeQuery("""
|
final var query = em.createNativeQuery("""
|
||||||
select c.currenttask, j.targettable, j.targetop
|
select c.currenttask, j.targettable, j.targetop
|
||||||
from tx_journal j
|
from tx_journal j
|
||||||
join tx_context c on j.txid = c.txid
|
join tx_context c on j.contextId = c.contextId
|
||||||
where targettable = 'hs_office_contact';
|
where targettable = 'hs_office_contact';
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -279,8 +279,9 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(customerLogEntries).map(Arrays::toString)
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
.contains("[creating RBAC test contact first contact, hs_office_contact, INSERT]");
|
"[creating contact test-data first contact, hs_office_contact, INSERT]",
|
||||||
|
"[creating contact test-data second contact, hs_office_contact, INSERT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HsOfficeContactEntity givenSomeTemporaryContact(
|
private HsOfficeContactEntity givenSomeTemporaryContact(
|
||||||
|
@ -438,7 +438,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var query = em.createNativeQuery("""
|
final var query = em.createNativeQuery("""
|
||||||
select c.currenttask, j.targettable, j.targetop
|
select c.currenttask, j.targettable, j.targetop
|
||||||
from tx_journal j
|
from tx_journal j
|
||||||
join tx_context c on j.txid = c.txid
|
join tx_context c on j.contextId = c.contextId
|
||||||
where targettable = 'hs_office_debitor';
|
where targettable = 'hs_office_debitor';
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -446,8 +446,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(customerLogEntries).map(Arrays::toString)
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
.contains("[creating RBAC test debitor FirstGmbH-firstcontact, hs_office_debitor, INSERT]");
|
"[creating debitor test-data FirstGmbH-firstcontact, hs_office_debitor, INSERT]",
|
||||||
|
"[creating debitor test-data Seconde.K.-secondcontact, hs_office_debitor, INSERT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HsOfficePartnerEntity rawReference(final HsOfficePartnerEntity givenPartner) {
|
private HsOfficePartnerEntity rawReference(final HsOfficePartnerEntity givenPartner) {
|
||||||
|
@ -393,7 +393,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var query = em.createNativeQuery("""
|
final var query = em.createNativeQuery("""
|
||||||
select c.currenttask, j.targettable, j.targetop
|
select c.currenttask, j.targettable, j.targetop
|
||||||
from tx_journal j
|
from tx_journal j
|
||||||
join tx_context c on j.txid = c.txid
|
join tx_context c on j.contextId = c.contextId
|
||||||
where targettable = 'hs_office_partner';
|
where targettable = 'hs_office_partner';
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -401,8 +401,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(customerLogEntries).map(Arrays::toString)
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
.contains("[creating RBAC test partner FirstGmbH-firstcontact, hs_office_partner, INSERT]");
|
"[creating partner test-data FirstGmbH-firstcontact, hs_office_partner, INSERT]",
|
||||||
|
"[creating partner test-data Seconde.K.-secondcontact, hs_office_partner, INSERT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
@ -3,9 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person;
|
|||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.context.ContextBasedTest;
|
import net.hostsharing.hsadminng.context.ContextBasedTest;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantRepository;
|
|
||||||
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
|
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
|
|
||||||
import net.hostsharing.test.Array;
|
import net.hostsharing.test.Array;
|
||||||
import net.hostsharing.test.JpaAttempt;
|
import net.hostsharing.test.JpaAttempt;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
@ -272,7 +270,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var query = em.createNativeQuery("""
|
final var query = em.createNativeQuery("""
|
||||||
select c.currenttask, j.targettable, j.targetop
|
select c.currenttask, j.targettable, j.targetop
|
||||||
from tx_journal j
|
from tx_journal j
|
||||||
join tx_context c on j.txid = c.txid
|
join tx_context c on j.contextId = c.contextId
|
||||||
where targettable = 'hs_office_person';
|
where targettable = 'hs_office_person';
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -280,8 +278,9 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(customerLogEntries).map(Arrays::toString)
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
.contains("[creating RBAC test person First GmbH, hs_office_person, INSERT]");
|
"[creating person test-data First GmbH, hs_office_person, INSERT]",
|
||||||
|
"[creating person test-data Second e.K., Sandra, Miller, hs_office_person, INSERT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
@ -26,7 +26,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@ComponentScan(basePackageClasses = { HsOfficeRelationshipRepository.class, Context.class, JpaAttempt.class })
|
@ComponentScan(basePackageClasses = { HsOfficeRelationshipRepository.class, Context.class, JpaAttempt.class })
|
||||||
@ -373,7 +372,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var query = em.createNativeQuery("""
|
final var query = em.createNativeQuery("""
|
||||||
select c.currenttask, j.targettable, j.targetop
|
select c.currenttask, j.targettable, j.targetop
|
||||||
from tx_journal j
|
from tx_journal j
|
||||||
join tx_context c on j.txid = c.txid
|
join tx_context c on j.contextId = c.contextId
|
||||||
where targettable = 'hs_office_relationship';
|
where targettable = 'hs_office_relationship';
|
||||||
""");
|
""");
|
||||||
|
|
||||||
@ -381,8 +380,9 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(customerLogEntries).map(Arrays::toString)
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
.contains("[creating RBAC test relationship FirstGmbH-Smith, hs_office_relationship, INSERT]");
|
"[creating relationship test-data FirstGmbH-Smith, hs_office_relationship, INSERT]",
|
||||||
|
"[creating relationship test-data Seconde.K.-Smith, hs_office_relationship, INSERT]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HsOfficeRelationshipEntity givenSomeTemporaryRelationshipBessler(final String holderPerson, final String contact) {
|
private HsOfficeRelationshipEntity givenSomeTemporaryRelationshipBessler(final String holderPerson, final String contact) {
|
||||||
|
Loading…
Reference in New Issue
Block a user