package jnpf.dmsMapper;
|
|
import jnpf.dmsDatabase.DmsUuidTypeHandler;
|
import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity;
|
import jnpf.dmsEntity.storage.approval.DmsApprovedItem;
|
import jnpf.dmsPermission.DmsPermissionMatcher;
|
import jnpf.dmsEntity.permission.model.DmsSubjectSet;
|
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
|
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
|
import org.apache.ibatis.mapping.Environment;
|
import org.apache.ibatis.session.*;
|
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
import java.io.StringReader;
|
import java.nio.charset.StandardCharsets;
|
import java.sql.SQLException;
|
import java.util.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
|
/** Real mapper/JDBC against connection-local tables. Never modifies customer rows or persistent DDL. */
|
@EnabledIfEnvironmentVariable(named="DMS_READ_TEST_URL",matches=".+")
|
class DmsApprovedReadAccessPostgresTest {
|
@Test void persistsSnapshotsReplacesOnlyOwnedReadAndRollsBackFailedReplacement() throws Exception {
|
var ds=new UnpooledDataSource("org.postgresql.Driver",System.getenv("DMS_READ_TEST_URL"),
|
System.getenv("DMS_READ_TEST_USER"),System.getenv("DMS_READ_TEST_PASSWORD"));
|
var config=new Configuration(new Environment("read-test",new JdbcTransactionFactory(),ds));
|
config.setDefaultStatementTimeout(15);
|
for (String name:List.of("DmsApprovedReadAccessMapper","DmsFilePermissionMapper","DmsApprovedBatchMapper")) {
|
String path="mapper/"+name+".xml";
|
try(var input=getClass().getResourceAsStream("/"+path)) {
|
String xml=new String(Objects.requireNonNull(input).readAllBytes(),StandardCharsets.UTF_8)
|
.replace("public.dms_file_permissions","pg_temp.read_permissions")
|
.replace("public.dms_approved_file_items","pg_temp.read_items")
|
.replace("public.base_organize","pg_temp.read_departments");
|
new XMLMapperBuilder(new StringReader(xml),config,path,config.getSqlFragments()).parse();
|
}
|
}
|
try(var session=new SqlSessionFactoryBuilder().build(config).openSession(false)) {
|
try {
|
var connection=session.getConnection();
|
try(var sql=connection.createStatement()) {
|
sql.execute("CREATE TEMP TABLE read_permissions (LIKE public.dms_file_permissions INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES) ON COMMIT DROP");
|
sql.execute("ALTER TABLE pg_temp.read_permissions ADD COLUMN IF NOT EXISTS approved_org_scope TEXT");
|
sql.execute("CREATE TRIGGER permission_guard BEFORE UPDATE ON pg_temp.read_permissions FOR EACH ROW EXECUTE FUNCTION public.dms_permission_guard_permission_update()");
|
sql.execute("CREATE TEMP TABLE read_items (LIKE public.dms_approved_file_items INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES) ON COMMIT DROP");
|
sql.execute("ALTER TABLE pg_temp.read_items ADD COLUMN IF NOT EXISTS read_access_scope JSONB NOT NULL DEFAULT '[]'::jsonb");
|
sql.execute("CREATE TEMP TABLE read_departments (f_id text, f_tenant_id text, f_delete_mark integer, f_enabled_mark integer) ON COMMIT DROP");
|
sql.execute("INSERT INTO pg_temp.read_departments VALUES ('dept','tenant',NULL,1),('disabled','tenant',NULL,0),('foreign','other',NULL,1)");
|
}
|
var mapper=session.getMapper(DmsApprovedReadAccessMapper.class);
|
var permissions=session.getMapper(DmsFilePermissionMapper.class);
|
assertEquals(List.of(new DmsApprovedReadAccessMapper.Department("dept")),mapper.departments("tenant",List.of("dept","disabled","foreign")));
|
var item=new DmsApprovedItem();item.setBatchId(UUID.randomUUID());item.setTenantId("tenant");
|
item.setSourceKey("audit:main");item.setChangeType("CREATE");item.setDocumentId(UUID.randomUUID());item.setFileVersionId(UUID.randomUUID());
|
item.setReadAccessScope("[\"dept--subOrg\"]");
|
var receipts=session.getMapper(DmsApprovedBatchMapper.class);receipts.insertItem(item);
|
assertEquals(item.getReadAccessScope(),receipts.item("tenant","audit:main").getReadAccessScope());
|
// SQL interfaces return top-level arrays as text, while nested organizeSelect values stay arrays.
|
try (var sql=connection.createStatement(); var result=sql.executeQuery(
|
"SELECT read_access_scope::text AS scope, jsonb_build_array(jsonb_build_object('chayue_shouquan_fanwei_id',read_access_scope))::text AS rows FROM pg_temp.read_items WHERE source_key='audit:main'")) {
|
assertTrue(result.next()); assertInstanceOf(String.class,result.getObject("scope"));
|
assertInstanceOf(String.class,result.getObject("rows"));
|
var json=new com.fasterxml.jackson.databind.ObjectMapper();
|
assertTrue(json.readTree((String)result.getObject("scope")).isArray());
|
assertEquals("dept--subOrg",json.readTree((String)result.getObject("rows"))
|
.get(0).get("chayue_shouquan_fanwei_id").get(0).textValue());
|
}
|
item.setSourceKey("audit:legacy");item.setReadAccessScope(null);receipts.insertItem(item);
|
assertEquals("[]",receipts.item("tenant","audit:legacy").getReadAccessScope());
|
item.setSourceKey("audit:clear");item.setReadAccessScope("[]");receipts.insertItem(item);
|
assertEquals("[]",receipts.item("tenant","audit:clear").getReadAccessScope());
|
var grant=new DmsFilePermissionEntity();grant.setTenantId("tenant");grant.setResourceId(item.getFileVersionId());
|
grant.setPrincipalId("dept");grant.setIncludeChildOrgs(false);grant.setApprovedOrgScope("CHILDREN");
|
grant.setSourceId("approved-read:"+item.getBatchId()+":"+item.getFileVersionId());
|
grant.setValidFrom(new Date(System.currentTimeMillis()-1000));grant.setGrantReason("read test");
|
var saved=mapper.insert(grant);
|
assertNotNull(saved.getId());assertEquals("CHILDREN",saved.getApprovedOrgScope());
|
assertEquals("CHILDREN",permissions.selectByTenantAndId("tenant",saved.getId()).getApprovedOrgScope());
|
assertEquals("CHILDREN",permissions.selectByTenantAndIdForUpdate("tenant",saved.getId()).getApprovedOrgScope());
|
assertNull(permissions.selectByTenantAndId("other",saved.getId()));
|
var candidates=permissions.selectCandidates("tenant",List.of("READ"),List.of(item.getFileVersionId()));
|
assertEquals(1,candidates.size());
|
var subjects=new DmsSubjectSet();subjects.getImmediateParentOrgIds().add("dept");
|
assertTrue(new DmsPermissionMatcher().matchesPrincipal(candidates.getFirst(),subjects));
|
assertTrue(permissions.selectCandidates("tenant",List.of("DOWNLOAD"),List.of(item.getFileVersionId())).isEmpty());
|
// Unrelated manual permission on this exact version survives replacement.
|
try(var sql=connection.createStatement()) {
|
sql.execute("INSERT INTO pg_temp.read_permissions(id,tenant_id,resource_type,resource_id,resource_scope,principal_type,principal_id,include_child_orgs,action_code,source_type,source_id,valid_from,granted_by_type,granted_by_id) VALUES (uuidv7(),'tenant','FILE_VERSION','"+item.getFileVersionId()+"','SELF','ORG','dept',false,'READ','MANUAL','manual-test',CURRENT_TIMESTAMP,'USER','admin')");
|
}
|
assertEquals(1,mapper.owned("tenant",item.getFileVersionId()).size());
|
var savepoint=connection.setSavepoint();
|
assertEquals(1,mapper.revoke("tenant",saved.getId()));
|
grant.setSourceId("approved-read:replacement");grant.setPrincipalId(" ");
|
assertThrows(Exception.class,()->mapper.insert(grant));
|
connection.rollback(savepoint);session.clearCache();
|
assertNull(mapper.owned("tenant",item.getFileVersionId()).getFirst().getRevokedAt());
|
assertEquals(1,mapper.revoke("tenant",saved.getId()));
|
assertEquals(0,mapper.revoke("tenant",saved.getId()));
|
assertEquals(1,permissions.selectCandidates("tenant",List.of("READ"),List.of(item.getFileVersionId())).size());
|
assertNotNull(mapper.owned("tenant",item.getFileVersionId()).getFirst().getRevokedAt());
|
} finally { session.rollback(true); }
|
}
|
}
|
}
|