Skip to content

Commit d2ea938

Browse files
committed
安全功能&U盘验证功能
修复 U盘绑定功能 ,新增对 Windows API调用 的错误处理和异常捕获机制 修复 ComboBox数据获取 ,优化当 currentData() 返回None时从显示文本中提取 盘符信息 的后备方案 修复 文件写入权限 ,新增使用 临时文件 策略避免secrets.json写入时的权限拒绝问题 修复 U盘验证布局 ,优化仅在需要 U盘验证 时才显示相关控件(状态标签和刷新按钮) 优化 界面显示逻辑 ,新增根据 验证配置 动态控制U盘验证组件的可见性
1 parent 5db3f40 commit d2ea938

File tree

5 files changed

+134
-50
lines changed

5 files changed

+134
-50
lines changed

CHANGELOG/v1.3.2-alpha.6/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ v2.0 - Koharu(小鸟游星野) Alpha 6
2020
- 优化 **闪抽动画关闭提示**,新增在动画结束后同时显示“x秒后关闭”和“连续点击3下关闭”
2121
- 优化 **配置查看窗口**,新增去除关闭按钮
2222
- 优化 **语音重试机制**,针对不同错误类型设置差异化重试间隔,提升成功率
23+
- 优化 **界面显示逻辑**,新增根据**验证配置**动态控制U盘验证组件的可见性
2324

2425
## 🐛 修复问题
2526

@@ -36,6 +37,10 @@ v2.0 - Koharu(小鸟游星野) Alpha 6
3637
- 修复 **语音缓存系统**,新增WebSocketError异常处理增强连接稳定性
3738
- 修复 **Edge TTS库版本**,升级到最新版本 7.2.7,解决无法生成语音的问题
3839
- 修复 **Windows平台文件隐藏功能**,新增**返回值检查****错误码记录**功能
40+
- 修复 **U盘绑定功能**,新增对**Windows API调用**的错误处理和异常捕获机制
41+
- 修复 **ComboBox数据获取**,优化当`currentData()`返回None时从显示文本中提取**盘符信息**的后备方案
42+
- 修复 **文件写入权限**,新增使用**临时文件**策略避免secrets.json写入时的权限拒绝问题
43+
- 修复 **U盘验证布局**,优化仅在需要**U盘验证**时才显示相关控件(状态标签和刷新按钮)
3944

4045
## 🔧 其它变更
4146

