#87 new: auto ask add system key (~/.ssh/id_rsa)

This commit is contained in:
lollipopkit
2023-08-04 21:46:44 +08:00
parent 60507ea4bc
commit 91967e6ce3
19 changed files with 129 additions and 76 deletions

View File

@@ -128,6 +128,12 @@ abstract class S {
/// **'Add private key'** /// **'Add private key'**
String get addPrivateKey; String get addPrivateKey;
/// No description provided for @addSystemPrivateKeyTip.
///
/// In en, this message translates to:
/// **'Currently don\'t have any private key, do you add the one that comes with the system (~/.ssh/id_rsa)?'**
String get addSystemPrivateKeyTip;
/// No description provided for @added2List. /// No description provided for @added2List.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -19,6 +19,9 @@ class SDe extends S {
@override @override
String get addPrivateKey => 'Private key hinzufügen'; String get addPrivateKey => 'Private key hinzufügen';
@override
String get addSystemPrivateKeyTip => 'Derzeit haben Sie keinen privaten Schlüssel, fügen Sie den Schlüssel hinzu, der mit dem System geliefert wird (~/.ssh/id_rsa)?';
@override @override
String get added2List => 'Zur Aufgabenliste hinzugefügt'; String get added2List => 'Zur Aufgabenliste hinzugefügt';

View File

@@ -19,6 +19,9 @@ class SEn extends S {
@override @override
String get addPrivateKey => 'Add private key'; String get addPrivateKey => 'Add private key';
@override
String get addSystemPrivateKeyTip => 'Currently don\'t have any private key, do you add the one that comes with the system (~/.ssh/id_rsa)?';
@override @override
String get added2List => 'Added to task list'; String get added2List => 'Added to task list';

View File

@@ -19,6 +19,9 @@ class SId extends S {
@override @override
String get addPrivateKey => 'Tambahkan kunci pribadi'; String get addPrivateKey => 'Tambahkan kunci pribadi';
@override
String get addSystemPrivateKeyTip => 'Saat ini tidak memiliki kunci privat, apakah Anda menambahkan kunci yang disertakan dengan sistem (~/.ssh/id_rsa)?';
@override @override
String get added2List => 'Ditambahkan ke Daftar Tugas'; String get added2List => 'Ditambahkan ke Daftar Tugas';

View File

@@ -19,6 +19,9 @@ class SZh extends S {
@override @override
String get addPrivateKey => '添加一个私钥'; String get addPrivateKey => '添加一个私钥';
@override
String get addSystemPrivateKeyTip => '当前没有任何私钥,是否添加系统自带的(~/.ssh/id_rsa';
@override @override
String get added2List => '已添加至任务列表'; String get added2List => '已添加至任务列表';
@@ -701,6 +704,9 @@ class SZhTw extends SZh {
@override @override
String get addPrivateKey => '新增一個私鑰'; String get addPrivateKey => '新增一個私鑰';
@override
String get addSystemPrivateKeyTip => '當前沒有任何私鑰,是否添加系統自帶的(~/.ssh/id_rsa';
@override @override
String get added2List => '已添加至任務列表'; String get added2List => '已添加至任務列表';

View File

@@ -77,13 +77,12 @@ String pathJoin(String path1, String path2) {
return path1 + (path1.endsWith('/') ? '' : '/') + path2; return path1 + (path1.endsWith('/') ? '' : '/') + path2;
} }
String getHome() { String? getHomeDir() {
String? home = ""; final envVars = Platform.environment;
Map<String, String> envVars = Platform.environment; if (isMacOS || isLinux) {
if (isMacOS ||isLinux) { return envVars['HOME'];
home = envVars['HOME'];
} else if (isWindows) { } else if (isWindows) {
home = envVars['UserProfile']; return envVars['UserProfile'];
} }
return home??""; return null;
} }

View File

@@ -32,14 +32,14 @@ enum GenSSHClientStatus {
} }
String getPrivateKey(String id) { String getPrivateKey(String id) {
final key = locator<PrivateKeyStore>().get(id); final pki = locator<PrivateKeyStore>().get(id);
if (key == null) { if (pki == null) {
throw SSHErr( throw SSHErr(
type: SSHErrType.noPrivateKey, type: SSHErrType.noPrivateKey,
message: 'key [$id] not found', message: 'key [$id] not found',
); );
} }
return key.privateKey; return pki.key;
} }
Future<SSHClient> genClient( Future<SSHClient> genClient(

View File

@@ -1,6 +1,3 @@
import 'dart:io';
import 'package:toolbox/core/utils/misc.dart' show getHome, pathJoin;
import 'package:toolbox/data/model/app/error.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
part 'private_key_info.g.dart'; part 'private_key_info.g.dart';
@@ -10,37 +7,25 @@ class PrivateKeyInfo {
@HiveField(0) @HiveField(0)
late String id; late String id;
@HiveField(1) @HiveField(1)
late String privateKey; late String key;
@Deprecated('Never use this field')
@HiveField(2) @HiveField(2)
late String password; late String password;
PrivateKeyInfo( PrivateKeyInfo({
this.id, required this.id,
this.privateKey, required this.key,
this.password, });
);
PrivateKeyInfo.fromJson(Map<String, dynamic> json) { PrivateKeyInfo.fromJson(Map<String, dynamic> json) {
id = json["id"].toString(); id = json["id"].toString();
privateKey = json["private_key"].toString(); key = json["private_key"].toString();
password = json["password"].toString();
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data["id"] = id; data["id"] = id;
data["private_key"] = privateKey; data["private_key"] = key;
data["password"] = password;
return data; return data;
} }
} }
class SystemPrivateKeyInfo extends PrivateKeyInfo {
SystemPrivateKeyInfo() : super("System private key", "", "");
Future getKey() async {
File idRsaFile = File(pathJoin(getHome(), ".ssh/id_rsa"));
if (!await idRsaFile.exists()) {
this.privateKey="";
}
this.privateKey= await idRsaFile.readAsString();
}
}

View File

@@ -17,9 +17,8 @@ class PrivateKeyInfoAdapter extends TypeAdapter<PrivateKeyInfo> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return PrivateKeyInfo( return PrivateKeyInfo(
fields[0] as String, id: fields[0] as String,
fields[1] as String, key: fields[1] as String,
fields[2] as String,
); );
} }
@@ -30,8 +29,9 @@ class PrivateKeyInfoAdapter extends TypeAdapter<PrivateKeyInfo> {
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
..write(obj.privateKey) ..write(obj.key)
..writeByte(2) ..writeByte(2)
// ignore: deprecated_member_use_from_same_package
..write(obj.password); ..write(obj.password);
} }

View File

@@ -4,29 +4,33 @@ import 'package:toolbox/data/store/private_key.dart';
import 'package:toolbox/locator.dart'; import 'package:toolbox/locator.dart';
class PrivateKeyProvider extends BusyProvider { class PrivateKeyProvider extends BusyProvider {
List<PrivateKeyInfo> get infos => _infos; List<PrivateKeyInfo> get pkis => _pkis;
final _store = locator<PrivateKeyStore>(); final _store = locator<PrivateKeyStore>();
late List<PrivateKeyInfo> _infos; late List<PrivateKeyInfo> _pkis;
void loadData() { void loadData() {
_infos = _store.fetch(); _pkis = _store.fetch();
} }
void addInfo(PrivateKeyInfo info) { void add(PrivateKeyInfo info) {
_infos.add(info); _pkis.add(info);
_store.put(info); _store.put(info);
notifyListeners(); notifyListeners();
} }
void delInfo(PrivateKeyInfo info) { void delete(PrivateKeyInfo info) {
_infos.removeWhere((e) => e.id == info.id); _pkis.removeWhere((e) => e.id == info.id);
_store.delete(info); _store.delete(info);
notifyListeners(); notifyListeners();
} }
void updateInfo(PrivateKeyInfo old, PrivateKeyInfo newInfo) { void update(PrivateKeyInfo old, PrivateKeyInfo newInfo) {
final idx = _infos.indexWhere((e) => e.id == old.id); final idx = _pkis.indexWhere((e) => e.id == old.id);
_infos[idx] = newInfo; if (idx == -1) {
_pkis.add(newInfo);
} else {
_pkis[idx] = newInfo;
}
_store.put(newInfo); _store.put(newInfo);
notifyListeners(); notifyListeners();
} }

View File

@@ -1,10 +1,7 @@
import 'dart:async';
import 'package:toolbox/core/persistant_store.dart'; import 'package:toolbox/core/persistant_store.dart';
import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:toolbox/data/model/server/private_key_info.dart';
import 'package:toolbox/core/utils/platform.dart';
class PrivateKeyStore extends PersistentStore { class PrivateKeyStore extends PersistentStore {
late SystemPrivateKeyInfo systemPrivateKeyInfo;
void put(PrivateKeyInfo info) { void put(PrivateKeyInfo info) {
box.put(info.id, info); box.put(info.id, info);
} }
@@ -18,19 +15,10 @@ class PrivateKeyStore extends PersistentStore {
ps.add(s); ps.add(s);
} }
} }
if (isLinux || isMacOS) {
SystemPrivateKeyInfo sysPk = SystemPrivateKeyInfo();
unawaited(sysPk.getKey());
systemPrivateKeyInfo = sysPk;
ps.add(sysPk);
}
return ps; return ps;
} }
PrivateKeyInfo? get(String? id) { PrivateKeyInfo? get(String? id) {
if (id == "System private key") {
return this.systemPrivateKeyInfo;
}
if (id == null) return null; if (id == null) return null;
return box.get(id); return box.get(id);
} }

