From b9edba7f696b2175cc74f111891d9051ce5e9c31 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 28 九月 2026 15:21:21 +0800
Subject: [PATCH] 拒绝把全权限账号写进厂商数据源配置

---
 battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java      |   20 +++++++++++++++++++-
 battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java |   16 ++++++++++++++++
 2 files changed, 35 insertions(+), 1 deletions(-)

diff --git a/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java b/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java
index 34da3c6..8e074a2 100644
--- a/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java
+++ b/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java
@@ -1,5 +1,7 @@
 package com.battery.device.neware;
 
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import com.battery.device.api.DeviceAdapter;
 import com.battery.device.api.DeviceAdapterException;
@@ -21,6 +23,8 @@
     public static final String PASSWORD_ENV_OPTION = "passwordEnv";
     private static final String DEFAULT_DATABASE = "bts63";
     private static final String READ_ONLY_USER = "visitor";
+    /** 现场已知的全权限账号;写进交付配置就等于把误写风险带进现场。 */
+    private static final List<String> FULL_PRIVILEGE_USERS = List.of("btsserver", "root");
 
     @Override
     public String adapterType()
@@ -32,7 +36,7 @@
     public DeviceAdapter create(AdapterInstanceConfig config)
     {
         String database = option(config.sourceConfig(), DATABASE_OPTION, DEFAULT_DATABASE);
-        String user = option(config.sourceConfig(), USER_OPTION, READ_ONLY_USER);
+        String user = readOnlyUser(config.sourceConfig());
         String password = credential(option(config.sourceConfig(), PASSWORD_ENV_OPTION,
                 NewareMysqlAdapter.DEFAULT_PASSWORD_ENV));
         SingleConnectionVendorDataSource source = new SingleConnectionVendorDataSource(
@@ -40,6 +44,20 @@
         return NewareMysqlAdapter.create(config, source);
     }
 
+    /**
+     * 解析厂商库账号:默认取现场取得的服务端只读账号,已知的全权限账号一律拒绝。
+     *
+     * <p>准入只按账号名拦已知的全权限账号(那不是"写一次就回不去"的能力),
+     * 真正的只读边界仍是服务端授权与语句白名单两层;这一层只是不把误配写进交付配置。
+     */
+    static String readOnlyUser(Map<String, String> sourceConfig)
+    {
+        String user = option(sourceConfig, USER_OPTION, READ_ONLY_USER);
+        if (FULL_PRIVILEGE_USERS.contains(user.toLowerCase(Locale.ROOT)))
+            throw new DeviceAdapterException("厂商数据源只允许只读账号,拒绝全权限账号:" + user);
+        return user;
+    }
+
     /** 只读账号口令;启动参数优先于环境变量,便于本机排查而不必改环境。 */
     static String credential(String variableName)
     {
diff --git a/battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java b/battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java
index d89ff23..d3be22c 100644
--- a/battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java
+++ b/battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java
@@ -204,4 +204,20 @@
         assertTrue(adapter.discoverChannels().stream()
                 .allMatch(item -> ADAPTER_CODE.equals(item.identity().adapterCode())));
     }
+
+    /** 凭据只用现场取得的服务端只读账号;已知的全权限账号不进入配置。 */
+    @Test
+    void credentialsFallBackToTheReadOnlyAccountAndRefuseFullPrivilegeAccounts()
+    {
+        assertEquals("visitor", NewareMysqlAdapterFactory.readOnlyUser(Map.of()));
+        assertEquals("visitor", NewareMysqlAdapterFactory.readOnlyUser(Map.of("user", " ")));
+        assertEquals("another_ro", NewareMysqlAdapterFactory.readOnlyUser(Map.of("user", "another_ro")));
+        for (String account : List.of("BtsServer", "btsserver", "root", "ROOT"))
+        {
+            var failure = org.junit.jupiter.api.Assertions.assertThrows(
+                    com.battery.device.api.DeviceAdapterException.class,
+                    () -> NewareMysqlAdapterFactory.readOnlyUser(Map.of("user", account)));
+            assertTrue(failure.getMessage().contains(account));
+        }
+    }
 }

--
Gitblit v1.7.1