fix(enterprise): let the admin read and manage collaboration Custom Fields
The Custom Fields table (collaboration_field_definition) answered 403 "This needs collaboration.view." to the all-access admin. Its permission rules are NOT_PROTECTED with qbit-collaboration's FieldSecurity checker, which tests collaboration.view / collaboration.admin (and collaboration.fields.manage for writes and processes) by literal name, so PermissionsHelper.getAllAvailablePermissionNames never lists them; the same pattern as the jobs and assistant names the profile already grants. The enterprise session now also holds collaboration.view, .admin, .fields.manage and .tags.manage. The grants moved into DemoUsers.grantEnterpriseAccess so EnterpriseAccessTest can run the real FieldSecurity checker against them: refused without, read and insert allowed with. GuardedColumnStatsStep gets a TODO to delete it once QRun-IO/qqq#1367 lands in core. Fixes #47 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
8015b8b090
commit
39c28a4e00
3 files changed
+90
-22
No files matched your search
@@ -88,32 +88,47 @@ public class DemoUsers implements QAuthenticationModuleCustomizerInterface
|
||||
}
|
||||
if(EnterpriseFeatures.isEnabled())
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// enterprise test server: every session is an all-access admin, so //
|
||||
// the terminal clients can reach every qbit surface //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
session.withPermissions(PermissionsHelper.getAllAvailablePermissionNames(instance));
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// permissions some modules check by literal name, outside the //
|
||||
// table/process permission rules (QRun-IO/qqq#1362) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
session.withPermissions("jobs_job.read", "jobs_admin", "data_analysis.admin", "assistant.hasAccess", "assistant.admin");
|
||||
for(QSecurityKeyType keyType : instance.getSecurityKeyTypes().values())
|
||||
{
|
||||
if(keyType.getAllAccessKeyName() != null)
|
||||
{
|
||||
session.withSecurityKeyValue(keyType.getAllAccessKeyName(), true);
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// per-user keys (owner locks of qbit tables) take the user id //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
session.withSecurityKeyValue(keyType.getName(), userId);
|
||||
}
|
||||
grantEnterpriseAccess(instance, session, userId);
|
||||
}
|
||||
session.withValueForFrontend("user", new LinkedHashMap<>(Map.of("name", name, "email", userId + "@example.test")));
|
||||
if(EnterpriseFeatures.isEnabled())
|
||||
{
|
||||
EnterpriseSessions.record(session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The enterprise test server: every session is an all-access admin, so the
|
||||
* terminal clients can reach every qbit surface.
|
||||
*/
|
||||
static void grantEnterpriseAccess(QInstance instance, QSession session, String userId)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// enterprise test server: every session is an all-access admin, so //
|
||||
// the terminal clients can reach every qbit surface //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
session.withPermissions(PermissionsHelper.getAllAvailablePermissionNames(instance));
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// permissions some modules check by literal name, outside the //
|
||||
// table/process permission rules (QRun-IO/qqq#1362) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
session.withPermissions("jobs_job.read", "jobs_admin", "data_analysis.admin", "assistant.hasAccess", "assistant.admin");
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// qbit-collaboration's custom checkers (Custom Fields, tags, the //
|
||||
// admin tables) test these names; without them the Custom Fields //
|
||||
// table answered 403 "This needs collaboration.view." (#47) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
session.withPermissions("collaboration.view", "collaboration.admin", "collaboration.fields.manage", "collaboration.tags.manage");
|
||||
for(QSecurityKeyType keyType : instance.getSecurityKeyTypes().values())
|
||||
{
|
||||
if(keyType.getAllAccessKeyName() != null)
|
||||
{
|
||||
session.withSecurityKeyValue(keyType.getAllAccessKeyName(), true);
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// per-user keys (owner locks of qbit tables) take the user id //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
session.withSecurityKeyValue(keyType.getName(), userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,10 @@ import com.kingsrook.qqq.backend.core.processes.implementations.columnstats.Colu
|
||||
* field-access policy to them instead of withholding table-less records.
|
||||
* The process shape belongs in qqq core (QRun-IO/qqq#1367); this is the host's
|
||||
* safe check until then.
|
||||
*
|
||||
* TODO(QRun-IO/qqq#1367): when core's ColumnStatsStep refuses fields the caller
|
||||
* cannot read in full and tags its result records with the source table, delete
|
||||
* this class and the code swap in EnterpriseFeatures.enableColumnStatistics.
|
||||
*/
|
||||
public class GuardedColumnStatsStep extends ColumnStatsStep
|
||||
{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2026 Kingsrook, LLC
|
||||
* 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
|
||||
*/
|
||||
package com.kingsrook.qqq.all;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import com.kingsrook.qbits.collaboration.customfields.FieldSecurity;
|
||||
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.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/** The enterprise admin passes qbit-collaboration's Custom Fields checker (qqq-all#47). */
|
||||
class EnterpriseAccessTest
|
||||
{
|
||||
@TempDir Path dataDirectory;
|
||||
|
||||
@AfterEach
|
||||
void tearDown()
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminCanReadAndManageCustomFields() throws Exception
|
||||
{
|
||||
QInstance instance = new QqqAllApplication(dataDirectory, "tcp://127.0.0.1:61616").defineQInstance();
|
||||
QSession plain = new QSession();
|
||||
QContext.init(instance, plain);
|
||||
assertThrows(QPermissionDeniedException.class,
|
||||
() -> new FieldSecurity().checkPermissionsThrowing(new QueryInput("collaboration_field_definition"), null));
|
||||
|
||||
QSession admin = new QSession();
|
||||
DemoUsers.grantEnterpriseAccess(instance, admin, "core:admin");
|
||||
QContext.init(instance, admin);
|
||||
assertDoesNotThrow(() -> new FieldSecurity().checkPermissionsThrowing(new QueryInput("collaboration_field_definition"), null));
|
||||
assertDoesNotThrow(() -> new FieldSecurity().checkPermissionsThrowing(new InsertInput("collaboration_field_definition"), null));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user