View File

@@ -5,6 +5,7 @@
"add": "Neu", "add": "Neu",
"addAServer": "Server hinzufügen", "addAServer": "Server hinzufügen",
"addPrivateKey": "Private key hinzufügen", "addPrivateKey": "Private key hinzufügen",
"addSystemPrivateKeyTip": "Derzeit haben Sie keinen privaten Schlüssel, fügen Sie den Schlüssel hinzu, der mit dem System geliefert wird (~/.ssh/id_rsa)?",
"added2List": "Zur Aufgabenliste hinzugefügt", "added2List": "Zur Aufgabenliste hinzugefügt",
"all": "Alle", "all": "Alle",
"alreadyLastDir": "Bereits im letzten Verzeichnis.", "alreadyLastDir": "Bereits im letzten Verzeichnis.",

View File

@@ -5,6 +5,7 @@
"add": "Add", "add": "Add",
"addAServer": "add a server", "addAServer": "add a server",
"addPrivateKey": "Add private key", "addPrivateKey": "Add private key",
"addSystemPrivateKeyTip": "Currently don't have any private key, do you add the one that comes with the system (~/.ssh/id_rsa)?",
"added2List": "Added to task list", "added2List": "Added to task list",
"all": "All", "all": "All",
"alreadyLastDir": "Already in last directory.", "alreadyLastDir": "Already in last directory.",

