刘光辉
117 分钟以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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); }
        }
    }
}