app/common/safety/secure_store.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ def _set_hidden(path: str) -> None:
2323
# Windows 平台:设置文件属性为隐藏和系统文件
2424
FILE_ATTRIBUTE_HIDDEN = 0x2
2525
FILE_ATTRIBUTE_SYSTEM = 0x4
26-
# 确保路径是Unicode字符串
26+
# 确保路径是Unicode字符串,使用 c_wchar_p 显式转换
2727
result = ctypes.windll.kernel32.SetFileAttributesW(
28-
path, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM
28+
ctypes.c_wchar_p(path), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM
2929
)
3030
if result == 0: # 函数失败
3131
logger.warning(
@@ -104,7 +104,7 @@ def read_secrets() -> dict:
104104
ensure_dir(os.path.dirname(p))
105105
with open(p, "wb") as f:
106106
f.write(b"")
107-
_set_hidden(p)
107+
_set_hidden(str(p))
108108
logger.debug(f"创建空安全配置文件:{p}")
109109
return {}
110110
if os.path.exists(p):
@@ -145,9 +145,41 @@ def write_secrets(d: dict) -> None:
145145
payload = _encrypt_payload(comp, key)
146146
with open(p, "wb") as f:
147147
f.write(b"SRV1" + payload)
148-
_set_hidden(p)
148+
_set_hidden(str(p))
149149
logger.debug(f"写入安全配置成功:{p}")
150-
except Exception:
151-
with open(p, "w", encoding="utf-8") as f:
152-
json.dump(d, f, ensure_ascii=False, indent=4)
153-
logger.warning(f"写入安全配置降级为明文JSON:{p}")
150+
except PermissionError as e:
151+
logger.error(
152+
f"写入安全配置失败:权限被拒绝,文件可能被占用或无写权限:{p}, 错误:{e}"
153+
)
154+
# 尝试使用临时文件写入然后替换
155+
try:
156+
import tempfile
157+
158+
with tempfile.NamedTemporaryFile(
159+
mode="wb", delete=False, dir=os.path.dirname(p)
160+
) as tmp_file:
161+
tmp_file.write(b"SRV1" + payload)
162+
tmp_path = tmp_file.name
163+
164+
# 替换原文件
165+
os.replace(tmp_path, p)
166+
_set_hidden(str(p))
167+
logger.debug(f"使用临时文件写入安全配置成功:{p}")
168+
except Exception as temp_e:
169+
logger.error(f"使用临时文件写入安全配置也失败:{temp_e}")
170+
# 降级到明文JSON写入
171+
try:
172+
with open(p, "w", encoding="utf-8") as f:
173+
json.dump(d, f, ensure_ascii=False, indent=4)
174+
logger.warning(f"写入安全配置降级为明文JSON:{p}")
175+
except Exception as e2:
176+
logger.error(f"降级写入明文JSON也失败:{e2}")
177+
except Exception as e:
178+
logger.error(f"写入安全配置失败:{p}, 错误:{e}")
179+
# 降级到明文JSON写入
180+
try:
181+
with open(p, "w", encoding="utf-8") as f:
182+
json.dump(d, f, ensure_ascii=False, indent=4)
183+
logger.warning(f"写入安全配置降级为明文JSON:{p}")
184+
except Exception as e2:
185+
logger.error(f"降级写入明文JSON也失败:{e2}")

app/common/safety/usb.py

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -213,23 +213,32 @@ def is_drive_removable(letter_or_path: str) -> bool:
213213

214214
def get_volume_serial(letter_or_path: str) -> str:
215215
if platform.system() == "Windows":
216-
buf_name = ctypes.create_unicode_buffer(256)
217-
vol_serial = ctypes.c_uint()
218-
max_comp_len = ctypes.c_uint()
219-
fs_flags = ctypes.c_uint()
220-
fs_name = ctypes.create_unicode_buffer(256)
221-
ctypes.windll.kernel32.GetVolumeInformationW(
222-
f"{letter_or_path}:\\",
223-
buf_name,
224-
ctypes.sizeof(buf_name),
225-
ctypes.byref(vol_serial),
226-
ctypes.byref(max_comp_len),
227-
ctypes.byref(fs_flags),
228-
fs_name,
229-
ctypes.sizeof(fs_name),
230-
)
231-
if vol_serial.value:
232-
return f"{vol_serial.value:08X}"
216+
try:
217+
buf_name = ctypes.create_unicode_buffer(256)
218+
vol_serial = ctypes.c_uint()
219+
max_comp_len = ctypes.c_uint()
220+
fs_flags = ctypes.c_uint()
221+
fs_name = ctypes.create_unicode_buffer(256)
222+
result = ctypes.windll.kernel32.GetVolumeInformationW(
223+
f"{letter_or_path}:\\",
224+
buf_name,
225+
ctypes.sizeof(buf_name),
226+
ctypes.byref(vol_serial),
227+
ctypes.byref(max_comp_len),
228+
ctypes.byref(fs_flags),
229+
fs_name,
230+
ctypes.sizeof(fs_name),
231+
)
232+
if result and vol_serial.value:
233+
logger.debug(
234+
f"获取卷序列号成功(GetVolumeInformationW):{letter_or_path}: -> {vol_serial.value:08X}"
235+
)
236+
return f"{vol_serial.value:08X}"
237+
logger.debug(
238+
f"GetVolumeInformationW 返回失败或序列号为空:{letter_or_path}:, result={result}"
239+
)
240+
except Exception as e:
241+
logger.warning(f"GetVolumeInformationW 异常:{letter_or_path}:, {e}")
233242
# 尝试获取卷GUID作为备用唯一标识
234243
try:
235244
buf = ctypes.create_unicode_buffer(64)
@@ -238,9 +247,17 @@ def get_volume_serial(letter_or_path: str) -> str:
238247
)
239248
if ok:
240249
guid = buf.value # 形如 "\\\\?\Volume{GUID}\""
250+
logger.debug(
251+
f"获取卷GUID成功:{letter_or_path}: -> {guid.strip().upper()}"
252+
)
241253
return guid.strip().upper()
242-
except Exception:
243-
pass
254+
logger.debug(
255+
f"GetVolumeNameForVolumeMountPointW 返回失败:{letter_or_path}:"
256+
)
257+
except Exception as e:
258+
logger.warning(
259+
f"GetVolumeNameForVolumeMountPointW 异常:{letter_or_path}:, {e}"
260+
)
244261
# 最终回退:QueryDosDevice 映射路径的哈希
245262
try:
246263
size = 256
@@ -253,9 +270,12 @@ def get_volume_serial(letter_or_path: str) -> str:
253270
.hexdigest()
254271
.upper()
255272
)
273+
logger.debug(f"使用设备路径哈希:{letter_or_path}: -> {h[:16]}")
256274
return f"DEV-{h}"
257-
except Exception:
258-
pass
275+
logger.debug(f"QueryDosDeviceW 返回失败:{letter_or_path}:")
276+
except Exception as e:
277+
logger.warning(f"QueryDosDeviceW 异常:{letter_or_path}:, {e}")
278+
logger.warning(f"所有方法均失败,返回默认值:{letter_or_path}: -> 00000000")
259279
return "00000000"
260280
else: # Linux
261281
try:
@@ -289,13 +309,13 @@ def get_volume_serial(letter_or_path: str) -> str:
289309