View File

@@ -5,6 +5,7 @@
"add": "Menambahkan", "add": "Menambahkan",
"addAServer": "tambahkan server", "addAServer": "tambahkan server",
"addPrivateKey": "Tambahkan kunci pribadi", "addPrivateKey": "Tambahkan kunci pribadi",
"addSystemPrivateKeyTip": "Saat ini tidak memiliki kunci privat, apakah Anda menambahkan kunci yang disertakan dengan sistem (~/.ssh/id_rsa)?",
"added2List": "Ditambahkan ke Daftar Tugas", "added2List": "Ditambahkan ke Daftar Tugas",
"all": "Semua", "all": "Semua",
"alreadyLastDir": "Sudah di direktori terakhir.", "alreadyLastDir": "Sudah di direktori terakhir.",

View File

@@ -5,6 +5,7 @@
"add": "新增", "add": "新增",
"addAServer": "添加服务器", "addAServer": "添加服务器",
"addPrivateKey": "添加一个私钥", "addPrivateKey": "添加一个私钥",
"addSystemPrivateKeyTip": "当前没有任何私钥,是否添加系统自带的(~/.ssh/id_rsa",
"added2List": "已添加至任务列表", "added2List": "已添加至任务列表",
"all": "所有", "all": "所有",
"alreadyLastDir": "已经是最上层目录了", "alreadyLastDir": "已经是最上层目录了",

View File

@@ -5,6 +5,7 @@
"add": "新增", "add": "新增",
"addAServer": "新增服務器", "addAServer": "新增服務器",
"addPrivateKey": "新增一個私鑰", "addPrivateKey": "新增一個私鑰",
"addSystemPrivateKeyTip": "當前沒有任何私鑰,是否添加系統自帶的(~/.ssh/id_rsa",
"added2List": "已添加至任務列表", "added2List": "已添加至任務列表",
"all": "所有", "all": "所有",
"alreadyLastDir": "已經是最上層目錄了", "alreadyLastDir": "已經是最上層目錄了",

View File

