Skip to content

Commit 5b6bea0

Browse files
committed
Let a smart subtransport ask for credentials and show the cert
Implement a method for a smart subtransport implementation ask the caller for what credentials to use. The certificate callback is also here, but untested as the web requests go to the central service point instead of asking individually.
1 parent cb20e22 commit 5b6bea0

File tree

3 files changed

+155
-2
lines changed

3 files changed

+155
-2
lines changed

LibGit2Sharp.Tests/SmartSubtransportFixture.cs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Net;
55
using System.Net.Security;
66
using LibGit2Sharp.Tests.TestHelpers;
7+
using LibGit2Sharp.Core;
78
using Xunit;
89
using Xunit.Extensions;
910

@@ -76,6 +77,58 @@ public void CustomSmartSubtransportTest(string scheme, string url)
7677
}
7778
}
7879

80+
[Theory]
81+
[InlineData("https", "https://bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3")]
82+
public void CanUseCredentials(string scheme, string url, string user, string pass)
83+
{
84+
string remoteName = "testRemote";
85+
86+
var scd = BuildSelfCleaningDirectory();
87+
var repoPath = Repository.Init(scd.RootedDirectoryPath);
88+
89+
SmartSubtransportRegistration<MockSmartSubtransport> registration = null;
90+
91+
try
92+
{
93+
// Disable server certificate validation for testing.
94+
// Do *NOT* enable this in production.
95+
ServicePointManager.ServerCertificateValidationCallback = certificateValidationCallback;
96+
97+
registration = GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>(scheme);
98+
Assert.NotNull(registration);
99+
100+
using (var repo = new Repository(scd.DirectoryPath))
101+
{
102+
Remote remote = repo.Network.Remotes.Add(remoteName, url);
103+
104+
// Set up structures for the expected results
105+
// and verifying the RemoteUpdateTips callback.
106+
TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance;
107+
ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName);
108+
109+
// Add expected branch objects
110+
foreach (KeyValuePair<string, ObjectId> kvp in expectedResults.BranchTips)
111+
{
112+
expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value);
113+
}
114+
115+
// Perform the actual fetch
116+
repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto,
117+
CredentialsProvider = (_user, _valid, _hostname) => new UsernamePasswordCredentials() { Username = "libgit3", Password = "libgit3" },
118+
});
119+
120+
// Verify the expected
121+
expectedFetchState.CheckUpdatedReferences(repo);
122+
}
123+
}
124+
finally
125+
{
126+
GlobalSettings.UnregisterSmartSubtransport(registration);
127+
128+
ServicePointManager.ServerCertificateValidationCallback -= certificateValidationCallback;
129+
}
130+
}
131+
79132
[Fact]
80133
public void CannotReregisterScheme()
81134
{
@@ -234,14 +287,39 @@ private HttpWebResponse GetResponseWithRedirects()
234287
}
235288
}
236289

237-
response = (HttpWebResponse)request.GetResponse();
290+
try
291+
{
292+
response = (HttpWebResponse)request.GetResponse();
293+
}
294+
catch (WebException ex)
295+
{
296+
response = ex.Response as HttpWebResponse;
297+
if (response.StatusCode == HttpStatusCode.Unauthorized)
298+
{
299+
Credentials cred;
300+
int ret = SmartTransport.AcquireCredentials(out cred, null, typeof(UsernamePasswordCredentials));
301+
if (ret != 0)
302+
{
303+
throw new InvalidOperationException("dunno");
304+
}
305+
306+
request = CreateWebRequest(EndpointUrl, IsPost, ContentType);
307+
UsernamePasswordCredentials userpass = (UsernamePasswordCredentials)cred;
308+
request.Credentials = new NetworkCredential(userpass.Username, userpass.Password);
309+
continue;
310+
}
311+
312+
// rethrow if it's not 401
313+
throw ex;
314+
}
238315

239316
if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect)
240317
{
241318
request = CreateWebRequest(response.Headers["Location"], IsPost, ContentType);
242319
continue;
243320
}
244321

322+
245323
break;
246324
}
247325

LibGit2Sharp/SmartSubtransport.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Runtime.InteropServices;
3+
using System.Text;
34
using LibGit2Sharp.Core;
5+
using LibGit2Sharp.Core.Handles;
46

57
namespace LibGit2Sharp
68
{
@@ -47,6 +49,76 @@ public abstract class RpcSmartSubtransport : SmartSubtransport
4749
/// </summary>
4850
public abstract class SmartSubtransport
4951
{
52+
internal IntPtr Transport { get; set; }
53+
54+
/// <summary>
55+
/// Call the certificate check callback
56+
/// </summary>
57+
/// <param name="cert">The certificate to send</param>
58+
/// <param name="valid">Whether we consider the certificate to be valid</param>
59+
/// <param name="hostname">The hostname we connected to</param>
60+
public int CertificateCheck(Certificate cert, bool valid, string hostname)
61+
{
62+
CertificateSsh sshCert = cert as CertificateSsh;
63+
CertificateX509 x509Cert = cert as CertificateX509;
64+
65+
if (sshCert == null && x509Cert == null)
66+
{
67+
throw new InvalidOperationException("Unsupported certificate type");
68+
}
69+
70+
int ret;
71+
if (sshCert != null)
72+
{
73+
var certPtr = sshCert.ToPointer();
74+
ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname);
75+
Marshal.FreeHGlobal(certPtr);
76+
} else {
77+
IntPtr certPtr, dataPtr;
78+
certPtr = x509Cert.ToPointers(out dataPtr);
79+
ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname);
80+
Marshal.FreeHGlobal(dataPtr);
81+
Marshal.FreeHGlobal(certPtr);
82+
}
83+
84+
return ret;
85+
}
86+
87+
public int AcquireCredentials(out Credentials cred, string user, params Type[] methods)
88+
{
89+
// Convert the user-provided types to libgit2's flags
90+
int allowed = 0;
91+
foreach (var method in methods)
92+
{
93+
if (method == typeof(UsernamePasswordCredentials))
94+
{
95+
allowed |= (int)GitCredentialType.UserPassPlaintext;
96+
}
97+
else
98+
{
99+
throw new InvalidOperationException("Unknown type passes as allowed credential");
100+
}
101+
}
102+
103+
IntPtr credHandle = IntPtr.Zero;
104+
int res = Proxy.git_transport_smart_credentials(out credHandle, Transport, user, allowed);
105+
if (res != 0)
106+
{
107+
cred = null;
108+
return res;
109+
}
110+
111+
var baseCred = credHandle.MarshalAs<GitCredential>();
112+
switch (baseCred.credtype)
113+
{
114+
case GitCredentialType.UserPassPlaintext:
115+
cred = UsernamePasswordCredentials.FromNative(credHandle.MarshalAs<GitCredentialUserpass>());
116+
return 0;
117+
default:
118+
throw new InvalidOperationException("User returned an unkown credential type");
119+
}
120+
}
121+
50122
/// <summary>
51123
/// Invoked by libgit2 to create a connection using this subtransport.
52124
/// </summary>

LibGit2Sharp/SmartSubtransportRegistration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Runtime.InteropServices;
33
using LibGit2Sharp.Core;
4+
using LibGit2Sharp.Core.Handles;
45

56
namespace LibGit2Sharp
67
{
@@ -75,7 +76,9 @@ private static int Subtransport(
7576

7677
try
7778
{
78-
subtransport = new T().GitSmartSubtransportPointer;
79+
var obj = new T();
80+
obj.Transport = transport;
81+
subtransport = obj.GitSmartSubtransportPointer;
7982

8083
return 0;
8184
}

0 commit comments

Comments
 (0)