290310
def get_volume_label(letter_or_path: str) -> str:
291311
if platform.system() == "Windows":
292-
buf_name = ctypes.create_unicode_buffer(256)
293-
vol_serial = ctypes.c_uint()
294-
max_comp_len = ctypes.c_uint()
295-
fs_flags = ctypes.c_uint()
296-
fs_name = ctypes.create_unicode_buffer(256)
297312
try:
298-
ctypes.windll.kernel32.GetVolumeInformationW(
313+
buf_name = ctypes.create_unicode_buffer(256)
314+
vol_serial = ctypes.c_uint()
315+
max_comp_len = ctypes.c_uint()
316+
fs_flags = ctypes.c_uint()
317+
fs_name = ctypes.create_unicode_buffer(256)
318+
result = ctypes.windll.kernel32.GetVolumeInformationW(
299319
f"{letter_or_path}:\\",
300320
buf_name,
301321
ctypes.sizeof(buf_name),
@@ -305,8 +325,13 @@ def get_volume_label(letter_or_path: str) -> str:
305325
fs_name,
306326
ctypes.sizeof(fs_name),
307327
)
308-
return buf_name.value
309-
except Exception:
328+
if result:
329+
logger.debug(f"获取卷标成功:{letter_or_path}: -> {buf_name.value}")
330+
return buf_name.value
331+
logger.debug(f"获取卷标失败:{letter_or_path}:, result={result}")
332+
return ""
333+
except Exception as e:
334+
logger.warning(f"获取卷标异常:{letter_or_path}:, {e}")
310335
return ""
311336
else: # Linux
312337
try:

app/view/another_window/security/verify_password.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,18 @@ def _configure_methods(self):
234234
self._required = list(dict.fromkeys(r))
235235
self._any_one = False
236236

237-
self.password_label.setVisible("password" in self._required)
238-
self.password_edit.setVisible("password" in self._required)
239-
self.totp_label.setVisible("totp" in self._required)
240-
self.totp_edit.setVisible("totp" in self._required)
241-
# 始终显示U盘解锁控件
242-
self.usb_status_label.setVisible(True)
243-
self.usb_refresh_button.setVisible(True)
237+
# 只在需要密码验证时显示密码控件
238+
password_visible = "password" in self._required
239+
self.password_label.setVisible(password_visible)
240+
self.password_edit.setVisible(password_visible)
241+
# 只在需要TOTP验证时显示TOTP控件
242+
totp_visible = "totp" in self._required
243+
self.totp_label.setVisible(totp_visible)
244+
self.totp_edit.setVisible(totp_visible)
245+
# 只在需要USB验证时显示USB控件
246+
usb_visible = "usb" in self._required
247+
self.usb_status_label.setVisible(usb_visible)
248+
self.usb_refresh_button.setVisible(usb_visible)
244249
present = bool(is_bound_present())
245250
self._last_usb_present = present
246251
self.usb_status_label.setText(

app/view/another_window/usb/bind_usb.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,21 @@ def __refresh(self):
111111
if not letters:
112112
# 回退到驱动类型为可移动盘的枚举
113113
letters = list_removable_drives()
114-
logger.debug(f"刷新可绑定设备,数量:{len(letters)}")
114+
logger.debug(f"刷新可绑定设备,数量:{len(letters)}, 设备列表:{letters}")
115115
for device in letters:
116116
if platform.system() == "Windows":
117117
# Windows 平台:device 是盘符(如 "E")
118118
name = get_volume_label(device)
119119
text = f"{name} ({device}:)" if name else f"({device}:)"
120120
self.drive_combo.addItem(text, device)
121+
logger.debug(f"添加设备到下拉框:显示文本={text}, 数据={device}")
121122
else:
122123
# Linux 平台:device 是挂载点路径(如 "/media/user/USB Drive")
123124
name = get_volume_label(device)
124125
# 在Linux上,name 可能就是挂载点的目录名,所以直接使用 device 作为显示文本
125126
text = device
126127
self.drive_combo.addItem(text, device)
128+
logger.debug(f"添加设备到下拉框:显示文本={text}, 数据={device}")
127129
if not letters:
128130
self.drive_combo.setCurrentIndex(-1)
129131
# 使占位文本可见
@@ -141,6 +143,7 @@ def __refresh(self):
141143
else:
142144
try:
143145
self.drive_combo.setEditable(False)
146+
self.drive_combo.setCurrentIndex(0)
144147
except Exception:
145148
pass
146149
self.bind_button.setEnabled(True)
@@ -149,19 +152,35 @@ def __refresh(self):
149152

150153
def __bind(self):
151154
idx = self.drive_combo.currentIndex()
155+
logger.debug(f"当前选中索引:{idx}")
152156
if idx < 0:
153157
self._notify_error(
154158
get_content_name_async("basic_safety_settings", "usb_no_removable")
155159
)
156160
return
157161
text = self.drive_combo.currentText()
158162
device = self.drive_combo.currentData()
159-
import platform
163+
logger.debug(
164+
f"当前选中项:显示文本={text}, 数据={device}, 数据类型={type(device)}"
165+
)
166+
167+
# 如果 currentData() 返回 None,尝试通过索引获取数据
168+
if device is None:
169+
logger.warning("currentData() 返回 None,尝试通过索引获取数据")
170+
# 从文本中提取盘符
171+
import re
172+
173+
match = re.search(r"\(([A-Z]):\)", text)
174+
if match:
175+
device = match.group(1)
176+
logger.debug(f"从文本中提取到盘符:{device}")
160177

161178
try:
162179
# 允许来自USB设备的逻辑盘(包括部分显示为固定盘的外置硬盘)
163180
serial = get_volume_serial(device)
181+
logger.debug(f"尝试绑定设备:{device}, 序列号:{serial}")
164182
if not serial or serial == "00000000":
183+
logger.warning(f"设备序列号无效:{device}, serial={serial}")
165184
raise RuntimeError(
166185
get_content_name_async("basic_safety_settings", "usb_no_removable")
167186
)
@@ -192,14 +211,12 @@ def __bind(self):
192211
key_value=key_value,
193212
name=display_name,
194213
)
195-
if platform.system() == "Windows":
196-
logger.debug(f"绑定设备成功:{device}:")
197-
else:
198-
logger.debug(f"绑定设备成功:{device}")
214+
logger.debug(f"绑定设备成功:{device}")
199215
self._notify_success(
200216
f"{get_content_name_async('basic_safety_settings', 'usb_bind_success')}: {text}"
201217
)
202218
except Exception as e:
219+
logger.error(f"绑定设备失败:{device}, 错误:{e}")
203220
self._notify_error(str(e))
204221

205222
def __cancel(self):

0 commit comments

Comments
 (0)