diff --git a/lib/src/bindings/credentials.dart b/lib/src/bindings/credentials.dart index d7f0c9f..e3bbe79 100644 --- a/lib/src/bindings/credentials.dart +++ b/lib/src/bindings/credentials.dart @@ -1,34 +1,9 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import '../error.dart'; import '../util.dart'; import 'libgit2_bindings.dart'; -/// Create a credential to specify a username. -/// -/// This is used with ssh authentication to query for the username if none is -/// specified in the url. -/// -/// Throws a [LibGit2Error] if error occured. -Pointer username(String username) { - final out = calloc>(); - final usernameC = username.toNativeUtf8().cast(); - - final error = libgit2.git_credential_username_new(out, usernameC); - - calloc.free(usernameC); - - if (error < 0) { - calloc.free(out); - throw LibGit2Error(libgit2.git_error_last()); - } else { - return out.value; - } -} - /// Create a new plain-text username and password credential object. -/// -/// Throws a [LibGit2Error] if error occured. Pointer userPass({ required String username, required String password, @@ -37,26 +12,15 @@ Pointer userPass({ final usernameC = username.toNativeUtf8().cast(); final passwordC = password.toNativeUtf8().cast(); - final error = libgit2.git_credential_userpass_plaintext_new( - out, - usernameC, - passwordC, - ); + libgit2.git_credential_userpass_plaintext_new(out, usernameC, passwordC); calloc.free(usernameC); calloc.free(passwordC); - if (error < 0) { - calloc.free(out); - throw LibGit2Error(libgit2.git_error_last()); - } else { - return out.value; - } + return out.value; } /// Create a new passphrase-protected ssh key credential object. -/// -/// Throws a [LibGit2Error] if error occured. Pointer sshKey({ required String username, required String publicKey, @@ -69,7 +33,7 @@ Pointer sshKey({ final privateKeyC = privateKey.toNativeUtf8().cast(); final passPhraseC = passPhrase.toNativeUtf8().cast(); - final error = libgit2.git_credential_ssh_key_new( + libgit2.git_credential_ssh_key_new( out, usernameC, publicKeyC, @@ -82,36 +46,22 @@ Pointer sshKey({ calloc.free(privateKeyC); calloc.free(passPhraseC); - if (error < 0) { - calloc.free(out); - throw LibGit2Error(libgit2.git_error_last()); - } else { - return out.value; - } + return out.value; } /// Create a new ssh key credential object used for querying an ssh-agent. -/// -/// Throws a [LibGit2Error] if error occured. Pointer sshKeyFromAgent(String username) { final out = calloc>(); final usernameC = username.toNativeUtf8().cast(); - final error = libgit2.git_credential_ssh_key_from_agent(out, usernameC); + libgit2.git_credential_ssh_key_from_agent(out, usernameC); calloc.free(usernameC); - if (error < 0) { - calloc.free(out); - throw LibGit2Error(libgit2.git_error_last()); - } else { - return out.value; - } + return out.value; } /// Create a new ssh key credential object reading the keys from memory. -/// -/// Throws a [LibGit2Error] if error occured. Pointer sshKeyFromMemory({ required String username, required String publicKey, @@ -124,7 +74,7 @@ Pointer sshKeyFromMemory({ final privateKeyC = privateKey.toNativeUtf8().cast(); final passPhraseC = passPhrase.toNativeUtf8().cast(); - final error = libgit2.git_credential_ssh_key_memory_new( + libgit2.git_credential_ssh_key_memory_new( out, usernameC, publicKeyC, @@ -137,10 +87,5 @@ Pointer sshKeyFromMemory({ calloc.free(privateKeyC); calloc.free(passPhraseC); - if (error < 0) { - calloc.free(out); - throw LibGit2Error(libgit2.git_error_last()); - } else { - return out.value; - } + return out.value; } diff --git a/lib/src/bindings/remote_callbacks.dart b/lib/src/bindings/remote_callbacks.dart index 0759d8a..32ba124 100644 --- a/lib/src/bindings/remote_callbacks.dart +++ b/lib/src/bindings/remote_callbacks.dart @@ -1,6 +1,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:libgit2dart/libgit2dart.dart'; +import 'package:libgit2dart/src/util.dart'; import '../credentials.dart'; import '../callbacks.dart'; import '../repository.dart'; @@ -120,21 +121,34 @@ class RemoteCallbacks { int allowedTypes, Pointer payload, ) { - final credentialType = credentials!.credentialType; - if (allowedTypes & credentialType.value != credentialType.value) { - throw ArgumentError('Invalid credential type $credentialType'); + if (payload.cast().value == 2) { + libgit2.git_error_set_str( + git_error_t.GIT_ERROR_INVALID, + 'Incorrect credentials.'.toNativeUtf8().cast(), + ); + throw LibGit2Error(libgit2.git_error_last()); } - if (credentials is Username) { - final cred = credentials as Username; - credPointer[0] = credentials_bindings.username(cred.username); - } else if (credentials is UserPass) { + final credentialType = credentials!.credentialType; + + if (allowedTypes & credentialType.value != credentialType.value) { + libgit2.git_error_set_str( + git_error_t.GIT_ERROR_INVALID, + 'Invalid credential type $credentialType'.toNativeUtf8().cast(), + ); + throw LibGit2Error(libgit2.git_error_last()); + } + + if (credentials is UserPass) { final cred = credentials as UserPass; credPointer[0] = credentials_bindings.userPass( username: cred.username, password: cred.password, ); - } else if (credentials is Keypair) { + payload.cast().value++; + } + + if (credentials is Keypair) { final cred = credentials as Keypair; credPointer[0] = credentials_bindings.sshKey( username: cred.username, @@ -142,10 +156,16 @@ class RemoteCallbacks { privateKey: cred.privateKey, passPhrase: cred.passPhrase, ); - } else if (credentials is KeypairFromAgent) { + payload.cast().value++; + } + + if (credentials is KeypairFromAgent) { final cred = credentials as KeypairFromAgent; credPointer[0] = credentials_bindings.sshKeyFromAgent(cred.username); - } else if (credentials is KeypairFromMemory) { + payload.cast().value++; + } + + if (credentials is KeypairFromMemory) { final cred = credentials as KeypairFromMemory; credPointer[0] = credentials_bindings.sshKeyFromMemory( username: cred.username, @@ -153,6 +173,7 @@ class RemoteCallbacks { privateKey: cred.privateKey, passPhrase: cred.passPhrase, ); + payload.cast().value++; } return 0; @@ -199,6 +220,8 @@ class RemoteCallbacks { if (callbacks.credentials != null) { credentials = callbacks.credentials; + final payload = calloc()..value = 1; + callbacksOptions.payload = payload.cast(); callbacksOptions.credentials = Pointer.fromFunction( credentialsCb, except, diff --git a/lib/src/credentials.dart b/lib/src/credentials.dart index 705fbcf..7a1749e 100644 --- a/lib/src/credentials.dart +++ b/lib/src/credentials.dart @@ -5,20 +5,6 @@ abstract class Credentials { GitCredential get credentialType; } -/// Credential with specific username. -class Username implements Credentials { - const Username(this.username); - - /// The username to authenticate with. - final String username; - - @override - GitCredential get credentialType => GitCredential.username; - - @override - String toString() => 'Username{username: $username}'; -} - /// Plain-text username and password credential. class UserPass implements Credentials { const UserPass({required this.username, required this.password}); diff --git a/test/credentials_test.dart b/test/credentials_test.dart index 4161fb9..f103737 100644 --- a/test/credentials_test.dart +++ b/test/credentials_test.dart @@ -17,15 +17,6 @@ void main() { } }); group('Credentials', () { - test('successfully initializes username credentials', () { - final credentials = const Username('user'); - - expect(credentials, isA()); - expect(credentials.username, 'user'); - expect(credentials.credentialType, GitCredential.username); - expect(credentials.toString(), contains('Username{')); - }); - test('successfully initializes username/password credentials', () { final credentials = const UserPass( username: 'user', @@ -82,18 +73,28 @@ void main() { expect(credentials.toString(), contains('KeypairFromAgent{')); }); - test('sucessfully clones repository with provided username', () { - final callbacks = const Callbacks(credentials: Username('git')); - - final repo = Repository.clone( - url: 'https://git@github.com/libgit2/TestGitRepository', - localPath: cloneDir.path, - callbacks: callbacks, + test('throws when provided username and password are incorrect', () { + final callbacks = const Callbacks( + credentials: UserPass( + username: 'libgit2', + password: 'libgit2', + ), ); - expect(repo.isEmpty, false); - - repo.free(); + expect( + () => Repository.clone( + url: 'https://github.com/github/github', + localPath: cloneDir.path, + callbacks: callbacks, + ), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "Incorrect credentials.", + ), + ), + ); }); test('sucessfully clones repository with provided keypair', () { @@ -122,7 +123,13 @@ void main() { url: 'ssh://git@github.com/libgit2/TestGitRepository', localPath: cloneDir.path, ), - throwsA(isA()), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "authentication required but no callback set", + ), + ), ); }); @@ -141,7 +148,62 @@ void main() { localPath: cloneDir.path, callbacks: callbacks, ), - throwsA(isA()), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "Failed to authenticate SSH session: Unable to open public key file", + ), + ), + ); + }); + + test('throws when provided keypair is incorrect', () { + final keypair = const Keypair( + username: 'git', + pubKey: 'test/assets/keys/id_rsa.pub', + privateKey: 'incorrect', + passPhrase: 'empty', + ); + final callbacks = Callbacks(credentials: keypair); + + expect( + () => Repository.clone( + url: 'ssh://git@github.com/libgit2/TestGitRepository', + localPath: cloneDir.path, + callbacks: callbacks, + ), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "Incorrect credentials.", + ), + ), + ); + }); + + test('throws when provided credential type is invalid', () { + final callbacks = const Callbacks( + credentials: UserPass( + username: 'libgit2', + password: 'libgit2', + ), + ); + + expect( + () => Repository.clone( + url: 'ssh://git@github.com/libgit2/TestGitRepository', + localPath: cloneDir.path, + callbacks: callbacks, + ), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "Invalid credential type GitCredential.userPassPlainText", + ), + ), ); }); @@ -167,23 +229,49 @@ void main() { repo.free(); }); - test('sucessfully clones repository with provided username and password', - () { - final userPass = const UserPass( - username: 'libgit2', - password: 'libgit2', + test('throws when provided keypair from memory is incorrect', () { + final pubKey = File('test/assets/keys/id_rsa.pub').readAsStringSync(); + final keypair = KeypairFromMemory( + username: 'git', + pubKey: pubKey, + privateKey: 'incorrect', + passPhrase: 'empty', ); - final callbacks = Callbacks(credentials: userPass); + final callbacks = Callbacks(credentials: keypair); - final repo = Repository.clone( - url: 'https://github.com/libgit2/TestGitRepository', - localPath: cloneDir.path, - callbacks: callbacks, + expect( + () => Repository.clone( + url: 'ssh://git@github.com/libgit2/TestGitRepository', + localPath: cloneDir.path, + callbacks: callbacks, + ), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "Incorrect credentials.", + ), + ), ); + }); - expect(repo.isEmpty, false); + test('throws when provided keypair from agent is incorrect', () { + final callbacks = const Callbacks(credentials: KeypairFromAgent('git')); - repo.free(); + expect( + () => Repository.clone( + url: 'ssh://git@github.com/libgit2/TestGitRepository', + localPath: cloneDir.path, + callbacks: callbacks, + ), + throwsA( + isA().having( + (e) => e.toString(), + 'error', + "Incorrect credentials.", + ), + ), + ); }); }); }