feat: gate overseas receiving and harden phase4 workflows
This commit is contained in:
@@ -113,6 +113,81 @@ DELIMITER ;
|
||||
CALL assert_overseas_pda_picker_permission();
|
||||
DROP PROCEDURE assert_overseas_pda_picker_permission;
|
||||
|
||||
-- 四期仓库后台角色严格最小授权:list 同时覆盖列表与详情,只读角色不得获得任何写权限。
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_phase4_warehouse_role_permission;
|
||||
CREATE TEMPORARY TABLE tmp_phase4_warehouse_role_permission (
|
||||
role_code VARCHAR(64) NOT NULL,
|
||||
permission_code VARCHAR(160) NOT NULL,
|
||||
PRIMARY KEY (role_code, permission_code)
|
||||
);
|
||||
INSERT INTO tmp_phase4_warehouse_role_permission (role_code, permission_code) VALUES
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:last-mile-order:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:inbound-order:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:relabel-order:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:inventory:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:warehouse:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:location:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-warehouse:pallet:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-business:purchase:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-business:truck:list'),
|
||||
('WAREHOUSE_OPERATOR','platform:overseas-business:looseboard:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:last-mile-order:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:inbound-order:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:relabel-order:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:inventory:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:warehouse:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:location:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-warehouse:pallet:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-business:purchase:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-business:truck:list'),
|
||||
('WAREHOUSE_TALLY','platform:overseas-business:looseboard:list');
|
||||
|
||||
DELETE grant_row FROM iam_role_permission grant_row
|
||||
JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id
|
||||
JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id
|
||||
LEFT JOIN tmp_phase4_warehouse_role_permission expected
|
||||
ON expected.role_code=role.role_code AND expected.permission_code=permission.permission_code
|
||||
WHERE role.tenant_id=0 AND role.app_code='PLATFORM'
|
||||
AND role.role_code IN ('WAREHOUSE_OPERATOR','WAREHOUSE_PICKER','WAREHOUSE_TALLY')
|
||||
AND permission.permission_code LIKE 'platform:overseas-%'
|
||||
AND expected.permission_code IS NULL;
|
||||
|
||||
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
|
||||
SELECT 0, role.id, permission.id, NOW(3)
|
||||
FROM tmp_phase4_warehouse_role_permission expected
|
||||
JOIN iam_role role ON role.tenant_id=0 AND role.app_code='PLATFORM'
|
||||
AND role.role_code=expected.role_code AND role.status='ACTIVE' AND role.deleted=0
|
||||
JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM'
|
||||
AND permission.permission_code=expected.permission_code
|
||||
AND permission.status='ACTIVE' AND permission.deleted=0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_phase4_warehouse_role_minimum;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_phase4_warehouse_role_minimum()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM tmp_phase4_warehouse_role_permission) <> 20 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='四期仓库角色最小权限矩阵数量异常';
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM tmp_phase4_warehouse_role_permission
|
||||
WHERE permission_code REGEXP ':(create|edit|status|export|adjust|delete|review)$') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='四期仓库只读角色错误获得写权限';
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM tmp_phase4_warehouse_role_permission expected
|
||||
LEFT JOIN iam_role role ON role.tenant_id=0 AND role.app_code='PLATFORM'
|
||||
AND role.role_code=expected.role_code AND role.status='ACTIVE' AND role.deleted=0
|
||||
LEFT JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM'
|
||||
AND permission.permission_code=expected.permission_code AND permission.status='ACTIVE' AND permission.deleted=0
|
||||
LEFT JOIN iam_role_permission grant_row ON grant_row.tenant_id=0
|
||||
AND grant_row.role_id=role.id AND grant_row.permission_id=permission.id
|
||||
WHERE grant_row.role_id IS NULL) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='四期仓库角色最小权限授权不完整';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_phase4_warehouse_role_minimum();
|
||||
DROP PROCEDURE assert_phase4_warehouse_role_minimum;
|
||||
DROP TEMPORARY TABLE tmp_phase4_warehouse_role_permission;
|
||||
|
||||
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
|
||||
SELECT 0, role.id, permission.id, NOW(3)
|
||||
FROM iam_role role
|
||||
|
||||
+37
@@ -60,6 +60,35 @@ class OverseasPhase4PermissionSqlContractTest {
|
||||
assertCustomAdminGrant(sql);
|
||||
}
|
||||
|
||||
@Test
|
||||
void warehouseRolesReceiveOnlyTheTenPhase4ListPermissionsAndPickerRemainsPdaOnly() throws Exception {
|
||||
String moduleSql = new ClassPathResource("db/lastleg-permissions.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
Path path = Path.of("deploy/sql/头程数据库统一增量-MySQL8.sql");
|
||||
if (!Files.exists(path)) path = Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql");
|
||||
String unifiedSql = Files.readString(path, StandardCharsets.UTF_8);
|
||||
|
||||
for (String sql : List.of(moduleSql, unifiedSql)) {
|
||||
String matrix = matrixBlock(sql);
|
||||
assertThat(matrix).contains("WAREHOUSE_OPERATOR", "WAREHOUSE_TALLY");
|
||||
assertThat(codes(matrix)).containsExactlyInAnyOrder(
|
||||
"platform:overseas-warehouse:last-mile-order:list",
|
||||
"platform:overseas-warehouse:inbound-order:list",
|
||||
"platform:overseas-warehouse:relabel-order:list",
|
||||
"platform:overseas-warehouse:inventory:list",
|
||||
"platform:overseas-warehouse:warehouse:list",
|
||||
"platform:overseas-warehouse:location:list",
|
||||
"platform:overseas-warehouse:pallet:list",
|
||||
"platform:overseas-business:purchase:list",
|
||||
"platform:overseas-business:truck:list",
|
||||
"platform:overseas-business:looseboard:list");
|
||||
assertThat(matrix).doesNotContain("WAREHOUSE_PICKER");
|
||||
assertThat(matrix).doesNotContain(":create'", ":edit'", ":status'", ":export'", ":adjust'");
|
||||
assertThat(sql).contains("assert_phase4_warehouse_role_minimum", "<> 20",
|
||||
"role.role_code IN ('WAREHOUSE_OPERATOR','WAREHOUSE_PICKER','WAREHOUSE_TALLY')");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void servicePermissionConstantsExactlyMatchTheIamCatalog() throws Exception {
|
||||
Set<String> moduleCatalog = codes(new ClassPathResource("db/lastleg-permissions.sql")
|
||||
@@ -109,6 +138,14 @@ class OverseasPhase4PermissionSqlContractTest {
|
||||
assertThat(sql.substring(grantStart, grantEnd)).contains("lastleg:pda:operate");
|
||||
}
|
||||
|
||||
private static String matrixBlock(String sql) {
|
||||
int start = sql.indexOf("INSERT INTO tmp_phase4_warehouse_role_permission");
|
||||
int end = sql.indexOf("DELETE grant_row FROM", start);
|
||||
assertThat(start).isGreaterThanOrEqualTo(0);
|
||||
assertThat(end).isGreaterThan(start);
|
||||
return sql.substring(start, end);
|
||||
}
|
||||
|
||||
private static String repositoryFile(String relativePath) throws Exception {
|
||||
Path path = Path.of(relativePath);
|
||||
if (!Files.exists(path)) path = Path.of("..").resolve(relativePath);
|
||||
|
||||
@@ -135,6 +135,34 @@ class PlatformUserManagementServiceTest {
|
||||
verify(auth).revokeSessions(eq("internal-token"), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void changingWarehouseBindingRevokesAllAuthSessionsSoAccountVersionIsBumped() {
|
||||
platformRole.setRoleCode("WAREHOUSE_PICKER");
|
||||
membership.setOverseasWarehouseId(900L);
|
||||
UpdateUserRequest changed = new UpdateUserRequest("张伟", "13800138000", null,
|
||||
0L, List.of(40001L), 901L);
|
||||
|
||||
UserResponse response = service.update(30L, changed, Set.of("platform:user:grant-role"));
|
||||
|
||||
assertEquals(901L, response.overseasWarehouseId());
|
||||
verify(auth).revokeSessions(eq("internal-token"),
|
||||
eq(new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(10L))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearingWarehouseBindingAlsoRevokesAllAuthSessions() {
|
||||
platformRole.setRoleCode("WAREHOUSE_OPERATOR");
|
||||
membership.setOverseasWarehouseId(900L);
|
||||
UpdateUserRequest cleared = new UpdateUserRequest("张伟", "13800138000", null,
|
||||
0L, List.of(40001L), null);
|
||||
|
||||
UserResponse response = service.update(30L, cleared, Set.of("platform:user:grant-role"));
|
||||
|
||||
assertNull(response.overseasWarehouseId());
|
||||
verify(auth).revokeSessions(eq("internal-token"),
|
||||
eq(new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(10L))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentPlatformOperatorCannotDisableSelf() {
|
||||
assertThrows(BizException.class,
|
||||
|
||||
Reference in New Issue
Block a user