초기화 [앱 설정 전체] 는 이 판이 모르는 설정 키도 지움 · 다른 판의 비밀 값은 [저장된 실제 비밀번호 · API 키] 를 골랐을 때만 (관리자 판단 10/21)
- 까닭 (3a): 사용자가 직접 "전체" 를 골랐고, 새 판으로 돌아갔을 때 예전 값이 되살아나면 더 헷갈린다.
모르는 키를 남기는 규칙 (40) 은 보통 저장 때만 - resetAllSettings 는 AppSettings()..extra = {} 로 (뜻을 드러냄)
- 안전 저장소: 이 판이 맡지 않는 열쇠 (os. · dav. · ai. · sftp. · smb. · ftp. · sec. 밖 = 다른 판이 쓴 것) 는
[저장된 실제 비밀번호 · API 키] 를 골랐을 때만 지움 (SettingsStore.clearForeignSecrets, clearStoredSecrets 끝에서). 마스터 · 최상 (sec.) 은 남김
- 초기화 창 설명에 한 줄씩: "다른 판에서 넣은 비밀번호 · 키도 지웁니다." · "다른 판에서 만든 설정도 지웁니다." (en · ja · zh, 새 줄로)
- 시험: master_lock_test - 모르는 키 · 다른 판 열쇠가 있는 설정 → 보통 저장은 남김 → 전체 초기화는 키 없음 · 열쇠는 남음 →
저장된 비밀번호 지우기는 열쇠도 지움 · sec. 남김. 초기화 창 캡처 다시 (ko · en · ja × 412 · 960, 넘침 없음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rc2
parent
5d8c79260e
commit
27259af83c
|
|
@ -974,6 +974,8 @@
|
|||
"다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "When you drop onto the other pane. Either way, the copy/move confirmation (same names etc.) still appears",
|
||||
"다른 창으로 복사": "Copy to other pane",
|
||||
"다른 창으로 이동": "Move to other pane",
|
||||
"다른 판에서 넣은 비밀번호 · 키도 지웁니다.": "Passwords and keys saved by other versions are deleted too.",
|
||||
"다른 판에서 만든 설정도 지웁니다.": "Settings made by other versions are deleted too.",
|
||||
"다른 폴더": "Other folder",
|
||||
"다른 폴더로 이동": "Move to another folder",
|
||||
"다른 프로그램이 쓰고 있거나 권한이 없을 수 있습니다. 나머지는 지웠습니다.": "Another program may be using them, or you may lack permission. The rest were deleted.",
|
||||
|
|
|
|||
|
|
@ -974,6 +974,8 @@
|
|||
"다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "もう一方のウィンドウにドロップしたとき。どちらでもコピー・移動の確認 (同じ名前など) は表示されます",
|
||||
"다른 창으로 복사": "もう一方のペインへコピー",
|
||||
"다른 창으로 이동": "もう一方のペインへ移動",
|
||||
"다른 판에서 넣은 비밀번호 · 키도 지웁니다.": "他のバージョンで保存したパスワード・キーも消します。",
|
||||
"다른 판에서 만든 설정도 지웁니다.": "他のバージョンで作った設定も消します。",
|
||||
"다른 폴더": "別のフォルダー",
|
||||
"다른 폴더로 이동": "他のフォルダーに移動",
|
||||
"다른 프로그램이 쓰고 있거나 권한이 없을 수 있습니다. 나머지는 지웠습니다.": "他のプログラムが使用中か、権限がない可能性があります。残りは削除しました。",
|
||||
|
|
|
|||
|
|
@ -974,6 +974,8 @@
|
|||
"다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "拖放到另一个窗格时。无论哪种,都会显示复制/移动确认 (同名等)",
|
||||
"다른 창으로 복사": "复制到另一窗格",
|
||||
"다른 창으로 이동": "移动到另一窗格",
|
||||
"다른 판에서 넣은 비밀번호 · 키도 지웁니다.": "其他版本保存的密码和密钥也会删除。",
|
||||
"다른 판에서 만든 설정도 지웁니다.": "其他版本创建的设置也会删除。",
|
||||
"다른 폴더": "其他文件夹",
|
||||
"다른 폴더로 이동": "移动到其他文件夹",
|
||||
"다른 프로그램이 쓰고 있거나 권한이 없을 수 있습니다. 나머지는 지웠습니다.": "可能有其他程序正在使用或没有权限。其余已删除。",
|
||||
|
|
|
|||
|
|
@ -1682,12 +1682,31 @@ class SettingsStore {
|
|||
});
|
||||
}
|
||||
|
||||
/// 이 판의 설정이 맡는 안전 저장소 열쇠 (그 밖은 마스터 · 최상 (sec.) 이나 이 판이 모르는 것 - 새 판이 쓴 열쇠)
|
||||
static bool ownsSecret(String k) =>
|
||||
k.startsWith('os.') || k.startsWith('dav.') || k.startsWith('ai.') || k.startsWith('sftp.') || k.startsWith('smb.') || k.startsWith('ftp.');
|
||||
|
||||
/// 관리자 판단 (10/21): 이 판이 모르는 비밀 값 (다른 판이 쓴 열쇠) 을 지운다 - 사용자가 초기화 창에서
|
||||
/// [저장된 실제 비밀번호 · API 키] 를 골랐을 때만 부른다 (고르지 않았으면 남김). 마스터 · 최상 (sec.) 은 [마스터 비밀번호] 체크가 정해 여기서 남긴다.
|
||||
/// 지운 열쇠 수
|
||||
Future<int> clearForeignSecrets() async {
|
||||
final all = await secrets.readAll();
|
||||
var n = 0;
|
||||
for (final k in all.keys) {
|
||||
if (k.startsWith('sec.') || ownsSecret(k)) continue;
|
||||
await secrets.delete(k);
|
||||
_known.remove(k);
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/// 바뀐 비밀 값만 안전 저장소에 쓴다 (빈 값 · 지운 서버는 저장소에서도 지움). 저장소에 넣지 못한 열쇠를 돌려준다.
|
||||
Future<Set<String>> _writeSecrets(Map<String, String> values) async {
|
||||
if (!_secretsOk) return {...values.keys.where((k) => _known[k] != values[k])};
|
||||
final unsaved = <String>{};
|
||||
for (final k in {..._known.keys, ...values.keys}) {
|
||||
if (!k.startsWith('os.') && !k.startsWith('dav.') && !k.startsWith('ai.') && !k.startsWith('sftp.') && !k.startsWith('smb.') && !k.startsWith('ftp.')) continue;
|
||||
if (!ownsSecret(k)) continue;
|
||||
final v = values[k];
|
||||
if (v == _known[k]) continue;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -147,11 +147,17 @@ Future<void> clearStoredSecrets(AppController c) => c.updateSettings((s) => s
|
|||
// AI 서비스 API 키도 (문구 "비밀번호 · API 키" 와 같게 - 예전에는 남았다, 관리자 결정 10/17)
|
||||
..aiServices = [for (final x in s.aiServices) x.copyWith(apiKey: '')]
|
||||
..openSubtitlesKey = ''
|
||||
..openSubtitlesPassword = '');
|
||||
..openSubtitlesPassword = '').then((_) async {
|
||||
// 관리자 판단 (10/21): 다른 판 (새 판) 이 안전 저장소에 넣은 이 판이 모르는 비밀번호 · 키도 (마스터 · 최상은 남김)
|
||||
await c.settingsStore?.clearForeignSecrets();
|
||||
});
|
||||
|
||||
/// 앱 설정 전체를 처음 상태로 (다음에 켤 때부터 모두 적용되도록 앱을 다시 켜 달라고 한다)
|
||||
/// 관리자 판단 (10/21): 이 판이 모르는 설정 키 (다른 판이 쓴 것) 도 지운다 - 사용자가 "전체" 를 골랐고, 새 판으로 돌아갔을 때
|
||||
/// 예전 값이 되살아나면 더 헷갈린다. 모르는 키를 남기는 규칙 (40) 은 보통 저장 때만 (새 AppSettings 는 [AppSettings.extra] 가 비어 있다).
|
||||
/// 안전 저장소의 이 판이 모르는 열쇠는 [저장된 실제 비밀번호 · API 키] 를 골랐을 때만 지운다 ([clearStoredSecrets])
|
||||
Future<void> resetAllSettings(AppController c) async {
|
||||
c.settings = AppSettings();
|
||||
c.settings = AppSettings()..extra = {};
|
||||
await c.updateSettings((_) {});
|
||||
}
|
||||
|
||||
|
|
@ -198,14 +204,17 @@ Future<void> showSuperReset(BuildContext context, AppController c, MasterLock lo
|
|||
onChanged: (v) => set(() => secrets = v!),
|
||||
title: Text(tr('저장된 실제 비밀번호 · API 키')),
|
||||
// 145: 10/18 에 생긴 SFTP · SMB · FTP 비밀번호 · 키도 지운다 (clearStoredSecrets)
|
||||
subtitle: Text(tr('WebDAV · SFTP · SMB · FTP · OpenSubtitles 비밀번호, SFTP 키, API 키. 서버 목록과 아이디는 남깁니다')),
|
||||
subtitle: Text('${tr('WebDAV · SFTP · SMB · FTP · OpenSubtitles 비밀번호, SFTP 키, API 키. 서버 목록과 아이디는 남깁니다')}\n'
|
||||
'${tr('다른 판에서 넣은 비밀번호 · 키도 지웁니다.')}'),
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: settings,
|
||||
onChanged: (v) => set(() => settings = v!),
|
||||
title: Text(tr('앱 설정 전체 (처음 상태로)')),
|
||||
// 145: 파일 탐색기 즐겨찾기 (73) 는 설정 안에 있어 함께 지워진다 - 예전 글은 "즐겨찾기는 그대로"
|
||||
subtitle: Text(tr('서버 목록 (WebDAV · SFTP · SMB · FTP) 과 그 비밀번호 · API 키, 파일 탐색기 즐겨찾기도 지워집니다. 동영상 목록 · 웹 즐겨찾기 · 파일은 그대로입니다')),
|
||||
subtitle: Text('${tr('서버 목록 (WebDAV · SFTP · SMB · FTP) 과 그 비밀번호 · API 키, 파일 탐색기 즐겨찾기도 지워집니다. 동영상 목록 · 웹 즐겨찾기 · 파일은 그대로입니다')}\n'
|
||||
// 관리자 판단 (10/21): 이 판이 모르는 설정 키도 지운다 (resetAllSettings)
|
||||
'${tr('다른 판에서 만든 설정도 지웁니다.')}'),
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -298,6 +298,34 @@ void main() {
|
|||
expect((c.settings.webdavServers.single.user, c.settings.sftpServers.single.user, c.settings.aiServices.single.url), ('u', 'u', 'https://ai'));
|
||||
});
|
||||
|
||||
test('관리자 판단 (10/21): "앱 설정 전체" 는 이 판이 모르는 설정 키도 지우고, 다른 판의 비밀 값은 "저장된 비밀번호" 를 골랐을 때만', () async {
|
||||
final tmp = Directory.systemTemp.createTempSync('jj_master_full_');
|
||||
addTearDown(() => tmp.deleteSync(recursive: true));
|
||||
final file = File(p.join(tmp.path, 'settings.json'))
|
||||
..writeAsStringSync(jsonEncode({'explorerLayout': 'single', 'zz_new_version': {'keep': 1}}));
|
||||
final secrets = MemorySecretStore()
|
||||
..values['nfs.pw.n1'] = 'new-version-pw' // 다른 (새) 판이 쓴 열쇠
|
||||
..values['sec.master'] = 'hash';
|
||||
final store = SettingsStore(file.path, secrets);
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()),
|
||||
settingsStore: store);
|
||||
await c.init();
|
||||
// 보통 저장: 모르는 키 · 다른 판의 열쇠 남음 (40)
|
||||
await c.updateSettings((s) => s.explorerLayout = 'dual');
|
||||
Map<String, dynamic> saved() => jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;
|
||||
expect(saved()['zz_new_version'], {'keep': 1});
|
||||
expect(secrets.values['nfs.pw.n1'], 'new-version-pw');
|
||||
// 앱 설정 전체: 모르는 키 없음 · 다른 판의 열쇠는 아직 남음 (비밀번호 체크를 안 함)
|
||||
await resetAllSettings(c);
|
||||
expect(saved().containsKey('zz_new_version'), isFalse);
|
||||
expect(saved()['explorerLayout'], AppSettings().explorerLayout);
|
||||
expect(secrets.values['nfs.pw.n1'], 'new-version-pw', reason: '[저장된 실제 비밀번호] 를 고르지 않으면 남김');
|
||||
// 저장된 실제 비밀번호 · API 키: 다른 판의 열쇠도 지움 · 마스터 (sec.) 는 남김
|
||||
await clearStoredSecrets(c);
|
||||
expect(secrets.values.containsKey('nfs.pw.n1'), isFalse);
|
||||
expect(secrets.values['sec.master'], 'hash');
|
||||
});
|
||||
|
||||
test('최상으로 "앱 설정 전체" 를 지우면 처음 상태', () async {
|
||||
final c = controller()..settings.explorerLayout = 'single';
|
||||
await resetAllSettings(c);
|
||||
|
|
|
|||
Loading…
Reference in New Issue