@@ -74,7 +74,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
IconButton( IconButton(
tooltip: _s.delete, tooltip: _s.delete,
onPressed: () { onPressed: () {
_provider.delInfo(widget.info!); _provider.delete(widget.info!);
context.pop(); context.pop();
}, },
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete))
@@ -100,9 +100,9 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
setState(() { setState(() {
_loading = centerSizedLoading; _loading = centerSizedLoading;
}); });
final info = PrivateKeyInfo(name, key, ''); final info = PrivateKeyInfo(id: name, key: key);
try { try {
info.privateKey = await compute(decyptPem, [key, pwd]); info.key = await compute(decyptPem, [key, pwd]);
} catch (e) { } catch (e) {
showSnackBar(context, Text(e.toString())); showSnackBar(context, Text(e.toString()));
rethrow; rethrow;
@@ -112,9 +112,9 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
}); });
} }
if (widget.info != null) { if (widget.info != null) {
_provider.updateInfo(widget.info!, info); _provider.update(widget.info!, info);
} else { } else {
_provider.addInfo(info); _provider.add(info);
} }
context.pop(); context.pop();
}, },
@@ -194,8 +194,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
Future<void> afterFirstLayout(BuildContext context) async { Future<void> afterFirstLayout(BuildContext context) async {
if (widget.info != null) { if (widget.info != null) {
_nameController.text = widget.info!.id; _nameController.text = widget.info!.id;
_keyController.text = widget.info!.privateKey; _keyController.text = widget.info!.key;
_pwdController.text = widget.info!.password;
} else { } else {
final clipdata = ((await Clipboard.getData(_format))?.text ?? '').trim(); final clipdata = ((await Clipboard.getData(_format))?.text ?? '').trim();
if (clipdata.startsWith('-----BEGIN') && clipdata.endsWith('-----')) { if (clipdata.startsWith('-----BEGIN') && clipdata.endsWith('-----')) {

View File

@@ -1,8 +1,17 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:toolbox/core/extension/navigator.dart';
import 'package:toolbox/core/utils/ui.dart';
import 'package:toolbox/data/store/private_key.dart';
import 'package:toolbox/locator.dart';
import '../../../core/route.dart'; import '../../../core/route.dart';
import '../../../core/utils/misc.dart';
import '../../../core/utils/platform.dart';
import '../../../data/model/server/private_key_info.dart';
import '../../../data/provider/private_key.dart'; import '../../../data/provider/private_key.dart';
import '../../../data/res/ui.dart'; import '../../../data/res/ui.dart';
import 'edit.dart'; import 'edit.dart';
@@ -24,6 +33,13 @@ class _PrivateKeyListState extends State<PrivateKeysListPage> {
_s = S.of(context)!; _s = S.of(context)!;
} }
@override
void initState() {
super.initState();
autoAddSystemPriavteKey();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -44,21 +60,21 @@ class _PrivateKeyListState extends State<PrivateKeysListPage> {
Widget _buildBody() { Widget _buildBody() {
return Consumer<PrivateKeyProvider>( return Consumer<PrivateKeyProvider>(
builder: (_, key, __) { builder: (_, key, __) {
if (key.infos.isEmpty) { if (key.pkis.isEmpty) {
return Center( return Center(
child: Text(_s.noSavedPrivateKey), child: Text(_s.noSavedPrivateKey),
); );
} }
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
itemCount: key.infos.length, itemCount: key.pkis.length,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return RoundRectCard( return RoundRectCard(
ListTile( ListTile(
title: Text(key.infos[idx].id), title: Text(key.pkis[idx].id),
trailing: TextButton( trailing: TextButton(
onPressed: () => AppRoute( onPressed: () => AppRoute(
PrivateKeyEditPage(info: key.infos[idx]), PrivateKeyEditPage(info: key.pkis[idx]),
'private key edit page', 'private key edit page',
).go(context), ).go(context),
child: Text(_s.edit), child: Text(_s.edit),
@@ -70,4 +86,40 @@ class _PrivateKeyListState extends State<PrivateKeysListPage> {
}, },
); );
} }
void autoAddSystemPriavteKey() {
final store = locator<PrivateKeyStore>();
// Only trigger on desktop platform and no private key saved
if (isDesktop && store.box.keys.isEmpty) {
final home = getHomeDir();
if (home == null) return;
final idRsaFile = File(pathJoin(home, '.ssh/id_rsa'));
if (!idRsaFile.existsSync()) return;
final sysPk = PrivateKeyInfo(
id: 'system',
key: idRsaFile.readAsStringSync(),
);
showRoundDialog(
context: context,
title: Text(_s.attention),
child: Text(_s.addSystemPrivateKeyTip),
actions: [
TextButton(
onPressed: () {
context.pop();
AppRoute(
PrivateKeyEditPage(info: sysPk),
'private key edit page',
).go(context);
},
child: Text(_s.ok),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(_s.cancel),
),
],
);
}
}
} }

View File

@@ -200,17 +200,17 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
Widget _buildKeyAuth() { Widget _buildKeyAuth() {
return Consumer<PrivateKeyProvider>( return Consumer<PrivateKeyProvider>(
builder: (_, key, __) { builder: (_, key, __) {
for (var item in key.infos) { for (var item in key.pkis) {
if (item.id == widget.spi?.pubKeyId) { if (item.id == widget.spi?.pubKeyId) {
_pubKeyIndex ??= key.infos.indexOf(item); _pubKeyIndex ??= key.pkis.indexOf(item);
} }
} }
final tiles = key.infos final tiles = key.pkis
.map( .map(
(e) => ListTile( (e) => ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text(e.id, textAlign: TextAlign.start), title: Text(e.id, textAlign: TextAlign.start),
trailing: _buildRadio(key.infos.indexOf(e), e), trailing: _buildRadio(key.pkis.indexOf(e), e),
), ),
) )
.toList(); .toList();