Skip to content

Commit

Permalink
Properly handle cancellation of plug-in credential providers
Browse files Browse the repository at this point in the history
  • Loading branch information
joelverhagen committed Jun 2, 2016
1 parent c8624ef commit 106e503
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 2 deletions.
Expand Up @@ -103,6 +103,10 @@ public PluginCredentialProvider(string path, int timeoutSeconds)
taskResponse = new CredentialResponse(CredentialStatus.ProviderNotApplicable);
}
}
catch (OperationCanceledException)
{
throw;
}
catch (PluginException)
{
throw;
Expand Down Expand Up @@ -178,6 +182,8 @@ public PluginCredentialProvider(string path, int timeoutSeconds)
process.CancelErrorRead();
process.CancelOutputRead();

cancellationToken.ThrowIfCancellationRequested();

var exitCode = process.ExitCode;

if (Enum.GetValues(typeof(PluginCredentialResponseExitCode)).Cast<int>().Contains(exitCode))
Expand Down
Expand Up @@ -96,6 +96,9 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
response.StatusCode == HttpStatusCode.Forbidden)
{
promptCredentials = await AcquireCredentialsAsync(response.StatusCode, beforeLockVersion, logger, cancellationToken);

cancellationToken.ThrowIfCancellationRequested();

if (promptCredentials == null)
{
return response;
Expand Down
Expand Up @@ -235,6 +235,34 @@ public async Task WhenResponseContainsPassword_ReturnCredential()
Assert.Equal("p", ((NetworkCredential)result.Credentials)?.Password);
}

[Fact]
public async Task WhenCredentialProviderIsCanceled_Throws()
{
// Arrange
var provider = _mockProvider;
var uri = new Uri("http://host/");
var proxy = null as IWebProxy;
var type = CredentialRequestType.Unauthorized;
var message = null as string;
var isRetry = true;
var nonInteractive = true;
var exception = new OperationCanceledException();
_mockProvider
.Setup(x => x.Execute(It.IsAny<PluginCredentialRequest>(), It.IsAny<CancellationToken>()))
.Throws(exception);

// Act & Assert
var actual = await Assert.ThrowsAsync<OperationCanceledException>(() => provider.Object.GetAsync(
uri,
proxy,
type,
message,
isRetry,
nonInteractive,
CancellationToken.None));
Assert.Same(exception, actual);
}

[Fact]
public void SetsIdBasedOnTypeAndFilename()
{
Expand Down
Expand Up @@ -92,6 +92,51 @@ public async Task SendAsync_WithAcquiredCredentials_RetriesRequest()
Times.Once());
}

[Fact]
public async Task SendAsync_WhenCancelledDuringAcquiringCredentials_Throws()
{
// Arrange
var packageSource = new PackageSource("http://package.source.net");
var clientHandler = new HttpClientHandler();

var cts = new CancellationTokenSource();

var credentialService = Mock.Of<ICredentialService>();
Mock.Get(credentialService)
.Setup(
x => x.GetCredentialsAsync(
packageSource.SourceUri,
It.IsAny<IWebProxy>(),
CredentialRequestType.Unauthorized,
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.ThrowsAsync(new TaskCanceledException())
.Callback(() => cts.Cancel());

var handler = new HttpSourceAuthenticationHandler(packageSource, clientHandler, credentialService);

int retryCount = 0;
var innerHandler = new LambdaMessageHandler(
_ => { retryCount++; return new HttpResponseMessage(HttpStatusCode.Unauthorized); });
handler.InnerHandler = innerHandler;

// Act & Assert
await Assert.ThrowsAsync<TaskCanceledException>(
() => SendAsync(handler, cancellationToken: cts.Token));

Assert.Equal(1, retryCount);

Mock.Get(credentialService)
.Verify(
x => x.GetCredentialsAsync(
packageSource.SourceUri,
It.IsAny<IWebProxy>(),
CredentialRequestType.Unauthorized,
It.IsAny<string>(),
It.IsAny<CancellationToken>()),
Times.Once);
}

[Fact]
public async Task SendAsync_WithWrongCredentials_StopsRetryingAfter3Times()
{
Expand Down Expand Up @@ -213,11 +258,14 @@ private static LambdaMessageHandler GetLambdaMessageHandler(params HttpStatusCod
_ => new HttpResponseMessage(responses.Dequeue()));
}

private static async Task<HttpResponseMessage> SendAsync(HttpMessageHandler handler, HttpRequestMessage request = null)
private static async Task<HttpResponseMessage> SendAsync(
HttpMessageHandler handler,
HttpRequestMessage request = null,
CancellationToken cancellationToken = default(CancellationToken))
{
using (var client = new HttpClient(handler))
{
return await client.SendAsync(request ?? new HttpRequestMessage(HttpMethod.Get, "http://foo"));
return await client.SendAsync(request ?? new HttpRequestMessage(HttpMethod.Get, "http://foo"), cancellationToken);
}
}
}
Expand Down

0 comments on commit 106e503

Please sign in to comment.