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.
This commit is contained in:
Vishesh Handa
2020-10-19 14:52:24 +02:00
parent 05be1ce397
commit 98c92a3fe1
8 changed files with 166 additions and 171 deletions

View File

@ -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: <hidden>");
continue;
}
if ([key isEqualToString:@"password"]) {
NSLog(@". password: <hidden>");
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;

View File

@ -171,7 +171,12 @@ class GitNoteRepository {
Future<void> 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();

View File

@ -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<GitRemoteSettingsScreen> {
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<GitRemoteSettingsScreen> {
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<GitRemoteSettingsScreen> {
}
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<GitRemoteSettingsScreen> {
"-" +
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<Settings>(context, listen: false);
settings.sshPublicKey = sshKey.publicKey;
settings.sshPrivateKey = sshKey.publicKey;
settings.sshPassword = sshKey.password;
settings.save();
Log.d("PublicKey: " + sshKey.publicKey);
_copyKeyToClipboard(context);
});
}

View File

@ -222,7 +222,8 @@ class SettingsListState extends State<SettingsList> {
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);

View File

@ -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);

View File

@ -210,10 +210,12 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
} 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<Settings>(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<GitHostSetupScreen> {
} 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<Settings>(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<GitHostSetupScreen> {
"-" +
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<Settings>(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<GitHostSetupScreen> {
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<GitHostSetupScreen> {
setState(() {
_autoConfigureMessage = tr('setup.sshKey.generate');
});
var publicKey = await generateSSHKeys(comment: "GitJournal");
var sshKey = await generateSSHKeys(comment: "GitJournal");
var settings = Provider.of<Settings>(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');

View File

@ -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<String> 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<SshKey> 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;
}

View File

@ -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