Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ebe6314ec
|
||
|
|
4e5007a64a | ||
|
|
0fdb078a88 | ||
|
|
28337c5614 | ||
|
|
cbd6442dcf | ||
|
|
128e1cf9c6 | ||
|
|
7c6f74cb1e | ||
|
|
eb056e04e2 | ||
|
|
fb9202a76d
|
||
|
|
13d5085489 | ||
|
|
307d3c8668
|
||
|
|
0c1d0dd94e |
No files matched your search
@@ -1,12 +1,12 @@
|
||||
version: 2.1
|
||||
setup: true
|
||||
orbs:
|
||||
qqq-orb: kingsrook/qqq-orb@0.7.12
|
||||
qqq-orb: kingsrook/qqq-orb@0.7.15
|
||||
workflows:
|
||||
setup:
|
||||
jobs:
|
||||
- qqq-orb/generate_pipeline:
|
||||
orb_version: 0.7.12
|
||||
orb_version: 0.7.15
|
||||
filters:
|
||||
branches:
|
||||
ignore: automation/qbit-rc-pending
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
profile: qbit
|
||||
orb_version: 0.7.12
|
||||
orb_version: 0.7.15
|
||||
default_branch: develop
|
||||
java_version: '21'
|
||||
browsers: true
|
||||
@@ -13,4 +13,3 @@ publish:
|
||||
release_candidate: true
|
||||
release: true
|
||||
asciidoc: false
|
||||
release_completion: true
|
||||
@@ -137,12 +137,6 @@ Open `/app/worm_audit` (or `/app/admin/worm_audit` with `parentAppName="admin"`)
|
||||
The full-width `wormAuditHome` HTML widget uses the standard `/qqq/v1/widget` API.
|
||||
Hosts can add `wormAuditHealthCard` to their own dashboard. Both widgets and the app
|
||||
require read permission on at least one WORM table, using the host's table permission rules.
|
||||
To embed WORM in an existing host app, set `withIncludeApp(false)` and configure
|
||||
`appName`, `appLabel` and optional `parentAppName` for that host app. The host owns
|
||||
its navigation, home widget placement and app help; the qbit does not create or replace
|
||||
the app or fill its help. Both widgets and all table help, permissions, guards,
|
||||
processes and schedules remain registered. The health card links to the configured
|
||||
host app. Standalone app creation remains enabled by default.
|
||||
The aggregate home shows only identifiers, counts, times, shortened hashes and reasons;
|
||||
it never renders audit messages or field values. Record links retain the target table's permissions.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<properties>
|
||||
<revision>0.1.0-SNAPSHOT</revision>
|
||||
<revision>0.1.0-RC.6</revision>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jacoco.version>0.8.15</jacoco.version>
|
||||
|
||||
@@ -48,7 +48,6 @@ public class WormAuditQBitConfig implements QBitConfig
|
||||
private String exportLockMode = "GOVERNANCE";
|
||||
private int exportRetentionDays = 30;
|
||||
private final WormAuditSettings settings = new WormAuditSettings(this);
|
||||
private boolean includeApp = true;
|
||||
private String appName = "worm_audit";
|
||||
private String appLabel = "WORM Audit";
|
||||
private String parentAppName;
|
||||
@@ -132,26 +131,7 @@ public class WormAuditQBitConfig implements QBitConfig
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether to create the standalone app. Widgets remain available when false.
|
||||
*******************************************************************************/
|
||||
public boolean getIncludeApp()
|
||||
{
|
||||
return (includeApp);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Disable standalone app creation when the host owns the Audit app and help.
|
||||
*******************************************************************************/
|
||||
public WormAuditQBitConfig withIncludeApp(boolean includeApp)
|
||||
{
|
||||
this.includeApp = includeApp;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the app name (the host-owned destination when includeApp is false).
|
||||
** Read the app name.
|
||||
*******************************************************************************/
|
||||
public String getAppName()
|
||||
{
|
||||
|
||||
@@ -214,10 +214,7 @@ public class WormAuditQBitProducer implements QBitMetaDataProducer<WormAuditQBit
|
||||
{
|
||||
qInstance.addScheduler(new QuartzSchedulerMetaData().withName(qBitConfig.getSchedulerName()));
|
||||
}
|
||||
if(qBitConfig.getIncludeApp())
|
||||
{
|
||||
output.add(WormAuditApp.app(qBitConfig, producedTables));
|
||||
}
|
||||
output.add(WormAuditApp.app(qBitConfig, producedTables));
|
||||
for(QWidgetMetaData widget : WormAuditApp.widgets())
|
||||
{
|
||||
output.add(widget);
|
||||
|
||||
@@ -115,7 +115,7 @@ public final class WormAuditHelp
|
||||
table.withHelpContent(SLOT, content(entry.getValue()));
|
||||
}
|
||||
}
|
||||
else if(config.getIncludeApp() && key.startsWith("app:"))
|
||||
else if(key.startsWith("app:"))
|
||||
{
|
||||
QAppMetaData app = qInstance.getApp(config.getAppName());
|
||||
if(app != null && (app.getHelpContent() == null || !app.getHelpContent().containsKey(SLOT)))
|
||||
|
||||
@@ -24,7 +24,6 @@ package com.kingsrook.qbits.wormaudit.processes;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.wormaudit.WormAuditQBitConfig;
|
||||
import com.kingsrook.qbits.wormaudit.chain.AuditStore;
|
||||
import com.kingsrook.qbits.wormaudit.model.WormAuditTables;
|
||||
@@ -44,11 +43,6 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
*******************************************************************************/
|
||||
public final class VerificationRuns
|
||||
{
|
||||
/** How many break reasons the alert summary spells out; the title carries the total. */
|
||||
private static final int ALERT_SUMMARY_REASONS = 3;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
@@ -85,15 +79,8 @@ public final class VerificationRuns
|
||||
}
|
||||
else
|
||||
{
|
||||
long problems = result.getBreakCount();
|
||||
String summary = result.getBreaks().stream().limit(ALERT_SUMMARY_REASONS).map(b -> b.reason()).collect(Collectors.joining("; "));
|
||||
if(problems > ALERT_SUMMARY_REASONS)
|
||||
{
|
||||
summary += "; and " + (problems - ALERT_SUMMARY_REASONS) + " more";
|
||||
}
|
||||
QAlerts.raise(new AlertSignal("wormaudit.chain.break").withDedupKey("chain")
|
||||
.withTitle("Audit chain verification found " + problems + (problems == 1 ? " problem" : " problems")).withSummary(summary)
|
||||
.withRecordRef(new AlertRecordRef(WormAuditTables.VERIFICATION, row.getValue("id"))));
|
||||
.withTitle(result.getBreaks().getFirst().reason()).withRecordRef(new AlertRecordRef(WormAuditTables.VERIFICATION, row.getValue("id"))));
|
||||
}
|
||||
}
|
||||
return (result);
|
||||
|
||||
@@ -142,8 +142,7 @@ class ProcessesAndAlertsTest
|
||||
assertThat(Sink.raised.getFirst().getDedupKey()).isEqualTo("chain");
|
||||
assertThat(Sink.raised.getFirst().getRecordRefs().getFirst().getTableName())
|
||||
.isEqualTo(WormAuditTables.VERIFICATION);
|
||||
assertThat(Sink.raised.getFirst().getTitle()).isEqualTo("Audit chain verification found 1 problem");
|
||||
assertThat(Sink.raised.getFirst().getSummary()).contains("Break at link 1");
|
||||
assertThat(Sink.raised.getFirst().getTitle()).contains("Break at link 1");
|
||||
var first = STORE.all(WormAuditTables.VERIFICATION).getFirst();
|
||||
assertThat(first.getValueLong("first_break_link_id")).isEqualTo(1);
|
||||
assertThat(first.getValueLong("first_break_audit_id")).isEqualTo(1);
|
||||
|
||||
@@ -278,29 +278,6 @@ class WormAuditHomeTest
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Embedded widgets retain direct authorization without a standalone app.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void embeddedWidgetsRequireReadPermission() throws Exception
|
||||
{
|
||||
var config = ChainVerificationTest.signed().withIncludeApp(false).withAppName("audit").withAppLabel("Audit");
|
||||
var instance = init(config);
|
||||
assertThat(instance.getApp("worm_audit")).isNull();
|
||||
assertThat(instance.getApp("audit")).isNull();
|
||||
var input = new RenderWidgetInput().withWidgetMetaData(instance.getWidget(WormAuditApp.HEALTH_CARD_WIDGET));
|
||||
QContext.getQSession().withPermissions("admin.hasAccess");
|
||||
assertThatThrownBy(() -> new WormAuditApp.HealthCardRenderer().render(input)).isInstanceOf(QPermissionDeniedException.class);
|
||||
assertThatThrownBy(() -> new WormAuditApp.HomeRenderer().render(input)).isInstanceOf(QPermissionDeniedException.class);
|
||||
QContext.getQSession().withPermissions(WormAuditTables.CHAIN + ".read");
|
||||
String card = ((RawHTML) new WormAuditApp.HealthCardRenderer().render(input).getWidgetData()).getHtml();
|
||||
assertThat(card).contains("href=\"/app/audit\"");
|
||||
assertThat(new WormAuditApp.HomeRenderer().render(input).getWidgetData()).isInstanceOf(RawHTML.class);
|
||||
QContext.setQSession(new QSession());
|
||||
assertThatThrownBy(() -> new WormAuditApp.HealthCardRenderer().render(input)).isInstanceOf(QPermissionDeniedException.class);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Exercise the real SQL anti-join without Docker, including its exact boundary.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -21,15 +21,10 @@
|
||||
package com.kingsrook.qbits.wormaudit;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.wormaudit.app.WormAuditApp;
|
||||
import com.kingsrook.qbits.wormaudit.model.WormAuditHelp;
|
||||
import com.kingsrook.qbits.wormaudit.ddl.WormAuditDdl;
|
||||
import com.kingsrook.qbits.wormaudit.model.WormAuditTables;
|
||||
import com.kingsrook.qbits.wormaudit.processes.WormAuditProcesses;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -48,7 +43,6 @@ class WormAuditQBitProducerTest
|
||||
{
|
||||
var config = new WormAuditQBitConfig();
|
||||
QInstance instance = WormAuditTestSupport.init(config);
|
||||
assertThat(config.getIncludeApp()).isTrue();
|
||||
assertThat(config.getRetentionYears()).isEqualTo(6);
|
||||
assertThat(config.getTableMetaDataCustomizer()).isNull();
|
||||
assertThat(config.getSigningKey()).isNull();
|
||||
@@ -166,36 +160,4 @@ class WormAuditQBitProducerTest
|
||||
() -> WormAuditDdl.resolveNames(new WormAuditQBitConfig(), new QInstance()))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Embedded production keeps the host app intact and registers all evidence metadata.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void embeddedMetadataPreservesHostApp() throws Exception
|
||||
{
|
||||
var config = new WormAuditQBitConfig().withBackendName("memory").withIncludeApp(false).withAppName("audit");
|
||||
var instance = WormAuditTestSupport.core(new QBackendMetaData().withName("memory").withBackendType(MemoryBackendModule.class));
|
||||
var host = new QAppMetaData().withName("audit").withLabel("Combined Audit").withWidgets(List.of("hostSummary"));
|
||||
instance.addApp(host);
|
||||
new WormAuditQBitProducer().withQBitConfig(config).produce(instance);
|
||||
assertThat(config.getIncludeApp()).isFalse();
|
||||
assertThat(instance.getApp("worm_audit")).isNull();
|
||||
assertThat(instance.getApp("audit")).isSameAs(host);
|
||||
assertThat(host.getLabel()).isEqualTo("Combined Audit");
|
||||
assertThat(host.getWidgets()).containsExactly("hostSummary");
|
||||
assertThat(host.getHelpContent()).isNullOrEmpty();
|
||||
assertThat(instance.getWidget(WormAuditApp.HEALTH_CARD_WIDGET)).isNotNull();
|
||||
assertThat(instance.getWidget(WormAuditApp.HOME_WIDGET)).isNotNull();
|
||||
for(String name : WormAuditTables.ALL)
|
||||
{
|
||||
assertThat(instance.getTable(name).getHelpContent()).containsKey(WormAuditHelp.SLOT);
|
||||
assertThat(instance.getTable(name).getPermissionRules().getPermissionBaseName()).isEqualTo(name);
|
||||
}
|
||||
assertThat(instance.getTable(WormAuditTables.CHAIN).getDisabledCapabilities()).contains(Capability.TABLE_UPDATE, Capability.TABLE_DELETE, Capability.TABLE_INSERT);
|
||||
assertThat(instance.getTable("audit").getDisabledCapabilities()).contains(Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
|
||||
assertThat(instance.getProcess(WormAuditProcesses.VERIFY)).isNotNull();
|
||||
assertThat(instance.getProcess(WormAuditProcesses.CHAIN).getSchedule().getRepeatSeconds()).isEqualTo(config.getChainIntervalSeconds());
|
||||
assertThat(instance.getProcess(WormAuditProcesses.DAILY).getSchedule().getCronExpression()).isEqualTo(config.getVerifyCron());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user