diff --git a/Lib/NativeBinaries/amd64/git2-9bbc8f3.dll b/Lib/NativeBinaries/amd64/git2-9bbc8f3.dll index f162e6c60..4700837ad 100644 Binary files a/Lib/NativeBinaries/amd64/git2-9bbc8f3.dll and b/Lib/NativeBinaries/amd64/git2-9bbc8f3.dll differ diff --git a/Lib/NativeBinaries/amd64/git2-9bbc8f3.pdb b/Lib/NativeBinaries/amd64/git2-9bbc8f3.pdb index 43f4171fc..b4b9c04c5 100644 Binary files a/Lib/NativeBinaries/amd64/git2-9bbc8f3.pdb and b/Lib/NativeBinaries/amd64/git2-9bbc8f3.pdb differ diff --git a/Lib/NativeBinaries/x86/git2-9bbc8f3.dll b/Lib/NativeBinaries/x86/git2-9bbc8f3.dll index db04bb5b2..b061e8d51 100644 Binary files a/Lib/NativeBinaries/x86/git2-9bbc8f3.dll and b/Lib/NativeBinaries/x86/git2-9bbc8f3.dll differ diff --git a/Lib/NativeBinaries/x86/git2-9bbc8f3.pdb b/Lib/NativeBinaries/x86/git2-9bbc8f3.pdb index 89c4624fc..cf55c0bc4 100644 Binary files a/Lib/NativeBinaries/x86/git2-9bbc8f3.pdb and b/Lib/NativeBinaries/x86/git2-9bbc8f3.pdb differ diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index 698595042..fc791d6ca 100644 --- a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -1,6 +1,8 @@ using System.Text.RegularExpressions; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using System.Globalization; +using System.IO; namespace LibGit2Sharp.Tests { @@ -13,6 +15,14 @@ public void CanGetMinimumCompiledInFeatures() Assert.True(features.HasFlag(BuiltInFeatures.Threads)); Assert.True(features.HasFlag(BuiltInFeatures.Https)); + + bool hasSsh; + using (var sr = new StreamReader(typeof(GlobalSettingsFixture).Assembly.GetManifestResourceStream("LibGit2Sharp.Tests.ssh_used.txt"))) + { + if (!bool.TryParse(sr.ReadLine(), out hasSsh)) + hasSsh = false; + } + Assert.Equal(hasSsh, features.HasFlag(BuiltInFeatures.Ssh)); } [Fact] diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index f93a24f30..a2d168b62 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -157,4 +157,7 @@ --> + + + diff --git a/LibGit2Sharp.Tests/ssh_used.txt b/LibGit2Sharp.Tests/ssh_used.txt new file mode 100644 index 000000000..bc59c12aa --- /dev/null +++ b/LibGit2Sharp.Tests/ssh_used.txt @@ -0,0 +1 @@ +False diff --git a/LibGit2Sharp/AuthenticationException.cs b/LibGit2Sharp/AuthenticationException.cs new file mode 100644 index 000000000..acbf331ff --- /dev/null +++ b/LibGit2Sharp/AuthenticationException.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.Serialization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown when an operation which requires an + /// authentication fails. + /// + [Serializable] + public class AuthenticationException : LibGit2SharpException + { + /// + /// Initializes a new instance of the class. + /// + public AuthenticationException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A message that describes the error. + public AuthenticationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. + public AuthenticationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class with a serialized data. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected AuthenticationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + internal AuthenticationException(string message, GitErrorCode code, GitErrorCategory category) + : base(message, code, category) + { + } + } +} diff --git a/LibGit2Sharp/Core/Ensure.cs b/LibGit2Sharp/Core/Ensure.cs index 343fc1887..357b4302e 100644 --- a/LibGit2Sharp/Core/Ensure.cs +++ b/LibGit2Sharp/Core/Ensure.cs @@ -101,6 +101,7 @@ private static readonly Dictionary new LockedFileException(m, r, c) }, { GitErrorCode.NotFound, (m, r, c) => new NotFoundException(m, r, c) }, { GitErrorCode.Peel, (m, r, c) => new PeelException(m, r, c) }, + { GitErrorCode.Auth, (m, r, c) => new AuthenticationException(m, r, c) }, }; private static void HandleError(int result) diff --git a/LibGit2Sharp/Core/GitCredentialType.cs b/LibGit2Sharp/Core/GitCredentialType.cs index 00a7460d9..37328a2c1 100644 --- a/LibGit2Sharp/Core/GitCredentialType.cs +++ b/LibGit2Sharp/Core/GitCredentialType.cs @@ -32,5 +32,10 @@ internal enum GitCredentialType /// TODO /// SshInteractive = (1 << 4), + + /// + /// Username only information. + /// + UsernameQuery = (1 << 5), } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index a8fb1eacf..ad2d51413 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -398,6 +398,24 @@ internal static extern int git_cred_userpass_plaintext_new( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string username, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string password); + [DllImport(libgit2)] + internal static extern int git_cred_ssh_key_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase); + + [DllImport(libgit2)] + internal static extern int git_cred_username_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username); + + [DllImport(libgit2)] + internal static extern int git_cred_ssh_key_from_agent( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username); + [DllImport(libgit2)] internal static extern int git_describe_commit( out DescribeResultSafeHandle describe, diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 3ca4a3892..6c65c1540 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -340,6 +340,10 @@ + + + + diff --git a/LibGit2Sharp/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 55be945d2..dd7561042 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -256,6 +256,14 @@ private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFro { types |= SupportedCredentialTypes.Default; } + if (credTypes.HasFlag(GitCredentialType.SshKey)) + { + types |= SupportedCredentialTypes.Ssh; + } + if (credTypes.HasFlag(GitCredentialType.UsernameQuery)) + { + types |= SupportedCredentialTypes.UsernameQuery; + } var cred = CredentialsProvider(url, username, types); diff --git a/LibGit2Sharp/SshAgentCredentials.cs b/LibGit2Sharp/SshAgentCredentials.cs new file mode 100644 index 000000000..f53d5d1cb --- /dev/null +++ b/LibGit2Sharp/SshAgentCredentials.cs @@ -0,0 +1,32 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds SSH agent credentials for remote repository access. + /// + public sealed class SshAgentCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("SshAgentCredentials contains a null Username."); + } + + return NativeMethods.git_cred_ssh_key_from_agent(out cred, Username); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + } +} + diff --git a/LibGit2Sharp/SshUserKeyCredentials.cs b/LibGit2Sharp/SshUserKeyCredentials.cs new file mode 100644 index 000000000..044ce69c0 --- /dev/null +++ b/LibGit2Sharp/SshUserKeyCredentials.cs @@ -0,0 +1,61 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds SSH username with key credentials for remote repository access. + /// + public sealed class SshUserKeyCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Username."); + } + + if (Passphrase == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Passphrase."); + } + + if (PublicKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PublicKey."); + } + + if (PrivateKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PrivateKey."); + } + + return NativeMethods.git_cred_ssh_key_new(out cred, Username, PublicKey, PrivateKey, Passphrase); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + + /// + /// Public key file location for SSH authentication. + /// + public string PublicKey { get; set; } + + /// + /// Private key file location for SSH authentication. + /// + public string PrivateKey { get; set; } + + /// + /// Passphrase for SSH authentication. + /// + public string Passphrase { get; set; } + } +} diff --git a/LibGit2Sharp/SupportedCredentialTypes.cs b/LibGit2Sharp/SupportedCredentialTypes.cs index bc38a259e..37114c19a 100644 --- a/LibGit2Sharp/SupportedCredentialTypes.cs +++ b/LibGit2Sharp/SupportedCredentialTypes.cs @@ -18,5 +18,15 @@ public enum SupportedCredentialTypes /// Ask Windows to provide its default credentials for the current user (e.g. NTLM) /// Default = (1 << 1), + + /// + /// SSH with username and public/private key. (SshUserKeyCredentials, SshAgentCredentials) + /// + Ssh = (1 << 2), + + /// + /// Queries the server with the specified username, then later returns the supported credential types. + /// + UsernameQuery = (1 << 3), } } diff --git a/LibGit2Sharp/UsernameQueryCredentials.cs b/LibGit2Sharp/UsernameQueryCredentials.cs new file mode 100644 index 000000000..14981d74e --- /dev/null +++ b/LibGit2Sharp/UsernameQueryCredentials.cs @@ -0,0 +1,31 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds username query credentials for remote repository access. + /// + public sealed class UsernameQueryCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("UsernameQueryCredentials contains a null Username."); + } + + return NativeMethods.git_cred_username_new(out cred, Username); + } + + /// + /// Username for querying the server for supported authentication. + /// + public string Username { get; set; } + } +} diff --git a/UpdateLibgit2ToSha.ps1 b/UpdateLibgit2ToSha.ps1 index 4a90841e4..b4692750f 100644 --- a/UpdateLibgit2ToSha.ps1 +++ b/UpdateLibgit2ToSha.ps1 @@ -11,6 +11,8 @@ If set, run the libgit2 tests on the desired version. .PARAMETER debug If set, build the "Debug" configuration of libgit2, rather than "RelWithDebInfo" (default). +.PARAMETER ssh + If set embeds SSH at the path pointed to by the value. #> Param( @@ -18,7 +20,8 @@ Param( [string]$vs = '10', [string]$libgit2Name = '', [switch]$test, - [switch]$debug + [switch]$debug, + [string]$ssh = '' ) Set-StrictMode -Version Latest @@ -28,12 +31,19 @@ $libgit2sharpDirectory = Split-Path $MyInvocation.MyCommand.Path $libgit2Directory = Join-Path $libgit2sharpDirectory "libgit2" $x86Directory = Join-Path $libgit2sharpDirectory "Lib\NativeBinaries\x86" $x64Directory = Join-Path $libgit2sharpDirectory "Lib\NativeBinaries\amd64" +$sshFile = Join-Path $libgit2sharpDirectory "LibGit2Sharp.Tests\ssh_used.txt" $build_clar = 'OFF' if ($test.IsPresent) { $build_clar = 'ON' } $configuration = "RelWithDebInfo" if ($debug.IsPresent) { $configuration = "Debug" } +if (![string]::IsNullOrEmpty($ssh)) { + $embed_ssh = "-D EMBED_SSH_PATH=""$ssh""" +} else { + $embed_ssh = '' +} + function Run-Command([scriptblock]$Command, [switch]$Fatal, [switch]$Quiet) { $output = "" if ($Quiet) { @@ -144,7 +154,7 @@ function Assert-Consistent-Naming($expected, $path) { Run-Command -Quiet { & remove-item build -recurse -force } Run-Command -Quiet { & mkdir build } cd build - Run-Command -Quiet -Fatal { & $cmake -G "Visual Studio $vs" -D ENABLE_TRACE=ON -D "BUILD_CLAR=$build_clar" -D "LIBGIT2_FILENAME=$binaryFilename" -DSTDCALL=ON .. } + Run-Command -Quiet -Fatal { & $cmake -G "Visual Studio $vs" -D THREADSAFE=ON -D ENABLE_TRACE=ON -D "BUILD_CLAR=$build_clar" -D "LIBGIT2_FILENAME=$binaryFilename" -DSTDCALL=ON $embed_ssh .. } Run-Command -Quiet -Fatal { & $cmake --build . --config $configuration } if ($test.IsPresent) { Run-Command -Quiet -Fatal { & $ctest -V . } } cd $configuration @@ -157,7 +167,7 @@ function Assert-Consistent-Naming($expected, $path) { cd .. Run-Command -Quiet { & mkdir build64 } cd build64 - Run-Command -Quiet -Fatal { & $cmake -G "Visual Studio $vs Win64" -D THREADSAFE=ON -D ENABLE_TRACE=ON -D "BUILD_CLAR=$build_clar" -D "LIBGIT2_FILENAME=$binaryFilename" -DSTDCALL=ON ../.. } + Run-Command -Quiet -Fatal { & $cmake -G "Visual Studio $vs Win64" -D THREADSAFE=ON -D ENABLE_TRACE=ON -D "BUILD_CLAR=$build_clar" -D "LIBGIT2_FILENAME=$binaryFilename" -DSTDCALL=ON $embed_ssh ../.. } Run-Command -Quiet -Fatal { & $cmake --build . --config $configuration } if ($test.IsPresent) { Run-Command -Quiet -Fatal { & $ctest -V . } } cd $configuration @@ -180,6 +190,7 @@ namespace LibGit2Sharp.Core sc -Encoding ASCII (Join-Path $libgit2sharpDirectory "Libgit2sharp\Core\NativeDllName.cs") $dllNameClass sc -Encoding ASCII (Join-Path $libgit2sharpDirectory "Libgit2sharp\libgit2_hash.txt") $sha + sc -Encoding ASCII (Join-Path $libgit2sharpDirectory "Libgit2sharp.Tests\ssh_used.txt") (![string]::IsNullOrEmpty($ssh)) $buildProperties = @" diff --git a/build.libgit2sharp.sh b/build.libgit2sharp.sh index d650afd26..96e525c0e 100755 --- a/build.libgit2sharp.sh +++ b/build.libgit2sharp.sh @@ -3,6 +3,8 @@ LIBGIT2SHA=`cat ./LibGit2Sharp/libgit2_hash.txt` SHORTSHA=${LIBGIT2SHA:0:7} EXTRADEFINE="$1" +USESSH=${1-OFF} +SSH_FILE="$(pwd)/LibGit2Sharp.Tests/ssh_used.txt" rm -rf libgit2/build mkdir libgit2/build @@ -11,13 +13,17 @@ export _BINPATH=`pwd` cmake -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo \ -DBUILD_CLAR:BOOL=OFF \ - -DUSE_SSH=OFF \ + -DUSE_SSH=$USESSH \ -DENABLE_TRACE=ON \ -DLIBGIT2_FILENAME=git2-$SHORTSHA \ -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" \ .. cmake --build . +shopt -s nocasematch +[[ $USESSH == "ON" ]] && echo "True" > $SSH_FILE || echo "False" > $SSH_FILE +shopt -u nocasematch + export LD_LIBRARY_PATH=$_BINPATH:$LD_LIBRARY_PATH export DYLD_LIBRARY_PATH=$_BINPATH:$DYLD_LIBRARY_PATH