Skip to content

Commit 7c9d27e

Browse files
authored
Add support for credential persistence (#480)
add support for credential persistence
1 parent e5e4dc7 commit 7c9d27e

14 files changed

+1571
-751
lines changed

src/PSGet.Format.ps1xml

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
<?xml version="1.0" encoding="utf-8" ?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Configuration>
33
<ViewDefinitions>
44
<View>
55
<Name>PSResourceInfo</Name>
66
<ViewSelectedBy>
77
<TypeName>Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo</TypeName>
8-
</ViewSelectedBy>
8+
</ViewSelectedBy>
99
<TableControl>
10-
<TableHeaders>
11-
<TableColumnHeader><Label>Name</Label></TableColumnHeader>
12-
<TableColumnHeader><Label>Version</Label></TableColumnHeader>
13-
<TableColumnHeader><Label>Prerelease</Label></TableColumnHeader>
14-
<TableColumnHeader><Label>Repository</Label></TableColumnHeader>
15-
<TableColumnHeader><Label>Description</Label></TableColumnHeader>
16-
</TableHeaders>
10+
<TableHeaders>
11+
<TableColumnHeader><Label>Name</Label></TableColumnHeader>
12+
<TableColumnHeader><Label>Version</Label></TableColumnHeader>
13+
<TableColumnHeader><Label>Prerelease</Label></TableColumnHeader>
14+
<TableColumnHeader><Label>Repository</Label></TableColumnHeader>
15+
<TableColumnHeader><Label>Description</Label></TableColumnHeader>
16+
</TableHeaders>
1717
<TableRowEntries>
1818
<TableRowEntry>
1919
<TableColumnItems>
@@ -23,42 +23,66 @@
2323
<TableColumnItem><PropertyName>Repository</PropertyName></TableColumnItem>
2424
<TableColumnItem><PropertyName>Description</PropertyName></TableColumnItem>
2525
</TableColumnItems>
26-
</TableRowEntry>
26+
</TableRowEntry>
2727
</TableRowEntries>
2828
</TableControl>
2929
</View>
3030
<View>
3131
<Name>PSIncludedResourceInfoTable</Name>
3232
<ViewSelectedBy>
33-
<TypeName>Microsoft.PowerShell.PowerShellGet.UtilClasses.PSIncludedResourceInfo</TypeName>
33+
<TypeName>Microsoft.PowerShell.PowerShellGet.UtilClasses.PSIncludedResourceInfo</TypeName>
3434
</ViewSelectedBy>
3535
<TableControl>
36-
<TableHeaders>
37-
<TableColumnHeader><Label>Name</Label></TableColumnHeader>
38-
<TableColumnHeader>
39-
<Label>Version</Label>
40-
</TableColumnHeader>
41-
<TableColumnHeader>
42-
<Label>Prerelease</Label>
43-
</TableColumnHeader>
44-
<TableColumnHeader>
45-
<Label>ModuleName</Label>
46-
</TableColumnHeader>
47-
<TableColumnHeader>
48-
<Label>Repository</Label>
49-
</TableColumnHeader>
50-
</TableHeaders>
51-
<TableRowEntries>
52-
<TableRowEntry>
53-
<TableColumnItems>
54-
<TableColumnItem><PropertyName>Name</PropertyName></TableColumnItem>
55-
<TableColumnItem><ScriptBlock>$_.ParentResource.Version</ScriptBlock></TableColumnItem>
56-
<TableColumnItem><ScriptBlock>$_.ParentResource.PrereleaseLabel</ScriptBlock></TableColumnItem>
57-
<TableColumnItem><ScriptBlock>$_.ParentResource.Name</ScriptBlock></TableColumnItem>
58-
<TableColumnItem><ScriptBlock>$_.ParentResource.Repository</ScriptBlock></TableColumnItem>
59-
</TableColumnItems>
60-
</TableRowEntry>
61-
</TableRowEntries>
36+
<TableHeaders>
37+
<TableColumnHeader><Label>Name</Label></TableColumnHeader>
38+
<TableColumnHeader>
39+
<Label>Version</Label>
40+
</TableColumnHeader>
41+
<TableColumnHeader>
42+
<Label>Prerelease</Label>
43+
</TableColumnHeader>
44+
<TableColumnHeader>
45+
<Label>ModuleName</Label>
46+
</TableColumnHeader>
47+
<TableColumnHeader>
48+
<Label>Repository</Label>
49+
</TableColumnHeader>
50+
</TableHeaders>
51+
<TableRowEntries>
52+
<TableRowEntry>
53+
<TableColumnItems>
54+
<TableColumnItem><PropertyName>Name</PropertyName></TableColumnItem>
55+
<TableColumnItem><ScriptBlock>$_.ParentResource.Version</ScriptBlock></TableColumnItem>
56+
<TableColumnItem><ScriptBlock>$_.ParentResource.PrereleaseLabel</ScriptBlock></TableColumnItem>
57+
<TableColumnItem><ScriptBlock>$_.ParentResource.Name</ScriptBlock></TableColumnItem>
58+
<TableColumnItem><ScriptBlock>$_.ParentResource.Repository</ScriptBlock></TableColumnItem>
59+
</TableColumnItems>
60+
</TableRowEntry>
61+
</TableRowEntries>
62+
</TableControl>
63+
</View>
64+
<View>
65+
<Name>PSRepositoryInfo</Name>
66+
<ViewSelectedBy>
67+
<TypeName>Microsoft.PowerShell.PowerShellGet.UtilClasses.PSRepositoryInfo</TypeName>
68+
</ViewSelectedBy>
69+
<TableControl>
70+
<TableHeaders>
71+
<TableColumnHeader><Label>Name</Label></TableColumnHeader>
72+
<TableColumnHeader><Label>Url</Label></TableColumnHeader>
73+
<TableColumnHeader><Label>Trusted</Label></TableColumnHeader>
74+
<TableColumnHeader><Label>Priority</Label></TableColumnHeader>
75+
</TableHeaders>
76+
<TableRowEntries>
77+
<TableRowEntry>
78+
<TableColumnItems>
79+
<TableColumnItem><PropertyName>Name</PropertyName></TableColumnItem>
80+
<TableColumnItem><PropertyName>Url</PropertyName></TableColumnItem>
81+
<TableColumnItem><PropertyName>Trusted</PropertyName></TableColumnItem>
82+
<TableColumnItem><PropertyName>Priority</PropertyName></TableColumnItem>
83+
</TableColumnItems>
84+
</TableRowEntry>
85+
</TableRowEntries>
6286
</TableControl>
6387
</View>
6488
</ViewDefinitions>

src/code/FindHelper.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
using NuGet.Protocol.Core.Types;
1010
using NuGet.Versioning;
1111
using System;
12+
using System.Collections;
1213
using System.Collections.Generic;
1314
using System.Data;
1415
using System.Linq;
1516
using System.Management.Automation;
1617
using System.Net;
1718
using System.Net.Http;
19+
using System.Security;
1820
using System.Threading;
1921

2022
using Dbg = System.Diagnostics.Debug;
@@ -139,7 +141,7 @@ public IEnumerable<PSResourceInfo> FindByResourceName(
139141

140142
// detect if Script repository needs to be added and/or Module repository needs to be skipped
141143
Uri psGalleryScriptsUrl = new Uri("http://www.powershellgallery.com/api/v2/items/psscript/");
142-
PSRepositoryInfo psGalleryScripts = new PSRepositoryInfo(_psGalleryScriptsRepoName, psGalleryScriptsUrl, repositoriesToSearch[i].Priority, false);
144+
PSRepositoryInfo psGalleryScripts = new PSRepositoryInfo(_psGalleryScriptsRepoName, psGalleryScriptsUrl, repositoriesToSearch[i].Priority, trusted: false, credentialInfo: null);
143145
if (_type == ResourceType.None)
144146
{
145147
_cmdletPassedIn.WriteVerbose("Null Type provided, so add PSGalleryScripts repository");
@@ -159,7 +161,7 @@ public IEnumerable<PSResourceInfo> FindByResourceName(
159161

160162
// detect if Script repository needs to be added and/or Module repository needs to be skipped
161163
Uri poshTestGalleryScriptsUrl = new Uri("https://www.poshtestgallery.com/api/v2/items/psscript/");
162-
PSRepositoryInfo poshTestGalleryScripts = new PSRepositoryInfo(_poshTestGalleryScriptsRepoName, poshTestGalleryScriptsUrl, repositoriesToSearch[i].Priority, false);
164+
PSRepositoryInfo poshTestGalleryScripts = new PSRepositoryInfo(_poshTestGalleryScriptsRepoName, poshTestGalleryScriptsUrl, repositoriesToSearch[i].Priority, trusted: false, credentialInfo: null);
163165
if (_type == ResourceType.None)
164166
{
165167
_cmdletPassedIn.WriteVerbose("Null Type provided, so add PoshTestGalleryScripts repository");
@@ -180,7 +182,8 @@ public IEnumerable<PSResourceInfo> FindByResourceName(
180182
_cmdletPassedIn.WriteVerbose(string.Format("Searching in repository {0}", repositoriesToSearch[i].Name));
181183
foreach (var pkg in SearchFromRepository(
182184
repositoryName: repositoriesToSearch[i].Name,
183-
repositoryUrl: repositoriesToSearch[i].Url))
185+
repositoryUrl: repositoriesToSearch[i].Url,
186+
repositoryCredentialInfo: repositoriesToSearch[i].CredentialInfo))
184187
{
185188
yield return pkg;
186189
}
@@ -193,7 +196,8 @@ public IEnumerable<PSResourceInfo> FindByResourceName(
193196

194197
private IEnumerable<PSResourceInfo> SearchFromRepository(
195198
string repositoryName,
196-
Uri repositoryUrl)
199+
Uri repositoryUrl,
200+
PSCredentialInfo repositoryCredentialInfo)
197201
{
198202
PackageSearchResource resourceSearch;
199203
PackageMetadataResource resourceMetadata;
@@ -229,12 +233,25 @@ private IEnumerable<PSResourceInfo> SearchFromRepository(
229233

230234
// HTTP, HTTPS, FTP Uri schemes (only other Uri schemes allowed by RepositorySettings.Read() API)
231235
PackageSource source = new PackageSource(repositoryUrl.ToString());
236+
237+
// Explicitly passed in Credential takes precedence over repository CredentialInfo
232238
if (_credential != null)
233239
{
234240
string password = new NetworkCredential(string.Empty, _credential.Password).Password;
235241
source.Credentials = PackageSourceCredential.FromUserInput(repositoryUrl.ToString(), _credential.UserName, password, true, null);
236242
_cmdletPassedIn.WriteVerbose("credential successfully set for repository: " + repositoryName);
237243
}
244+
else if (repositoryCredentialInfo != null)
245+
{
246+
PSCredential repoCredential = Utils.GetRepositoryCredentialFromSecretManagement(
247+
repositoryName,
248+
repositoryCredentialInfo,
249+
_cmdletPassedIn);
250+
251+
string password = new NetworkCredential(string.Empty, repoCredential.Password).Password;
252+
source.Credentials = PackageSourceCredential.FromUserInput(repositoryUrl.ToString(), repoCredential.UserName, password, true, null);
253+
_cmdletPassedIn.WriteVerbose("credential successfully read from vault and set for repository: " + repositoryName);
254+
}
238255

239256
// GetCoreV3() API is able to handle V2 and V3 repository endpoints
240257
var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider);

src/code/InstallHelper.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ private List<PSResourceInfo> ProcessRepositories(
226226

227227
List<PSResourceInfo> pkgsInstalled = InstallPackage(
228228
pkgsFromRepoToInstall,
229+
repoName,
229230
repo.Url.AbsoluteUri,
231+
repo.CredentialInfo,
230232
credential,
231233
isLocalRepo);
232234

@@ -303,7 +305,9 @@ private IEnumerable<PSResourceInfo> FilterByInstalledPkgs(IEnumerable<PSResource
303305

304306
private List<PSResourceInfo> InstallPackage(
305307
IEnumerable<PSResourceInfo> pkgsToInstall, // those found to be required to be installed (includes Dependency packages as well)
308+
string repoName,
306309
string repoUrl,
310+
PSCredentialInfo repoCredentialInfo,
307311
PSCredential credential,
308312
bool isLocalRepo)
309313
{
@@ -398,11 +402,23 @@ private List<PSResourceInfo> InstallPackage(
398402
/* Download from a non-local repository */
399403
// Set up NuGet API resource for download
400404
PackageSource source = new PackageSource(repoUrl);
405+
406+
// Explicitly passed in Credential takes precedence over repository CredentialInfo
401407
if (credential != null)
402408
{
403409
string password = new NetworkCredential(string.Empty, credential.Password).Password;
404410
source.Credentials = PackageSourceCredential.FromUserInput(repoUrl, credential.UserName, password, true, null);
405411
}
412+
else if (repoCredentialInfo != null)
413+
{
414+
PSCredential repoCredential = Utils.GetRepositoryCredentialFromSecretManagement(
415+
repoName,
416+
repoCredentialInfo,
417+
_cmdletPassedIn);
418+
419+
string password = new NetworkCredential(string.Empty, repoCredential.Password).Password;
420+
source.Credentials = PackageSourceCredential.FromUserInput(repoUrl, repoCredential.UserName, password, true, null);
421+
}
406422
var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider);
407423
SourceRepository repository = new SourceRepository(source, provider);
408424

src/code/PSCredentialInfo.cs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Management.Automation;
6+
7+
namespace Microsoft.PowerShell.PowerShellGet.UtilClasses
8+
{
9+
/// <summary>
10+
/// This class contains information for a repository's authentication credential.
11+
/// </summary>
12+
public sealed class PSCredentialInfo
13+
{
14+
#region Constructor
15+
16+
/// <summary>
17+
/// Initializes a new instance of the PSCredentialInfo class with
18+
/// vaultName and secretName of type string, and
19+
/// (optionally) credential of type PSCredential.
20+
/// </summary>
21+
/// <param name="vaultName"></param>
22+
/// <param name="secretName"></param>
23+
/// <param name="credential"></param>
24+
public PSCredentialInfo(string vaultName, string secretName, PSCredential credential = null)
25+
{
26+
VaultName = vaultName;
27+
SecretName = secretName;
28+
Credential = credential;
29+
}
30+
31+
/// <summary>
32+
/// Initializes a new instance of the PSCredentialInfo class with
33+
/// vaultName and secretName of type string, and
34+
/// (optionally) credential of type PSCredential from a PSObject.
35+
/// </summary>
36+
/// <param name="psObject"></param>
37+
public PSCredentialInfo(PSObject psObject)
38+
{
39+
if (psObject == null)
40+
{
41+
throw new ArgumentNullException(nameof(psObject));
42+
}
43+
44+
VaultName = (string) psObject.Properties[PSCredentialInfo.VaultNameAttribute]?.Value;
45+
SecretName = (string) psObject.Properties[PSCredentialInfo.SecretNameAttribute]?.Value;
46+
Credential = (PSCredential) psObject.Properties[PSCredentialInfo.CredentialAttribute]?.Value;
47+
}
48+
49+
#endregion
50+
51+
#region Members
52+
53+
private string _vaultName;
54+
/// <summary>
55+
/// the Name of the SecretManagement Vault
56+
/// </summary>
57+
public string VaultName {
58+
get
59+
{
60+
return _vaultName;
61+
}
62+
63+
private set
64+
{
65+
if (string.IsNullOrEmpty(value))
66+
{
67+
throw new ArgumentException($"Invalid CredentialInfo, {PSCredentialInfo.VaultNameAttribute} must be a non-empty string");
68+
}
69+
70+
_vaultName = value;
71+
}
72+
}
73+
74+
private string _secretName;
75+
/// <summary>
76+
/// the Name of the Secret
77+
/// </summary>
78+
public string SecretName {
79+
get
80+
{
81+
return _secretName;
82+
}
83+
84+
private set
85+
{
86+
if (string.IsNullOrEmpty(value))
87+
{
88+
throw new ArgumentException($"Invalid CredentialInfo, {PSCredentialInfo.SecretNameAttribute} must be a non-empty string");
89+
}
90+
91+
_secretName = value;
92+
}
93+
}
94+
95+
/// <summary>
96+
/// optional Credential object to save in a SecretManagement Vault
97+
/// for authenticating to repositories
98+
/// </summary>
99+
public PSCredential Credential { get; private set; }
100+
101+
internal static readonly string VaultNameAttribute = nameof(VaultName);
102+
internal static readonly string SecretNameAttribute = nameof(SecretName);
103+
internal static readonly string CredentialAttribute = nameof(Credential);
104+
105+
#endregion
106+
}
107+
}

src/code/PSRepositoryInfo.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ public sealed class PSRepositoryInfo
1313
{
1414
#region Constructor
1515

16-
public PSRepositoryInfo(string name, Uri url, int priority, bool trusted)
16+
public PSRepositoryInfo(string name, Uri url, int priority, bool trusted, PSCredentialInfo credentialInfo)
1717
{
1818
Name = name;
1919
Url = url;
2020
Priority = priority;
2121
Trusted = trusted;
22+
CredentialInfo = credentialInfo;
2223
}
2324

2425
#endregion
@@ -37,6 +38,7 @@ public PSRepositoryInfo(string name, Uri url, int priority, bool trusted)
3738

3839
/// <summary>
3940
/// whether the repository is trusted
41+
/// </summary>
4042
public bool Trusted { get; }
4143

4244
/// <summary>
@@ -45,6 +47,11 @@ public PSRepositoryInfo(string name, Uri url, int priority, bool trusted)
4547
[ValidateRange(0, 50)]
4648
public int Priority { get; }
4749

50+
/// <summary>
51+
/// the credential information for repository authentication
52+
/// </summary>
53+
public PSCredentialInfo CredentialInfo { get; }
54+
4855
#endregion
4956
}
5057
}

0 commit comments

Comments
 (0)