From 98c92a3fe19398c84db56a32e95e1110bbd45732 Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Mon, 19 Oct 2020 14:52:24 +0200 Subject: [PATCH] Handle new version of git_bindings This changes the way the SSH keys are managed, they are no longer managed by the git_bindings plugin and are instead just passed as parameters. They are now saved in shared_prefs. This allows us to easily have multiple ssh keys. It also allows us to store the ssh keys in a more secure storage location in the future. --- ios/Runner/AppDelegate.m | 172 +++++++-------------------- lib/core/git_repo.dart | 14 ++- lib/screens/settings_git_remote.dart | 37 +++--- lib/screens/settings_screen.dart | 3 +- lib/settings.dart | 38 +++++- lib/setup/screens.dart | 46 +++++-- lib/ssh/keygen.dart | 25 ++-- pubspec.yaml | 2 +- 8 files changed, 166 insertions(+), 171 deletions(-) diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m index e2c71bec..02b7eecb 100644 --- a/ios/Runner/AppDelegate.m +++ b/ios/Runner/AppDelegate.m @@ -40,20 +40,7 @@ static FlutterMethodChannel* gitChannel = 0; } } - NSString* filesDir = [self getApplicationDocumentsDirectory]; - - NSArray *sshPublicComponents = [NSArray arrayWithObjects:filesDir, @"ssh", @"id_rsa.pub", nil]; - NSString *sshPublicKeyString = [NSString pathWithComponents:sshPublicComponents]; - - NSArray *sshPrivateComponents = [NSArray arrayWithObjects:filesDir, @"ssh", @"id_rsa", nil]; - NSString *sshPrivateKeyString = [NSString pathWithComponents:sshPrivateComponents]; - - if ([@"gitClone" isEqualToString:method]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self handleMethodCallAsync:call result:result]; - }); - } - else if ([@"gitFetch" isEqualToString:method]) { + if ([@"gitFetch" isEqualToString:method]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self handleMethodCallAsync:call result:result]; }); @@ -160,73 +147,6 @@ static FlutterMethodChannel* gitChannel = 0; return; } } - else if ([@"getSSHPublicKey" isEqualToString:method]) { - NSError *error = nil; - NSString *content = [NSString stringWithContentsOfFile:sshPublicKeyString - encoding:NSUTF8StringEncoding error:&error]; - - if (error != nil) { - result([FlutterError errorWithCode:@"FAILED" - message:[error localizedDescription] details:nil]); - return; - } - if (content == nil || [content length] == 0) { - result([FlutterError errorWithCode:@"FAILED" - message:@"PublicKey File not found" details:nil]); - return; - } - - result(content); - } - else if ([@"setSshKeys" isEqualToString:method]) { - NSString *publicKey = arguments[@"publicKey"]; - NSString *privateKey = arguments[@"privateKey"]; - - if (publicKey == nil || [publicKey length] == 0) { - result([FlutterError errorWithCode:@"InvalidParams" - message:@"Invalid publicKey" details:nil]); - return; - } - if (privateKey == nil || [privateKey length] == 0) { - result([FlutterError errorWithCode:@"InvalidParams" - message:@"Invalid privateKey" details:nil]); - return; - } - - NSArray *sshComponents = [NSArray arrayWithObjects:filesDir, @"ssh", nil]; - NSString* sshDirPath = [NSString pathWithComponents:sshComponents]; - - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:sshDirPath - withIntermediateDirectories:YES - attributes:nil - error:&error]; - - if (error != nil) { - NSLog(@"Create directory error: %@", error); - result([FlutterError errorWithCode:@"FAILED" - message:[error localizedDescription] details:nil]); - return; - } - - [publicKey writeToFile:sshPublicKeyString atomically:YES encoding:NSUTF8StringEncoding error:&error]; - - if (error != nil) { - result([FlutterError errorWithCode:@"FAILED" - message:[error localizedDescription] details:nil]); - return; - } - - [privateKey writeToFile:sshPrivateKeyString atomically:YES encoding:NSUTF8StringEncoding error:&error]; - - if (error != nil) { - result([FlutterError errorWithCode:@"FAILED" - message:[error localizedDescription] details:nil]); - return; - } - - result(@YES); - } else { NSLog(@"Not Implemented"); result(FlutterMethodNotImplemented); @@ -262,10 +182,6 @@ bool handleError(FlutterResult result, int err) { return false; } -- (NSString*)getApplicationDocumentsDirectory { - return GetDirectoryOfType(NSDocumentDirectory); -} - - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *method = [call method]; NSDictionary *arguments = [call arguments]; @@ -277,47 +193,36 @@ bool handleError(FlutterResult result, int err) { NSLog(@". privateKey: "); continue; } + if ([key isEqualToString:@"password"]) { + NSLog(@". password: "); + continue; + } NSLog(@". %@: %@", key, [arguments objectForKey:key]); } } - NSString* filesDir = [self getApplicationDocumentsDirectory]; - - NSArray *sshPublicComponents = [NSArray arrayWithObjects:filesDir, @"ssh", @"id_rsa.pub", nil]; - NSString *sshPublicKeyString = [NSString pathWithComponents:sshPublicComponents]; - const char *sshPublicKeyPath = [sshPublicKeyString UTF8String]; - - NSArray *sshPrivateComponents = [NSArray arrayWithObjects:filesDir, @"ssh", @"id_rsa", nil]; - NSString *sshPrivateKeyString = [NSString pathWithComponents:sshPrivateComponents]; - const char *sshPrivateKeyPath = [sshPrivateKeyString UTF8String]; - - if ([@"gitClone" isEqualToString:method]) { - NSString *cloneUrl = arguments[@"cloneUrl"]; - NSString *folderPath = arguments[@"folderPath"]; - - if (cloneUrl == nil || [cloneUrl length] == 0) { - result([FlutterError errorWithCode:@"InvalidParams" - message:@"Invalid cloneUrl" details:nil]); - return; - } - if (folderPath == nil || [folderPath length] == 0) { - result([FlutterError errorWithCode:@"InvalidParams" - message:@"Invalid folderPath" details:nil]); - return; - } - - gj_set_ssh_keys_paths((char*) sshPublicKeyPath, (char*) sshPrivateKeyPath, ""); - - int err = gj_git_clone([cloneUrl UTF8String], [folderPath UTF8String]); - if (!handleError(result, err)) { - result(@YES); - return; - } - } - else if ([@"gitFetch" isEqualToString:method]) { + if ([@"gitFetch" isEqualToString:method]) { NSString *folderPath = arguments[@"folderPath"]; NSString *remote = arguments[@"remote"]; + NSString *publicKey = arguments[@"publicKey"]; + NSString *privateKey = arguments[@"privateKey"]; + NSString *password = arguments[@"password"]; + if (publicKey == nil || [publicKey length] == 0) { + result([FlutterError errorWithCode:@"InvalidParams" + message:@"Invalid publicKey" details:nil]); + return; + } + if (privateKey == nil || [privateKey length] == 0) { + result([FlutterError errorWithCode:@"InvalidParams" + message:@"Invalid privateKey" details:nil]); + return; + } + if (password == nil || [privateKey length] == 0) { + result([FlutterError errorWithCode:@"InvalidParams" + message:@"Invalid password" details:nil]); + return; + } if (folderPath == nil || [folderPath length] == 0) { result([FlutterError errorWithCode:@"InvalidParams" @@ -330,9 +235,7 @@ bool handleError(FlutterResult result, int err) { return; } - gj_set_ssh_keys_paths((char*) sshPublicKeyPath, (char*) sshPrivateKeyPath, ""); - - int err = gj_git_fetch([folderPath UTF8String], [remote UTF8String]); + int err = gj_git_fetch([folderPath UTF8String], [remote UTF8String], [publicKey UTF8String], [privateKey UTF8String], [password UTF8String]); if (!handleError(result, err)) { result(@YES); return; @@ -365,8 +268,6 @@ bool handleError(FlutterResult result, int err) { return; } - gj_set_ssh_keys_paths((char*) sshPublicKeyPath, (char*) sshPrivateKeyPath, ""); - int err = gj_git_merge([folderPath UTF8String], [branch UTF8String], [authorName UTF8String], [authorEmail UTF8String]); if (!handleError(result, err)) { result(@YES); @@ -376,6 +277,25 @@ bool handleError(FlutterResult result, int err) { else if ([@"gitPush" isEqualToString:method]) { NSString *folderPath = arguments[@"folderPath"]; NSString *remote = arguments[@"remote"]; + NSString *publicKey = arguments[@"publicKey"]; + NSString *privateKey = arguments[@"privateKey"]; + NSString *password = arguments[@"password"]; + + if (publicKey == nil || [publicKey length] == 0) { + result([FlutterError errorWithCode:@"InvalidParams" + message:@"Invalid publicKey" details:nil]); + return; + } + if (privateKey == nil || [privateKey length] == 0) { + result([FlutterError errorWithCode:@"InvalidParams" + message:@"Invalid privateKey" details:nil]); + return; + } + if (password == nil || [privateKey length] == 0) { + result([FlutterError errorWithCode:@"InvalidParams" + message:@"Invalid password" details:nil]); + return; + } if (folderPath == nil || [folderPath length] == 0) { result([FlutterError errorWithCode:@"InvalidParams" @@ -388,9 +308,7 @@ bool handleError(FlutterResult result, int err) { return; } - gj_set_ssh_keys_paths((char*) sshPublicKeyPath, (char*) sshPrivateKeyPath, ""); - - int err = gj_git_push([folderPath UTF8String], [remote UTF8String]); + int err = gj_git_push([folderPath UTF8String], [remote UTF8String], [publicKey UTF8String], [privateKey UTF8String], [password UTF8String]); if (!handleError(result, err)) { result(@YES); return; diff --git a/lib/core/git_repo.dart b/lib/core/git_repo.dart index 0a36c2d6..e6ed910a 100644 --- a/lib/core/git_repo.dart +++ b/lib/core/git_repo.dart @@ -171,7 +171,12 @@ class GitNoteRepository { Future fetch() async { try { - await _gitRepo.fetch("origin"); + await _gitRepo.fetch( + remote: "origin", + publicKey: settings.sshPublicKey, + privateKey: settings.sshPrivateKey, + password: settings.sshPassword, + ); } on GitException catch (ex, stackTrace) { Log.e("GitPull Failed", ex: ex, stacktrace: stackTrace); } @@ -210,7 +215,12 @@ class GitNoteRepository { } catch (_) {} try { - await _gitRepo.push("origin"); + await _gitRepo.push( + remote: "origin", + publicKey: settings.sshPublicKey, + privateKey: settings.sshPrivateKey, + password: settings.sshPassword, + ); } on GitException catch (ex, stackTrace) { if (ex.cause == 'cannot push non-fastforwardable reference') { await fetch(); diff --git a/lib/screens/settings_git_remote.dart b/lib/screens/settings_git_remote.dart index dc12a09e..978a86bc 100644 --- a/lib/screens/settings_git_remote.dart +++ b/lib/screens/settings_git_remote.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:git_bindings/git_bindings.dart' as git_bindings; import 'package:path/path.dart' as p; import 'package:provider/provider.dart'; @@ -18,25 +17,16 @@ import 'package:gitjournal/utils.dart'; import 'package:gitjournal/utils/logger.dart'; class GitRemoteSettingsScreen extends StatefulWidget { + final String sshPublicKey; + + GitRemoteSettingsScreen(this.sshPublicKey); + @override _GitRemoteSettingsScreenState createState() => _GitRemoteSettingsScreenState(); } class _GitRemoteSettingsScreenState extends State { - String publicKey = ""; - - @override - void initState() { - super.initState(); - git_bindings.getSSHPublicKey().then((String val) { - if (!mounted) return; - setState(() { - publicKey = val; - }); - }); - } - @override Widget build(BuildContext context) { var textTheme = Theme.of(context).textTheme; @@ -50,7 +40,7 @@ class _GitRemoteSettingsScreenState extends State { textAlign: TextAlign.left, ), const SizedBox(height: 16.0), - PublicKeyWidget(publicKey), + PublicKeyWidget(widget.sshPublicKey), const SizedBox(height: 16.0), const Divider(), Builder( @@ -104,7 +94,7 @@ class _GitRemoteSettingsScreenState extends State { } void _copyKeyToClipboard(BuildContext context) { - Clipboard.setData(ClipboardData(text: publicKey)); + Clipboard.setData(ClipboardData(text: widget.sshPublicKey)); showSnackbar(context, tr('setup.sshKey.copied')); } @@ -114,12 +104,15 @@ class _GitRemoteSettingsScreenState extends State { "-" + DateTime.now().toIso8601String().substring(0, 10); // only the date - generateSSHKeys(comment: comment).then((String publicKey) { - setState(() { - this.publicKey = publicKey; - Log.d("PublicKey: " + publicKey); - _copyKeyToClipboard(context); - }); + generateSSHKeys(comment: comment).then((SshKey sshKey) { + var settings = Provider.of(context, listen: false); + settings.sshPublicKey = sshKey.publicKey; + settings.sshPrivateKey = sshKey.publicKey; + settings.sshPassword = sshKey.password; + settings.save(); + + Log.d("PublicKey: " + sshKey.publicKey); + _copyKeyToClipboard(context); }); } diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index faddcece..b2b252e6 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -222,7 +222,8 @@ class SettingsListState extends State { subtitle: Text(tr("settings.gitRemote.subtitle")), onTap: () { var route = MaterialPageRoute( - builder: (context) => GitRemoteSettingsScreen(), + builder: (context) => + GitRemoteSettingsScreen(settings.sshPublicKey), settings: const RouteSettings(name: '/settings/gitRemote'), ); Navigator.of(context).push(route); diff --git a/lib/settings.dart b/lib/settings.dart index 9242503f..7cc87a25 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -64,6 +64,10 @@ class Settings extends ChangeNotifier { bool storeInternally = true; String storageLocation = ""; + String sshPublicKey = ""; + String sshPrivateKey = ""; + String sshPassword = ""; + void load(SharedPreferences pref) { gitAuthor = pref.getString("gitAuthor") ?? gitAuthor; gitAuthorEmail = pref.getString("gitAuthorEmail") ?? gitAuthorEmail; @@ -130,6 +134,10 @@ class Settings extends ChangeNotifier { // From AppState folderName = pref.getString("remoteGitRepoPath") ?? folderName; + sshPublicKey = pref.getString("sshPublicKey") ?? sshPublicKey; + sshPrivateKey = pref.getString("sshPrivateKey") ?? sshPrivateKey; + sshPassword = pref.getString("sshPassword") ?? sshPassword; + bottomMenuBar = pref.getBool("bottomMenuBar") ?? bottomMenuBar; storeInternally = pref.getBool("storeInternally") ?? storeInternally; storageLocation = pref.getString("storageLocation") ?? ""; @@ -213,6 +221,10 @@ class Settings extends ChangeNotifier { _setString( pref, "storageLocation", storageLocation, defaultSet.storageLocation); + _setString(pref, "sshPublicKey", sshPublicKey, defaultSet.sshPublicKey); + _setString(pref, "sshPrivateKey", sshPrivateKey, defaultSet.sshPrivateKey); + _setString(pref, "sshPassword", sshPassword, defaultSet.sshPassword); + pref.setInt("settingsVersion", version); pref.setString("remoteGitRepoPath", folderName); @@ -297,6 +309,7 @@ class Settings extends ChangeNotifier { 'bottomMenuBar': bottomMenuBar.toString(), 'storeInternally': storeInternally.toString(), 'storageLocation': storageLocation, + 'sshPublicKey': sshPublicKey, }; } @@ -356,7 +369,30 @@ class Settings extends ChangeNotifier { } } - // TODO: Save the ssh keys path + // Save the ssh keys + var oldSshDir = Directory(p.join(gitBaseDir, '../files/ssh')); + if (oldSshDir.existsSync()) { + var sshPublicKeyPath = p.join(oldSshDir.path, "id_rsa.pub"); + var sshPrivateKeyPath = p.join(oldSshDir.path, "id_rsa"); + + sshPublicKey = await File(sshPublicKeyPath).readAsString(); + sshPrivateKey = await File(sshPrivateKeyPath).readAsString(); + sshPassword = ""; + + await oldSshDir.delete(recursive: true); + } + + var newSshDir = Directory(p.join(gitBaseDir, 'ssh')); + if (newSshDir.existsSync()) { + var sshPublicKeyPath = p.join(newSshDir.path, "id_rsa.pub"); + var sshPrivateKeyPath = p.join(newSshDir.path, "id_rsa"); + + sshPublicKey = await File(sshPublicKeyPath).readAsString(); + sshPrivateKey = await File(sshPrivateKeyPath).readAsString(); + sshPassword = ""; + + await newSshDir.delete(recursive: true); + } version = 1; pref.setInt("settingsVersion", version); diff --git a/lib/setup/screens.dart b/lib/setup/screens.dart index 19ff88d6..a3247eb5 100644 --- a/lib/setup/screens.dart +++ b/lib/setup/screens.dart @@ -210,10 +210,12 @@ class GitHostSetupScreenState extends State { } else if (_keyGenerationChoice == KeyGenerationChoice.UserProvided) { return GitHostUserProvidedKeys( doneFunction: (String publicKey, String privateKey) async { - await git_bindings.setSshKeys( - publicKey: publicKey, - privateKey: privateKey, - ); + var settings = Provider.of(context, listen: false); + settings.sshPublicKey = publicKey; + settings.sshPrivateKey = privateKey; + settings.sshPassword = ""; + settings.save(); + setState(() { this.publicKey = publicKey; _pageCount = pos + 2; @@ -294,8 +296,12 @@ class GitHostSetupScreenState extends State { } else if (_keyGenerationChoice == KeyGenerationChoice.UserProvided) { return GitHostUserProvidedKeys( doneFunction: (String publicKey, String privateKey) async { - await git_bindings.setSshKeys( - publicKey: publicKey, privateKey: privateKey); + var settings = Provider.of(context, listen: false); + settings.sshPublicKey = publicKey; + settings.sshPrivateKey = privateKey; + settings.sshPassword = ""; + settings.save(); + setState(() { this.publicKey = publicKey; _pageCount = pos + 2; @@ -410,9 +416,15 @@ class GitHostSetupScreenState extends State { "-" + DateTime.now().toIso8601String().substring(0, 10); // only the date - generateSSHKeys(comment: comment).then((String publicKey) { + generateSSHKeys(comment: comment).then((SshKey sshKey) { + var settings = Provider.of(context, listen: false); + settings.sshPublicKey = sshKey.publicKey; + settings.sshPrivateKey = sshKey.privateKey; + settings.sshPassword = sshKey.password; + settings.save(); + setState(() { - this.publicKey = publicKey; + publicKey = sshKey.publicKey; Log.d("PublicKey: " + publicKey); _copyKeyToClipboard(context); }); @@ -493,7 +505,12 @@ class GitHostSetupScreenState extends State { await repo.addRemote(widget.remoteName, _gitCloneUrl); var repoN = git_bindings.GitRepo(folderPath: repoPath); - await repoN.fetch(widget.remoteName); + await repoN.fetch( + remote: widget.remoteName, + publicKey: settings.sshPublicKey, + privateKey: settings.sshPrivateKey, + password: settings.sshPassword, + ); } on Exception catch (e) { Log.e(e.toString()); error = e.toString(); @@ -549,7 +566,16 @@ class GitHostSetupScreenState extends State { setState(() { _autoConfigureMessage = tr('setup.sshKey.generate'); }); - var publicKey = await generateSSHKeys(comment: "GitJournal"); + var sshKey = await generateSSHKeys(comment: "GitJournal"); + var settings = Provider.of(context, listen: false); + settings.sshPublicKey = sshKey.publicKey; + settings.sshPrivateKey = sshKey.privateKey; + settings.sshPassword = sshKey.password; + settings.save(); + + setState(() { + publicKey = sshKey.publicKey; + }); Log.i("Adding as a deploy key"); _autoConfigureMessage = tr('setup.sshKey.addDeploy'); diff --git a/lib/ssh/keygen.dart b/lib/ssh/keygen.dart index 8b5921dc..c87733cd 100644 --- a/lib/ssh/keygen.dart +++ b/lib/ssh/keygen.dart @@ -1,21 +1,32 @@ -import 'package:git_bindings/git_bindings.dart'; import 'package:meta/meta.dart'; import 'package:gitjournal/ssh/rsa_key_pair.dart'; import 'package:gitjournal/utils/logger.dart'; -Future generateSSHKeys({@required String comment}) async { +class SshKey { + final String publicKey; + final String privateKey; + final String password; + + const SshKey({ + @required this.publicKey, + @required this.privateKey, + @required this.password, + }); +} + +Future generateSSHKeys({@required String comment}) async { try { var keyPair = await RsaKeyPair.generateAsync(); - var publicKeyStr = keyPair.publicKeyString(comment: comment); - await setSshKeys( - publicKey: publicKeyStr, + + return SshKey( + publicKey: keyPair.publicKeyString(comment: comment), privateKey: keyPair.privateKeyString(), + password: "", ); - return publicKeyStr; } catch (e) { Log.e(e); } - return ""; + return null; } diff --git a/pubspec.yaml b/pubspec.yaml index 3cea12fe..d831a93d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: dynamic_theme: ^1.0.0 flutter_staggered_grid_view: ^0.3.0 provider: ^4.3.2+2 - git_bindings: ^0.0.15 + git_bindings: ^0.0.16 dart_git: git: https://github.com/GitJournal/dart_git.git #path: /Users/vishesh/src/gitjournal/dart_git