Skip to content

Commit 45e98b6

Browse files
authored
Add TestableVSCredentialProvider (#119)
1 parent 0170955 commit 45e98b6

9 files changed

Lines changed: 268 additions & 0 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.12.35330.244 main
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestableVSCredentialProvider", "TestableVSCredentialProvider\TestableVSCredentialProvider.csproj", "{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
EndGlobal
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using System.Reflection;
2+
using System.Runtime.InteropServices;
3+
4+
[assembly: AssemblyTitle("NuGet.Test.TestExtensions.TestableVSCredentialProvider")]
5+
[assembly: AssemblyDescription("Test implementation of a Visual Studio credential provider")]
6+
[assembly: ComVisible(false)]
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using System.ComponentModel.Composition;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Security;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using NuGet.VisualStudio;
10+
11+
namespace NuGet.Test.TestExtensions.TestableVSCredentialProvider
12+
{
13+
14+
[Export(typeof(IVsCredentialProvider))]
15+
public class TestCredentialProvider
16+
: IVsCredentialProvider
17+
{
18+
/// <summary>
19+
/// Get credentials for the supplied package source Uri.
20+
/// </summary>
21+
/// <param name="uri">The NuGet package source Uri for which credentials are being requested. Implementors are
22+
/// expected to first determine if this is a package source for which they can supply credentials.
23+
/// If not, then Null should be returned.</param>
24+
/// <param name="proxy">Web proxy to use when comunicating on the network. Null if there is no proxy
25+
/// authentication configured.</param>
26+
/// <param name="isProxyRequest">True if if this request is to get proxy authentication
27+
/// credentials. If the implementation is not valid for acquiring proxy credentials, then
28+
/// null should be returned.</param>
29+
/// <param name="isRetry">True if credentials were previously acquired for this uri, but
30+
/// the supplied credentials did not allow authorized access.</param>
31+
/// <param name="nonInteractive">If true, then interactive prompts must not be allowed.</param>
32+
/// <param name="cancellationToken">This cancellation token should be checked to determine if the
33+
/// operation requesting credentials has been cancelled.</param>
34+
/// <returns>Credentials acquired by this provider for the given package source uri.
35+
/// If the provider does not handle requests for the input parameter set, then null should be returned.
36+
/// If the provider does handle the request, but cannot supply credentials, an exception should be thrown.</returns>
37+
public Task<ICredentials> GetCredentialsAsync(Uri uri, IWebProxy proxy, bool isProxyRequest, bool isRetry, bool nonInteractive, CancellationToken cancellationToken)
38+
{
39+
if (uri == null)
40+
{
41+
throw new ArgumentNullException(nameof(uri));
42+
}
43+
44+
var className = this.GetType().Name;
45+
46+
Trace.TraceInformation($"{className}.GetCredentialsAsync was called for uri {uri}.");
47+
48+
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
49+
var responseUser = query[$"{className}-responseUser"];
50+
var responsePassword = query[$"{className}-responsePassword"];
51+
52+
if (responsePassword != null)
53+
{
54+
try
55+
{
56+
var token = new SecureString();
57+
responsePassword.ToList<char>().ForEach(x => token.AppendChar(x));
58+
59+
return Task.FromResult<ICredentials>(new TestCredentials(username: responseUser ?? "username", token: token));
60+
}
61+
catch (TaskCanceledException)
62+
{
63+
Trace.TraceError($"{className}.GetCredentialsAsync Credentials acquisition for server {uri} was cancelled by the user.");
64+
throw;
65+
}
66+
catch (Exception ex)
67+
{
68+
Trace.TraceError($"{className}.GetCredentialsAsync Credentials acquisition for server {uri} failed with error: {ex.Message}");
69+
}
70+
}
71+
72+
// By default, this provider is not applicable.
73+
Trace.TraceInformation($"{className}.GetCredentialsAsync Provider not applicable.");
74+
return Task.FromResult<ICredentials>(null);
75+
}
76+
}
77+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.ComponentModel.Composition;
2+
using NuGet.VisualStudio;
3+
4+
namespace NuGet.Test.TestExtensions.TestableVSCredentialProvider
5+
{
6+
7+
[Export(typeof(IVsCredentialProvider))]
8+
public sealed class TestCredentialProvider2
9+
: TestCredentialProvider
10+
{
11+
}
12+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Net;
3+
using System.Security;
4+
5+
namespace NuGet.Test.TestExtensions.TestableVSCredentialProvider
6+
{
7+
public sealed class TestCredentials
8+
: ICredentials
9+
{
10+
private readonly string _username;
11+
private readonly SecureString _token;
12+
13+
internal TestCredentials(string username, SecureString token)
14+
{
15+
_username = username;
16+
_token = token;
17+
}
18+
19+
public NetworkCredential GetCredential(Uri uri, string authType)
20+
{
21+
return new NetworkCredential(_username, _token);
22+
}
23+
}
24+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# TestableVSCredentialProvider
2+
3+
## Overview
4+
5+
On NuGet VSIX startup in Visual Studio, NuGet will search for any MEF exports of IVsCredentialProvider,
6+
and add them to its internal list of credential providers to consult for credentials when a package
7+
source fails with an HTTP 401 response.
8+
9+
This project generates a VSIX that exports two test credential providers, to help with validating
10+
scenarios in which multiple credential providers are present.
11+
12+
By default, each of these test credential providers will trace calls to it, and by default respond
13+
with null credentials, indicating that it does not own providing credential for the given uri. Testers
14+
can verify that these providers have been called by configuring tracing in Visual Studio.
15+
16+
## Configuring test responses
17+
18+
A test provider can also be configured to return credentials for a given package source by appending
19+
query string parameters to the package source url.
20+
21+
| Credential Provider | Query String | Notes |
22+
| ----------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------- |
23+
| TestCredentialProvider | testCredentialProvider-responseUser | used to set the username of the credential returned by TestCredentialProvider for this source |
24+
| TestCredentialProvider | testCredentialProvider-responsePassword | used to set the password of the credential returned by TestCredentialProvider for this source |
25+
| TestCredentialProvider2 | testCredentialProvider2-responseUser | used to set the username of the credential returned by TestCredentialProvider2 for this source |
26+
| TestCredentialProvider2 | testCredentialProvider2-responsePassword | used to set the username of the credential returned by TestCredentialProvider2 for this source |
27+
28+
Example Package Source:
29+
30+
https://www.myget.org/F/my-secure-feed/api/v3/index.json?testCredentialProvider2-responseUser=user1&testCredentialProvider2-responsePassword=Password1
31+
32+
In this example, TestCredentialProvider will return null credentials, and TestCredentialProvider2
33+
will return {"username": "user1", "password": "Password1"} credentials.
34+
35+
## Installation
36+
37+
On build, both a v14 and v15 VSIX will be output. Install the appropriate VSIX in visual studio alongside
38+
the NuGet VSIX in order to use the test credential providers.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
5+
<MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
6+
<SchemaVersion>2.0</SchemaVersion>
7+
<ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
8+
<ProjectGuid>{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}</ProjectGuid>
9+
<OutputType>Library</OutputType>
10+
<AppDesignerFolder>Properties</AppDesignerFolder>
11+
<RootNamespace>NuGet.Test.TestExtensions.TestableVSCredentialProvider</RootNamespace>
12+
<AssemblyName>TestableVSCredentialProvider</AssemblyName>
13+
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
14+
<GeneratePkgDefFile>false</GeneratePkgDefFile>
15+
<IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer>
16+
<IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
17+
<IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
18+
<DeployExtension>False</DeployExtension>
19+
<RunCodeAnalysis>false</RunCodeAnalysis>
20+
<ManagePackageVersionsCentrally>False</ManagePackageVersionsCentrally>
21+
<Description>A sample VS credential provider used for integration tests.</Description>
22+
<TargetFrameworkProfile />
23+
<Prefer32Bit>false</Prefer32Bit>
24+
<OutputPath>bin\Debug\</OutputPath>
25+
<PlatformTarget>x86</PlatformTarget>
26+
</PropertyGroup>
27+
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
28+
<Prefer32Bit>false</Prefer32Bit>
29+
</PropertyGroup>
30+
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
31+
<Prefer32Bit>false</Prefer32Bit>
32+
<DocumentationFile>
33+
</DocumentationFile>
34+
<PlatformTarget>x86</PlatformTarget>
35+
</PropertyGroup>
36+
<PropertyGroup>
37+
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
38+
<TargetFrameworkProfile />
39+
<ProjectGuid>{7D0DDCB8-E362-4283-8AC8-3FF1E9B0263E}</ProjectGuid>
40+
</PropertyGroup>
41+
<ItemGroup>
42+
<Compile Include="TestCredentialProvider2.cs" />
43+
<Compile Include="TestCredentialProvider.cs" />
44+
<Compile Include="Properties\AssemblyInfo.cs" />
45+
<Compile Include="TestCredentials.cs" />
46+
</ItemGroup>
47+
<ItemGroup>
48+
<None Include="app.config" />
49+
<None Include="source.extension.vsixmanifest">
50+
<SubType>Designer</SubType>
51+
</None>
52+
<None Include="TestableVSCredentialProvider.README.md" />
53+
</ItemGroup>
54+
<ItemGroup>
55+
<Reference Include="System" />
56+
<Reference Include="System.ComponentModel.Composition" />
57+
<Reference Include="System.Web" />
58+
</ItemGroup>
59+
<ItemGroup>
60+
<PackageReference Include="Microsoft.VSSDK.BuildTools" PrivateAssets="All" />
61+
<PackageReference Include="NuGet.VisualStudio">
62+
<Version>17.11.1</Version>
63+
</PackageReference>
64+
</ItemGroup>
65+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
66+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
3+
<Metadata>
4+
<Identity Id="NuGet.TestableVSCredentialProvider.EFFA9532-96B6-42F3-8B5F-DE775F5049C2" Version="1.0" Language="en-US" Publisher="NuGet" />
5+
<DisplayName>NuGet.TestableVSCredentialProvider</DisplayName>
6+
<Description xml:space="preserve">Testable NuGet Credential Provider for Visual Studio</Description>
7+
</Metadata>
8+
<Prerequisites>
9+
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,17.0)" DisplayName="Visual Studio core editor" />
10+
</Prerequisites>
11+
<Installation>
12+
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[14.0,]" />
13+
</Installation>
14+
<Dependencies>
15+
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
16+
</Dependencies>
17+
<Assets>
18+
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
19+
</Assets>
20+
</PackageManifest>

0 commit comments

Comments
 (0)