build: QQQ 4.1.0-RC.7; remove core stand-ins #15
No files matched your search
+7
-1
@@ -4,6 +4,12 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed — direct core field access and audit protection
|
||||
- Remove all #1172/#1182 stand-ins and reflective bridges; use core field-access, classified-value token, audit protection, wipe flag and shredded-event APIs directly.
|
||||
- Register the resolver, key source and audit protector through core supplemental metadata.
|
||||
- Run core field-access integration and retention proof tests unconditionally.
|
||||
- Pin QQQ to `4.1.0-RC.7`, which ships field access (#1172) and audit value protection (#1182); all core stand-ins and reflective bridges are removed.
|
||||
|
||||
### Changed — RC.6 migration
|
||||
- Pin QQQ to 4.1.0-RC.6; use released classification, client-channel and table-feature APIs directly.
|
||||
- Fold UI API code and tests into the main build and remove the profile and reflective registration.
|
||||
@@ -15,7 +21,7 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
|
||||
- Atomic wipe batches (fields, markers, key deletions, audits, access log and run row in one rollback-capable transaction), per-record and per-table legal holds, a Review/Result "Run wipe now" process and a daily scheduled run.
|
||||
- Per-record-field AES-GCM wrapped DEKs (AAD-bound) for crypto-shredding of audit tokens, `CHANGED_ONLY` audit treatment, and the audit-value protector.
|
||||
- App-home warning when known backup retention exceeds a rule's `age_days`; `withBackupRetentionDays` and `withArchiveSink` options; README retention section and the backups residual risk.
|
||||
- Tests: all actions, holds, rollback, Review/Result, schedule, permissions, wrap/unwrap, and (on the SECUREDATA snapshot) the section 15 canary and wipe-proof tests.
|
||||
- Tests: all actions, holds, rollback, Review/Result, schedule, permissions, wrap/unwrap, and the section 15 canary and wipe-proof tests.
|
||||
|
||||
### Fixed
|
||||
- The key source returned a DEK array it then zeroed; it now returns copies, with a regression test.
|
||||
|
||||
@@ -4,11 +4,11 @@ Phases (b), (c), (e) and (f) of [SECURED-DATA](https://github.com/QRun-IO/qqq/bl
|
||||
|
||||
## Compatibility
|
||||
|
||||
Java 21, Maven wrapper 3.9.11, **QQQ 4.1.0-RC.6**.
|
||||
Java 21, Maven wrapper 3.9.11, and **QQQ 4.1.0-RC.7** or later (it ships field access #1172 and audit value protection #1182).
|
||||
|
||||
RC.6 supplies core classifications, AI metadata, client channels, table-feature permissions and the UI API extension points. These are used directly; the UI API and its contract test are part of the main build. The installed middleware jar provides `UiApiContract`, but no `UiApiContractAssertions` facade, so the contract test calls that released API directly.
|
||||
Core classifications, AI metadata, client channels, table-feature permissions and UI API extension points are used directly. The UI API and its contract test are part of the main build; the contract test calls `UiApiContract` directly.
|
||||
|
||||
Field access (#1172) and audit-value protection (#1182) are not in RC.6. Field-access stand-ins and `CoreFieldAccessBridge` remain until the first QQQ RC that ships both #1172 and #1182 (expected RC.8). RC.6 **does not enforce field tiers**. Integration tests can use `4.1.0-SECUREDATA-SNAPSHOT`.
|
||||
Field access (#1172), classified-value tokens and audit-value protection (#1182) use core types directly. The producer registers the resolver, key source and audit protector through core supplemental metadata; QQQ core enforces field tiers.
|
||||
|
||||
## Install and register
|
||||
|
||||
@@ -16,7 +16,7 @@ Field access (#1172) and audit-value protection (#1182) are not in RC.6. Field-a
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-secured-data</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<version>0.1.0-RC.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
@@ -159,10 +159,9 @@ Configure before producing the QBit; runtime infrastructure and HMAC keys are ca
|
||||
|
||||
```powershell
|
||||
./mvnw.cmd -q -o clean verify
|
||||
./mvnw.cmd -q -o clean verify -Dqqq.snapshot.version=4.1.0-SECUREDATA-SNAPSHOT
|
||||
```
|
||||
|
||||
The direct suite uses memory and H2. `CoreFieldAccessBridgeIT` is included by Surefire and uses JUnit assumptions to skip unless the actual core resolver metadata is present. With the installed core snapshot it exercises Query/Get, policy tiers, masking, exact matches and outcomes, sorting denial, rate/entropy refusal and the assistant wall. Checkstyle and JaCoCo remain 70% instructions / 90% classes; stand-ins are not excluded.
|
||||
The suite uses memory and H2. `CoreFieldAccessIT` runs unconditionally through Surefire and exercises Query/Get, policy tiers, masking, exact matches and outcomes, sorting denial, rate/entropy refusal and the assistant wall. `RetentionProofTest` also runs unconditionally. Checkstyle and JaCoCo gates remain 70% instructions / 90% classes.
|
||||
|
||||
## Anomaly monitoring (phase e)
|
||||
|
||||
@@ -243,11 +242,11 @@ Each is read from a JVM property or the environment and is never logged. Losing
|
||||
|
||||
### Core hooks
|
||||
|
||||
Audit-value protection (QRun-IO/qqq#1182) and the wipe audit flag need core classes that are not in RC.6. On RC.6 the qbit uses marked stand-ins (`removeBy=RC.8`), writes value-free wipe audit details itself, and core does not tokenise ordinary audits. Run the build with `-Dqqq.snapshot.version=4.1.0-SECUREDATA-SNAPSHOT` to exercise the real hooks, including the section 15 canary and wipe-proof tests (`RetentionProofTest`), which skip themselves when the hooks are absent.
|
||||
Audit-value protection, wipe audit flags and shredded audit events use the core #1182 APIs directly. Core tokenises ordinary audits and writes value-free wipe details. The section 15 canary and wipe-proof tests (`RetentionProofTest`) are mandatory in the normal build.
|
||||
|
||||
## Remaining phases
|
||||
|
||||
RC.6 migration is complete; field-access and audit-token compatibility types remain until RC.8.
|
||||
Field-access and audit-token stand-ins and reflective bridges have been removed. The coordinator will replace the temporary snapshot pin with the first QQQ RC containing #1172 and #1182.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
<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>
|
||||
<!-- Released APIs; fieldaccess and audit protection remain RC.7 stand-ins. -->
|
||||
<qqq.snapshot.version>4.1.0-RC.6</qqq.snapshot.version>
|
||||
<revision>1.0.0-SNAPSHOT</revision>
|
||||
<!-- QQQ 4.1.0-RC.7 ships field access (#1172) and audit value protection (#1182) -->
|
||||
<qqq.snapshot.version>4.1.0-RC.7</qqq.snapshot.version>
|
||||
<revision>0.1.0-SNAPSHOT</revision>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jacoco.version>0.8.15</jacoco.version>
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueTokens;
|
||||
import com.kingsrook.qbits.secureddata.standin.DMLType;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.AuditAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
/*******************************************************************************
|
||||
** A single reflection boundary for #1182, failing closed for incompatible hooks.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
|
||||
public final class CoreAuditBridge
|
||||
{
|
||||
private static final String TOKENS = "com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.";
|
||||
private static final String AUDITS = "com.kingsrook.qqq.backend.core.actions.audits.";
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private CoreAuditBridge()
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Core hook availability (RC.6 has none).
|
||||
*******************************************************************************/
|
||||
public static boolean available()
|
||||
{
|
||||
try
|
||||
{
|
||||
Class.forName(TOKENS + "ClassifiedValueTokens");
|
||||
return (true);
|
||||
}
|
||||
catch(ClassNotFoundException expected)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Register exact #1182 contracts when present; no silent partial registration.
|
||||
*******************************************************************************/
|
||||
public static void register(QInstance instance, SecuredDataRuntime runtime)
|
||||
{
|
||||
if(!available())
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Class<?> contract = Class.forName(TOKENS + "ClassifiedValueKeySource");
|
||||
Object keyProxy = Proxy.newProxyInstance(contract.getClassLoader(), new Class<?>[] {contract}, (self, method, args) ->
|
||||
{
|
||||
if(method.getDeclaringClass() == Object.class)
|
||||
{
|
||||
return (objectMethod(self, method.getName(), args));
|
||||
}
|
||||
Optional<ClassifiedValueKeySource.KeyAndId> key = switch(method.getName())
|
||||
{
|
||||
case "instanceKey" -> runtime.keys().instanceKey();
|
||||
case "keyForRecordField" -> runtime.keys().keyForRecordField((String) args[0], (String) args[1], (Serializable) args[2], (Boolean) args[3]);
|
||||
default -> throw new UnsupportedOperationException(method.getName());
|
||||
};
|
||||
return (key.isEmpty() ? Optional.empty() : Optional.of(Class.forName(TOKENS + "ClassifiedValueKeySource$KeyAndId")
|
||||
.getConstructor(byte[].class, String.class).newInstance(key.get().key(), key.get().id())));
|
||||
});
|
||||
Class<?> metadata = Class.forName(TOKENS + "ClassifiedValueKeySourceMetaData");
|
||||
Object registry = metadata.getMethod("ofOrWithNew", QInstance.class).invoke(null, instance);
|
||||
metadata.getMethod("withKeySourceInstance", contract).invoke(registry, keyProxy);
|
||||
SecuredDataAuditProtector protector = new SecuredDataAuditProtector();
|
||||
Class<?> auditContract = Class.forName(AUDITS + "AuditValueProtectorInterface");
|
||||
Object auditProxy = Proxy.newProxyInstance(auditContract.getClassLoader(), new Class<?>[] {auditContract}, (self, method, args) ->
|
||||
{
|
||||
if(method.getDeclaringClass() == Object.class)
|
||||
{
|
||||
return (objectMethod(self, method.getName(), args));
|
||||
}
|
||||
if("classificationOf".equals(method.getName()))
|
||||
{
|
||||
return (protector.classificationOf((QTableMetaData) args[0], (QFieldMetaData) args[1]));
|
||||
}
|
||||
if(!"protect".equals(method.getName()))
|
||||
{
|
||||
throw (new UnsupportedOperationException(method.getName()));
|
||||
}
|
||||
var values = protector.protect((QTableMetaData) args[0], (QFieldMetaData) args[1], (Serializable) args[2], (Serializable) args[3], (Serializable) args[4], DMLType.valueOf(args[5].toString()));
|
||||
return (Class.forName(AUDITS + "ProtectedAuditValues").getConstructor(String.class, String.class, String.class)
|
||||
.newInstance(values.oldToken(), values.newToken(), values.message()));
|
||||
});
|
||||
metadata = Class.forName(AUDITS + "AuditValueProtectionMetaData");
|
||||
registry = metadata.getMethod("ofOrWithNew", QInstance.class).invoke(null, instance);
|
||||
metadata.getMethod("withProtectorInstance", auditContract).invoke(registry, auditProxy);
|
||||
}
|
||||
catch(ReflectiveOperationException e)
|
||||
{
|
||||
throw (new IllegalStateException("Unable to register core audit protection", e));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable object behavior for registered service proxies.
|
||||
*******************************************************************************/
|
||||
private static Object objectMethod(Object self, String name, Object[] args)
|
||||
{
|
||||
return (switch(name)
|
||||
{
|
||||
case "toString" -> "Secured-data audit bridge";
|
||||
case "hashCode" -> System.identityHashCode(self);
|
||||
case "equals" -> self == args[0];
|
||||
default -> throw new UnsupportedOperationException(name);
|
||||
});
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Delegate to the installed core helper, with contract-identical RC.6 fallback.
|
||||
*******************************************************************************/
|
||||
public static String token(QTableMetaData table, QFieldMetaData field, Serializable pk, Serializable value)
|
||||
{
|
||||
if(!available())
|
||||
{
|
||||
return (ClassifiedValueTokens.forRecordField(table, field, pk, value));
|
||||
}
|
||||
try
|
||||
{
|
||||
return ((String) Class.forName(TOKENS + "ClassifiedValueTokens").getMethod("forRecordField", QTableMetaData.class, QFieldMetaData.class, Serializable.class, Serializable.class)
|
||||
.invoke(null, table, field, pk, value));
|
||||
}
|
||||
catch(InvocationTargetException e)
|
||||
{
|
||||
throw (new IllegalStateException("Classified token creation failed"));
|
||||
}
|
||||
catch(ReflectiveOperationException e)
|
||||
{
|
||||
throw (new IllegalStateException("Incompatible core token API", e));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Real WipeAuditFlag on snapshot; RC.6 uses a value-free explicit audit below.
|
||||
*******************************************************************************/
|
||||
public static void flag(UpdateInput input, String ruleId)
|
||||
{
|
||||
if(!available())
|
||||
{
|
||||
input.setOmitDmlAudit(true);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
ActionFlag flag = (ActionFlag) Class.forName("com.kingsrook.qqq.backend.core.model.actions.tables.WipeAuditFlag").getConstructor(String.class).newInstance(ruleId);
|
||||
input.setFlags(Set.of(flag));
|
||||
}
|
||||
catch(ReflectiveOperationException e)
|
||||
{
|
||||
throw (new IllegalStateException("Incompatible core wipe API", e));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Append immutable value-free audit evidence in the batch transaction.
|
||||
*******************************************************************************/
|
||||
public static void detail(QTableMetaData table, Serializable pk, String field, String ruleId, boolean shredded, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
if(shredded && available())
|
||||
{
|
||||
try
|
||||
{
|
||||
Class.forName(AUDITS + "AuditShreddedEvent").getMethod("shreddedDetail", QTableMetaData.class, Serializable.class, QFieldMetaData.class, String.class, QBackendTransaction.class)
|
||||
.invoke(null, table, pk, table.getField(field), ruleId, transaction);
|
||||
return;
|
||||
}
|
||||
catch(ReflectiveOperationException e)
|
||||
{
|
||||
throw (new QException("Unable to append shredded audit detail"));
|
||||
}
|
||||
}
|
||||
String message = (shredded ? "Audit values shredded" : "Wiped") + " per policy";
|
||||
QRecord detail = new QRecord().withValue("fieldName", field).withValue("newValue", (shredded ? "shredded:v1:" : "wiped:v1:") + ruleId).withValue("message", message);
|
||||
AuditInput input = new AuditInput();
|
||||
input.setTransaction(transaction);
|
||||
AuditAction.appendToInput(input, table.getName(), pk, AuditAction.getRecordSecurityKeyValues(table,
|
||||
new QRecord().withValue(table.getPrimaryKeyField(), pk), Optional.empty()), message, List.of(detail));
|
||||
new AuditAction().execute(input);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessDecision;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessResolverInterface;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The only boundary between the temporary field-access API and unreleased core.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8)
|
||||
public final class CoreFieldAccessBridge
|
||||
{
|
||||
private static final String PACKAGE = "com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.";
|
||||
private static final QLogger LOG = QLogger.getLogger(CoreFieldAccessBridge.class);
|
||||
private static final AtomicBoolean WARNED = new AtomicBoolean();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private CoreFieldAccessBridge()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Register only when core can actually enforce field access.
|
||||
*******************************************************************************/
|
||||
public static boolean register(QInstance instance, FieldAccessResolverInterface resolver)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> metadata = Class.forName(PACKAGE + "FieldAccessResolverMetaData");
|
||||
Class<?> contract = Class.forName(PACKAGE + "FieldAccessResolverInterface");
|
||||
Object proxy = Proxy.newProxyInstance(contract.getClassLoader(), new Class<?>[] {contract}, (self, method, args) ->
|
||||
{
|
||||
return (switch(method.getName())
|
||||
{
|
||||
case "resolve" -> decision(resolver.resolve((QTableMetaData) args[0], (QFieldMetaData) args[1]));
|
||||
case "getAccessFingerprint" -> resolver.getAccessFingerprint();
|
||||
case "isRevealActive" -> resolver.isRevealActive((String) args[0], (String) args[1], (Serializable) args[2]);
|
||||
case "fingerprintSearchValue" -> resolver.fingerprintSearchValue((Serializable) args[0]);
|
||||
case "checkExactMatch" ->
|
||||
{
|
||||
resolver.checkExactMatch(event(args[0]));
|
||||
yield null;
|
||||
}
|
||||
case "onAccess" ->
|
||||
{
|
||||
resolver.onAccess(event(args[0]));
|
||||
yield null;
|
||||
}
|
||||
case "toString" -> "SecuredData core bridge";
|
||||
case "hashCode" -> System.identityHashCode(self);
|
||||
case "equals" -> self == args[0];
|
||||
default -> throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
});
|
||||
Object registry = metadata.getMethod("ofOrWithNew", QInstance.class).invoke(null, instance);
|
||||
metadata.getMethod("withResolverInstance", contract).invoke(registry, proxy);
|
||||
return (true);
|
||||
}
|
||||
catch(ClassNotFoundException e)
|
||||
{
|
||||
if(WARNED.compareAndSet(false, true))
|
||||
{
|
||||
LOG.info("Secured-data field enforcement needs QQQ RC.6+; policy and access-log services are available.");
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
catch(ReflectiveOperationException e)
|
||||
{
|
||||
throw (new IllegalStateException("Core field-access API is incompatible", unwrap(e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Construct the core record by its declared component types and enum names.
|
||||
*******************************************************************************/
|
||||
private static Object decision(FieldAccessDecision decision) throws ReflectiveOperationException
|
||||
{
|
||||
Class<?> type = Class.forName(PACKAGE + "FieldAccessDecision");
|
||||
Class<?> tier = type.getRecordComponents()[0].getType();
|
||||
Class<?> mask = type.getRecordComponents()[1].getType();
|
||||
return (type.getConstructor(tier, mask, String.class, boolean.class).newInstance(
|
||||
enumValue(tier, decision.tier().name()), decision.maskStyle() == null ? null : enumValue(mask, decision.maskStyle().name()),
|
||||
decision.reason(), decision.exactMatchAllowed()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert enum types without sharing class identity across the bridge.
|
||||
*******************************************************************************/
|
||||
private static Object enumValue(Class<?> type, String name) throws ReflectiveOperationException
|
||||
{
|
||||
return (type.getMethod("valueOf", String.class).invoke(null, name));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Events carry identifiers and keyed fingerprints, never search literals.
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static FieldAccessEvent event(Object source) throws ReflectiveOperationException
|
||||
{
|
||||
Class<?> type = source.getClass();
|
||||
Object outcome = type.getMethod("getOutcome").invoke(source);
|
||||
return (new FieldAccessEvent()
|
||||
.withType(FieldAccessEventType.valueOf(type.getMethod("getType").invoke(source).toString()))
|
||||
.withTableName((String) type.getMethod("getTableName").invoke(source))
|
||||
.withFieldNames((List<String>) type.getMethod("getFieldNames").invoke(source))
|
||||
.withPrimaryKeys((List<Serializable>) type.getMethod("getPrimaryKeys").invoke(source))
|
||||
.withRecordCount((Integer) type.getMethod("getRecordCount").invoke(source))
|
||||
.withActionName((String) type.getMethod("getActionName").invoke(source))
|
||||
.withOutcome(outcome == null ? null : FieldAccessOutcome.valueOf(outcome.toString()))
|
||||
.withSearchValueFingerprints((List<String>) type.getMethod("getSearchValueFingerprints").invoke(source)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Preserve the startup failure, including duplicate resolver registration.
|
||||
*******************************************************************************/
|
||||
private static Throwable unwrap(ReflectiveOperationException exception)
|
||||
{
|
||||
return (exception instanceof InvocationTargetException invocation ? invocation.getCause() : exception);
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,11 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
|
||||
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
@@ -60,7 +60,6 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Bounded asynchronous value-free audit, with persisted and local enumeration accounting.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -22,10 +22,11 @@ package com.kingsrook.qbits.secureddata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qbits.secureddata.retention.RetentionRules;
|
||||
import com.kingsrook.qbits.secureddata.standin.AuditValueProtectorInterface;
|
||||
import com.kingsrook.qbits.secureddata.standin.DMLType;
|
||||
import com.kingsrook.qbits.secureddata.standin.ProtectedAuditValues;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.AuditValueProtectorInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction.DMLType;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.ProtectedAuditValues;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
@@ -56,7 +57,7 @@ public final class SecuredDataAuditProtector implements AuditValueProtectorInter
|
||||
{
|
||||
boolean changed = RetentionRules.changedOnly(table.getName(), field.getName());
|
||||
String message = (oldValue == null ? "Set " : newValue == null ? "Removed " : "Changed ") + field.getLabel();
|
||||
return (new ProtectedAuditValues(oldValue == null ? null : changed ? "changed:v1" : CoreAuditBridge.token(table, field, pk, oldValue),
|
||||
newValue == null ? null : changed ? "changed:v1" : CoreAuditBridge.token(table, field, pk, newValue), message));
|
||||
return (new ProtectedAuditValues(oldValue == null ? null : changed ? "changed:v1" : ClassifiedValueTokens.forRecordField(table, field, pk, oldValue),
|
||||
newValue == null ? null : changed ? "changed:v1" : ClassifiedValueTokens.forRecordField(table, field, pk, newValue), message));
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,10 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.retention.RetentionRules;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueKeySource;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@@ -280,7 +281,7 @@ public final class SecuredDataKeySource implements ClassifiedValueKeySource, Aut
|
||||
instanceOnly.set(true);
|
||||
try
|
||||
{
|
||||
return (CoreAuditBridge.token(table, field, pk, value));
|
||||
return (ClassifiedValueTokens.forRecordField(table, field, pk, value));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -29,13 +29,12 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Configuration for policy resolution and bounded asynchronous access logging.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -29,7 +29,10 @@ import com.kingsrook.qbits.secureddata.metadata.SecuredDataTableCustomizers;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.AuditValueProtectionMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueKeySourceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverMetaData;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
@@ -39,7 +42,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Entry point for secured-data policy, classification and access logging.
|
||||
*******************************************************************************/
|
||||
@@ -47,7 +49,7 @@ public class SecuredDataQBitProducer implements QBitMetaDataProducer<SecuredData
|
||||
{
|
||||
public static final String GROUP_ID = "com.kingsrook.qbits";
|
||||
public static final String ARTIFACT_ID = "qbit-secured-data";
|
||||
public static final String VERSION = "1.0.0";
|
||||
public static final String VERSION = "0.1.0";
|
||||
|
||||
private SecuredDataQBitConfig qBitConfig;
|
||||
private String namespace;
|
||||
@@ -171,8 +173,9 @@ public class SecuredDataQBitProducer implements QBitMetaDataProducer<SecuredData
|
||||
runtime.addSelfToInstance(qInstance);
|
||||
try
|
||||
{
|
||||
CoreFieldAccessBridge.register(qInstance, runtime.resolver());
|
||||
CoreAuditBridge.register(qInstance, runtime);
|
||||
FieldAccessResolverMetaData.ofOrWithNew(qInstance).withResolverInstance(runtime.resolver());
|
||||
ClassifiedValueKeySourceMetaData.ofOrWithNew(qInstance).withKeySourceInstance(runtime.keys());
|
||||
AuditValueProtectionMetaData.ofOrWithNew(qInstance).withProtectorInstance(new SecuredDataAuditProtector());
|
||||
}
|
||||
catch(RuntimeException e)
|
||||
{
|
||||
|
||||
@@ -36,14 +36,14 @@ import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.permissions.SecuredDataPermissions;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessDecision;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessResolverInterface;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldMaskStyle;
|
||||
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessDecision;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldMaskStyle;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
@@ -57,7 +57,6 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Seven-step policy resolution with instance-versioned, bounded per-user state.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -23,15 +23,14 @@ package com.kingsrook.qbits.secureddata.metadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldMaskStyle;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldMaskStyle;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** String-valued possible-value sources shared by UI metadata and the DDL.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -35,19 +35,20 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.secureddata.CoreAuditBridge;
|
||||
import com.kingsrook.qbits.secureddata.SecuredDataKeySource;
|
||||
import com.kingsrook.qbits.secureddata.SecuredDataRuntime;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.support.ActivityLog;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.AuditShreddedEvent;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.WipeAuditFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
@@ -292,7 +293,7 @@ public final class WipeService
|
||||
{
|
||||
throw (new QException("Audit key deletion failed."));
|
||||
}
|
||||
CoreAuditBridge.detail(table, pk, field, rule.getValueString("id"), true, transaction);
|
||||
AuditShreddedEvent.shreddedDetail(table, pk, table.getField(field), rule.getValueString("id"), transaction);
|
||||
}
|
||||
markers.add(new QRecord().withValue("table_name", table.getName()).withValue("record_id", String.valueOf(pk)).withValue("field_name", field)
|
||||
.withValue("rule_id", rule.getValue("id")).withValue("action", action).withValue("wiped_at", started));
|
||||
@@ -385,15 +386,9 @@ public final class WipeService
|
||||
patch.setValue(field, value);
|
||||
}
|
||||
UpdateInput input = new UpdateInput(table.getName()).withRecord(patch).withTransaction(transaction);
|
||||
CoreAuditBridge.flag(input, rule.getValueString("id"));
|
||||
input.setFlags(Set.of(new WipeAuditFlag(rule.getValueString("id"))));
|
||||
check(new UpdateAction().execute(input).getRecords());
|
||||
if(!CoreAuditBridge.available())
|
||||
{
|
||||
for(String field : fields)
|
||||
{
|
||||
CoreAuditBridge.detail(table, pk, field, rule.getValueString("id"), false, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
@@ -30,13 +30,13 @@ import java.util.Objects;
|
||||
import com.kingsrook.qbits.secureddata.SecuredDataResolver;
|
||||
import com.kingsrook.qbits.secureddata.SecuredDataRuntime;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qbits.secureddata.support.ActivityLog;
|
||||
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.CorePermissionEnforcer;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
|
||||
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qbits.secureddata.standin.DMLType;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.AiDataClassifications;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Application hook for audit tokens and effective classification. Never return plain values.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
|
||||
public interface AuditValueProtectorInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Protect the values in the current action's transaction before audit insertion.
|
||||
*******************************************************************************/
|
||||
ProtectedAuditValues protect(QTableMetaData table, QFieldMetaData field, Serializable primaryKey, Serializable oldValue, Serializable newValue, DMLType dmlType);
|
||||
|
||||
/*******************************************************************************
|
||||
** Override to use an application-owned classification source.
|
||||
*******************************************************************************/
|
||||
default DataClassification classificationOf(QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
return (AiDataClassifications.ofField(table, field.getName()));
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Supplies instance keys and optional per-record, per-field shreddable keys.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
|
||||
public interface ClassifiedValueKeySource
|
||||
{
|
||||
/*******************************************************************************
|
||||
** The active instance key, if configured.
|
||||
*******************************************************************************/
|
||||
Optional<KeyAndId> instanceKey();
|
||||
|
||||
/*******************************************************************************
|
||||
** Verification uses create=false and must never recreate a destroyed key.
|
||||
*******************************************************************************/
|
||||
Optional<KeyAndId> keyForRecordField(String table, String field, Serializable pk, boolean create);
|
||||
|
||||
/*******************************************************************************
|
||||
** Key bytes and the public identifier stored in a token.
|
||||
*******************************************************************************/
|
||||
record KeyAndId(byte[] key, String id)
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Own the key bytes; identifiers must keep tokens ASCII and under 200 characters.
|
||||
*******************************************************************************/
|
||||
public KeyAndId
|
||||
{
|
||||
if(key == null || key.length == 0 || id == null || !id.matches("[A-Za-z0-9._-]{1,140}"))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid classified-value key or identifier");
|
||||
}
|
||||
key = key.clone();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Do not expose mutable key material.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public byte[] key()
|
||||
{
|
||||
return (key.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource.KeyAndId;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Shared, versioned audit/email value tokens. The MAC input is UTF-8.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
|
||||
public final class ClassifiedValueTokens
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Static utility.
|
||||
*******************************************************************************/
|
||||
private ClassifiedValueTokens()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Bind a value to the table, field and primary key, preferring a shreddable key.
|
||||
*******************************************************************************/
|
||||
public static String forRecordField(QTableMetaData table, QFieldMetaData field, Serializable pk, Serializable value)
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ClassifiedValueKeySource source = source();
|
||||
Optional<KeyAndId> key = source.keyForRecordField(table.getName(), field.getName(), pk, true);
|
||||
return key.isPresent() ? token("shred", key.get(), binding(table, field, pk), value)
|
||||
: instanceToken(source, binding(table, field, pk), value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Non-record bindings must start with a nonempty domain.
|
||||
*******************************************************************************/
|
||||
public static String forContext(List<String> bindingParts, Serializable value)
|
||||
{
|
||||
validateDomain(bindingParts);
|
||||
return value == null ? null : instanceToken(source(), bindingParts, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** No derivative of the original value.
|
||||
*******************************************************************************/
|
||||
public static String changed()
|
||||
{
|
||||
return "changed:v1";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A key-destruction event, never a derivative of the value.
|
||||
*******************************************************************************/
|
||||
public static String shreddedToken(String ruleId)
|
||||
{
|
||||
if(ruleId == null || !ruleId.matches("[A-Za-z0-9._-]{1,180}"))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid shred rule identifier");
|
||||
}
|
||||
return "shredded:v1:" + ruleId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Verify only keyed tokens, using existing keys and constant-time MAC comparison.
|
||||
*******************************************************************************/
|
||||
public static boolean verifyRecordField(QTableMetaData table, QFieldMetaData field, Serializable pk, Serializable candidate, String token)
|
||||
{
|
||||
if(candidate == null || token == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ClassifiedValueKeySource source = source();
|
||||
if(token.startsWith("shred:v1:"))
|
||||
{
|
||||
return verify(source.keyForRecordField(table.getName(), field.getName(), pk, false), "shred", binding(table, field, pk), candidate, token);
|
||||
}
|
||||
return verify(source.instanceKey(), "hmac", binding(table, field, pk), candidate, token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Verify a non-record token without creating a key.
|
||||
*******************************************************************************/
|
||||
public static boolean verifyContext(List<String> bindingParts, Serializable candidate, String token)
|
||||
{
|
||||
validateDomain(bindingParts);
|
||||
return candidate != null && token != null && verify(source().instanceKey(), "hmac", bindingParts, candidate, token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Canonical ValueUtils strings, with scale-independent plain decimal notation.
|
||||
*******************************************************************************/
|
||||
public static String canonical(Serializable value)
|
||||
{
|
||||
return value instanceof BigDecimal decimal ? decimal.stripTrailingZeros().toPlainString() : ValueUtils.getValueAsString(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate the context even for absent values.
|
||||
*******************************************************************************/
|
||||
private static void validateDomain(List<String> parts)
|
||||
{
|
||||
if(parts == null || parts.isEmpty() || parts.get(0) == null || parts.get(0).isBlank() || parts.stream().anyMatch(p -> p == null || p.indexOf('\u001F') >= 0))
|
||||
{
|
||||
throw new IllegalArgumentException("A nonempty domain and unambiguous binding parts are required");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The agreed record binding does not prepend an additional domain.
|
||||
*******************************************************************************/
|
||||
private static List<String> binding(QTableMetaData table, QFieldMetaData field, Serializable pk)
|
||||
{
|
||||
return List.of(table.getName(), field.getName(), String.valueOf(pk));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Emit a keyed token or a value-free fallback.
|
||||
*******************************************************************************/
|
||||
private static String instanceToken(ClassifiedValueKeySource source, List<String> parts, Serializable value)
|
||||
{
|
||||
return source.instanceKey().map(key -> token("hmac", key, parts, value)).orElseGet(ClassifiedValueTokens::changed);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Compare full tokens, including the key identifier and version.
|
||||
*******************************************************************************/
|
||||
private static boolean verify(Optional<KeyAndId> key, String kind, List<String> parts, Serializable value, String supplied)
|
||||
{
|
||||
return key.map(k -> MessageDigest.isEqual(token(kind, k, parts, value).getBytes(StandardCharsets.UTF_8), supplied.getBytes(StandardCharsets.UTF_8))).orElse(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Encode the HMAC with URL-safe base64 and no padding.
|
||||
*******************************************************************************/
|
||||
private static String token(String kind, KeyAndId key, List<String> parts, Serializable value)
|
||||
{
|
||||
try
|
||||
{
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key.key(), "HmacSHA256"));
|
||||
byte[] digest = mac.doFinal((String.join("\u001F", parts) + "\u001F" + canonical(value)).getBytes(StandardCharsets.UTF_8));
|
||||
return kind + ":v1:" + key.id() + ":" + Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
}
|
||||
catch(GeneralSecurityException e)
|
||||
{
|
||||
throw new IllegalStateException("Unable to compute classified-value token", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Use instance metadata, falling back to environment/property configuration.
|
||||
*******************************************************************************/
|
||||
private static ClassifiedValueKeySource source()
|
||||
{
|
||||
return (com.kingsrook.qbits.secureddata.SecuredDataRuntime.current().keys());
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
/*******************************************************************************
|
||||
** RC.6's nested DMLType is package-private; #1182 makes it public.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
|
||||
public enum DMLType
|
||||
{
|
||||
INSERT, UPDATE, DELETE, OTHER
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A resolver decision; the reason must never contain a field value.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessDecision; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public record FieldAccessDecision(FieldAccessTier tier, FieldMaskStyle maskStyle, String reason, boolean exactMatchAllowed)
|
||||
{
|
||||
public static final FieldAccessDecision FULL = new FieldAccessDecision(FieldAccessTier.FULL, null, null);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Compatibility constructor: exact-match searches are allowed by default.
|
||||
*******************************************************************************/
|
||||
public FieldAccessDecision(FieldAccessTier tier, FieldMaskStyle maskStyle, String reason)
|
||||
{
|
||||
this(tier, maskStyle, reason, true);
|
||||
}
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Value-free audit notification. Primary key samples contain at most 50 keys.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public class FieldAccessEvent
|
||||
{
|
||||
private FieldAccessEventType type;
|
||||
private String tableName;
|
||||
private List<String> fieldNames;
|
||||
private List<Serializable> primaryKeys;
|
||||
private Integer recordCount;
|
||||
private String actionName;
|
||||
private FieldAccessOutcome outcome;
|
||||
private List<String> searchValueFingerprints;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public FieldAccessEventType getType()
|
||||
{
|
||||
return (this.type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
*******************************************************************************/
|
||||
public void setType(FieldAccessEventType type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withType(FieldAccessEventType type)
|
||||
{
|
||||
setType(type);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return (this.tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withTableName(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldNames
|
||||
*******************************************************************************/
|
||||
public List<String> getFieldNames()
|
||||
{
|
||||
return (this.fieldNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fieldNames
|
||||
*******************************************************************************/
|
||||
public void setFieldNames(List<String> fieldNames)
|
||||
{
|
||||
this.fieldNames = fieldNames == null ? null : new ArrayList<>(fieldNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fieldNames
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withFieldNames(List<String> fieldNames)
|
||||
{
|
||||
setFieldNames(fieldNames);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for primaryKeys
|
||||
*******************************************************************************/
|
||||
public List<Serializable> getPrimaryKeys()
|
||||
{
|
||||
return (this.primaryKeys == null ? null : Collections.unmodifiableList(this.primaryKeys));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for primaryKeys
|
||||
*******************************************************************************/
|
||||
public void setPrimaryKeys(List<Serializable> primaryKeys)
|
||||
{
|
||||
this.primaryKeys = primaryKeys == null ? null : new ArrayList<>(primaryKeys.subList(0, Math.min(50, primaryKeys.size())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for primaryKeys
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withPrimaryKeys(List<Serializable> primaryKeys)
|
||||
{
|
||||
setPrimaryKeys(primaryKeys);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordCount
|
||||
*******************************************************************************/
|
||||
public Integer getRecordCount()
|
||||
{
|
||||
return (this.recordCount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordCount
|
||||
*******************************************************************************/
|
||||
public void setRecordCount(Integer recordCount)
|
||||
{
|
||||
this.recordCount = recordCount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordCount
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withRecordCount(Integer recordCount)
|
||||
{
|
||||
setRecordCount(recordCount);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for actionName
|
||||
*******************************************************************************/
|
||||
public String getActionName()
|
||||
{
|
||||
return (this.actionName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for actionName
|
||||
*******************************************************************************/
|
||||
public void setActionName(String actionName)
|
||||
{
|
||||
this.actionName = actionName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for actionName
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withActionName(String actionName)
|
||||
{
|
||||
setActionName(actionName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for outcome
|
||||
*******************************************************************************/
|
||||
public FieldAccessOutcome getOutcome()
|
||||
{
|
||||
return (outcome);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for outcome
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withOutcome(FieldAccessOutcome outcome)
|
||||
{
|
||||
this.outcome = outcome;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for searchValueFingerprints
|
||||
*******************************************************************************/
|
||||
public List<String> getSearchValueFingerprints()
|
||||
{
|
||||
return (searchValueFingerprints);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for searchValueFingerprints
|
||||
*******************************************************************************/
|
||||
public FieldAccessEvent withSearchValueFingerprints(List<String> searchValueFingerprints)
|
||||
{
|
||||
this.searchValueFingerprints = searchValueFingerprints == null ? null : List.copyOf(searchValueFingerprints);
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Kinds of field access reported to the resolver.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public enum FieldAccessEventType
|
||||
{
|
||||
VIEW, EXACT_MATCH_FILTER, DENIED
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Outcome of an exact-match search.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public enum FieldAccessOutcome
|
||||
{
|
||||
MATCH, NO_MATCH, REFUSED
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Session-aware policy, reveal grants and value-free access notifications.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverInterface; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public interface FieldAccessResolverInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Resolve using the current QContext session.
|
||||
*******************************************************************************/
|
||||
FieldAccessDecision resolve(QTableMetaData table, QFieldMetaData field);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether this record and field have an active reveal grant.
|
||||
*******************************************************************************/
|
||||
default boolean isRevealActive(String tableName, String fieldName, Serializable primaryKey)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable identity of effective access, excluding reveal grants.
|
||||
*******************************************************************************/
|
||||
default String getAccessFingerprint()
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Receive an audit notification; failures never prevent enforcement.
|
||||
*******************************************************************************/
|
||||
default void onAccess(FieldAccessEvent event)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Refuse an exact-match search before execution; the exception message is user-facing.
|
||||
*******************************************************************************/
|
||||
default void checkExactMatch(FieldAccessEvent event) throws QPermissionDeniedException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** One-way fingerprint of a search literal. Raw values never enter access events.
|
||||
*******************************************************************************/
|
||||
default String fingerprintSearchValue(Serializable value)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Effective field access, ordered from least to most restrictive.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public enum FieldAccessTier
|
||||
{
|
||||
FULL, REVEALABLE, MASKED, HIDDEN;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** canReadValue
|
||||
*******************************************************************************/
|
||||
public boolean canReadValue()
|
||||
{
|
||||
return (this == FULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** canSort
|
||||
*******************************************************************************/
|
||||
public boolean canSort()
|
||||
{
|
||||
return (this == FULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** canGroupOrAggregate
|
||||
*******************************************************************************/
|
||||
public boolean canGroupOrAggregate()
|
||||
{
|
||||
return (this == FULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** canQuickSearch
|
||||
*******************************************************************************/
|
||||
public boolean canQuickSearch()
|
||||
{
|
||||
return (this == FULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** canWrite
|
||||
*******************************************************************************/
|
||||
public boolean canWrite()
|
||||
{
|
||||
return (this == FULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether the operator is available for this tier.
|
||||
*******************************************************************************/
|
||||
public boolean canFilter(QCriteriaOperator operator)
|
||||
{
|
||||
return (this == FULL || (this == REVEALABLE && (operator == QCriteriaOperator.EQUALS || operator == QCriteriaOperator.IN)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether the field may be described to the caller.
|
||||
*******************************************************************************/
|
||||
public boolean isInMetaData()
|
||||
{
|
||||
return (this != HIDDEN);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Combine tiers; null means FULL.
|
||||
*******************************************************************************/
|
||||
public FieldAccessTier mostRestrictive(FieldAccessTier other)
|
||||
{
|
||||
FieldAccessTier candidate = other == null ? FULL : other;
|
||||
return (ordinal() > candidate.ordinal() ? this : candidate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Combine tiers; null means FULL.
|
||||
*******************************************************************************/
|
||||
public FieldAccessTier leastRestrictive(FieldAccessTier other)
|
||||
{
|
||||
FieldAccessTier candidate = other == null ? FULL : other;
|
||||
return (ordinal() < candidate.ordinal() ? this : candidate);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Presentation rules for a restricted value.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldMaskStyle; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
|
||||
public enum FieldMaskStyle
|
||||
{
|
||||
AUTO, EMAIL, LAST_4, FIRST_LAST, FULL
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.secureddata.standin;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Tokens and a value-free message supplied before an audit is inserted.
|
||||
*******************************************************************************/
|
||||
// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
|
||||
public record ProtectedAuditValues(String oldToken, String newToken, String message)
|
||||
{
|
||||
}
|
||||
+2
-25
@@ -35,8 +35,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.channelUser;
|
||||
@@ -55,34 +53,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Core field access bridge it.
|
||||
** Core field access integration tests.
|
||||
*******************************************************************************/
|
||||
class CoreFieldAccessBridgeIT
|
||||
class CoreFieldAccessIT
|
||||
{
|
||||
static final String EMAIL = "CANARY-EMAIL-918273@example.invalid";
|
||||
static final String SSN = "CANARY-PHI-192837465";
|
||||
|
||||
/*******************************************************************************
|
||||
** Require core.
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void requireCore()
|
||||
{
|
||||
boolean available;
|
||||
try
|
||||
{
|
||||
Class.forName("com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverMetaData");
|
||||
available = true;
|
||||
}
|
||||
catch(ClassNotFoundException expected)
|
||||
{
|
||||
available = false;
|
||||
}
|
||||
Assumptions.assumeTrue(available, "QQQ field-access core (#1172) is not on this classpath");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Cleanup.
|
||||
*******************************************************************************/
|
||||
@@ -35,9 +35,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.retention.WipeService;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource.KeyAndId;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueTokens;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueKeySource.KeyAndId;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@@ -56,8 +56,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qbits.secureddata.RetentionTestSupport.rows;
|
||||
import static com.kingsrook.qbits.secureddata.RetentionTestSupport.rule;
|
||||
@@ -67,7 +65,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
/*******************************************************************************
|
||||
** SECURED-DATA section 15 proofs: a planted canary never leaves the host table,
|
||||
** and shredded audit tokens cannot be brute-forced once the data key is gone.
|
||||
** Both need the core audit-protection hooks (#1182), so RC.6 skips them.
|
||||
*******************************************************************************/
|
||||
class RetentionProofTest
|
||||
{
|
||||
@@ -76,17 +73,6 @@ class RetentionProofTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Skip unless core supplies the audit hooks.
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void requireCoreHooks()
|
||||
{
|
||||
Assumptions.assumeTrue(CoreAuditBridge.available(), "QQQ core audit-protection hooks (#1182) are not on this classpath");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Cleanup.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -20,23 +20,23 @@
|
||||
|
||||
package com.kingsrook.qbits.secureddata;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Base64;
|
||||
import java.util.Set;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.retention.RetentionRules;
|
||||
import com.kingsrook.qbits.secureddata.retention.RetentionWidgets;
|
||||
import com.kingsrook.qbits.secureddata.retention.WipeService;
|
||||
import com.kingsrook.qbits.secureddata.retention.WipeProcesses;
|
||||
import com.kingsrook.qbits.secureddata.standin.DMLType;
|
||||
import com.kingsrook.qbits.secureddata.standin.ClassifiedValueTokens;
|
||||
import com.kingsrook.qbits.secureddata.retention.WipeService;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction.DMLType;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
@@ -50,8 +50,8 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static com.kingsrook.qbits.secureddata.RetentionTestSupport.*;
|
||||
|
||||
@@ -31,9 +31,9 @@ import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.processes.BreakGlassProcesses;
|
||||
import com.kingsrook.qbits.secureddata.reveal.RevealCappedException;
|
||||
import com.kingsrook.qbits.secureddata.reveal.RevealService;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertResolution;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSeverity;
|
||||
|
||||
@@ -33,9 +33,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.IntStream;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertResolution;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSinkInterface;
|
||||
@@ -49,7 +49,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.CLOCK;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.NOW;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.channelUser;
|
||||
|
||||
@@ -25,16 +25,16 @@ import java.util.List;
|
||||
import com.kingsrook.qbits.secureddata.breakglass.BreakGlassService;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qbits.secureddata.reveal.RevealService;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
|
||||
import com.kingsrook.qbits.secureddata.support.ActivityLog;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.*;
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaDat
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.CLOCK;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.RUNTIMES;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.base;
|
||||
@@ -97,8 +96,8 @@ class SecuredDataDdlTest
|
||||
classification(null, "PII");
|
||||
policy("cs_admin", "PII", "REVEALABLE");
|
||||
assertThat(SecuredDataRuntime.current().resolver().resolve(instance.getTable("person"), instance.getTable("person").getField("email")).tier().name()).isEqualTo("REVEALABLE");
|
||||
SecuredDataRuntime.current().logger().onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qbits.secureddata.standin.FieldAccessEventType.EXACT_MATCH_FILTER,
|
||||
com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome.MATCH, "hmac"));
|
||||
SecuredDataRuntime.current().logger().onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType.EXACT_MATCH_FILTER,
|
||||
com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome.MATCH, "hmac"));
|
||||
SecuredDataRuntime.current().logger().flush();
|
||||
assertThat(logs()).extracting(row -> row.getValueString("event_type")).containsExactly("CLASSIFICATION_CHANGE", "POLICY_CHANGE", "EXACT_MATCH_FILTER");
|
||||
// Equal timestamps must be counted by SQL as well as the memory backend.
|
||||
|
||||
@@ -88,8 +88,8 @@ class SecuredDataHomeTest
|
||||
delete(SecuredDataTables.POLICY, policy);
|
||||
classification("email", "PII");
|
||||
var logger = SecuredDataRuntime.current().logger();
|
||||
logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qbits.secureddata.standin.FieldAccessEventType.VIEW, null, null));
|
||||
logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qbits.secureddata.standin.FieldAccessEventType.VIEW, null, null));
|
||||
logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType.VIEW, null, null));
|
||||
logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType.VIEW, null, null));
|
||||
var data = SecuredDataHomeData.load();
|
||||
assertThat(data.getKpis()).containsEntry("Reveals today", 15L).containsEntry("Active reveal grants", 1L)
|
||||
.containsEntry("Active break-glass sessions", 1L).containsEntry("Anomaly flags (7 days)", 1L)
|
||||
|
||||
@@ -26,11 +26,9 @@ import java.util.Map;
|
||||
import com.kingsrook.qbits.secureddata.metadata.FieldsOfTablePossibleValueSource;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataEnums;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.ClientChannelsMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.TableFeaturePermissionsMetaData;
|
||||
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
@@ -40,11 +38,12 @@ import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSo
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.ClientChannelsMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.TableFeaturePermissionsMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.NOW;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.RUNTIMES;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.base;
|
||||
@@ -89,7 +88,7 @@ class SecuredDataQBitProducerTest
|
||||
{
|
||||
QInstance instance = setup();
|
||||
assertThat(instance.getTables().keySet()).containsAll(SecuredDataTables.ALL);
|
||||
assertThat(instance.getQBits().get("com.kingsrook.qbits:qbit-secured-data").getVersion()).isEqualTo("1.0.0");
|
||||
assertThat(instance.getQBits().get("com.kingsrook.qbits:qbit-secured-data").getVersion()).isEqualTo("0.1.0");
|
||||
for(String name : SecuredDataTables.ALL)
|
||||
{
|
||||
QTableMetaData table = instance.getTable(name);
|
||||
|
||||
@@ -23,12 +23,12 @@ package com.kingsrook.qbits.secureddata;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
|
||||
import com.kingsrook.qbits.secureddata.standin.FieldMaskStyle;
|
||||
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
|
||||
import com.kingsrook.qbits.secureddata.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldMaskStyle;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@@ -39,7 +39,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.CLOCK;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.NOW;
|
||||
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.channelUser;
|
||||
|
||||
Reference in new issue
Block a user