diff --git a/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj b/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj
index a5a137283..602df84d1 100644
--- a/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj
+++ b/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj
@@ -7,7 +7,10 @@
Properties
AzureDevOps.Authentication.Test
AzureDevOps.Authentication.Proxy
-
+
+
+ v4.5.2
+
@@ -31,8 +34,8 @@
..\..\packages\Moq.4.8.2\lib\net45\Moq.dll
-
- ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll
+
+ ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll
@@ -56,7 +59,9 @@
-
+
+ Designer
+
diff --git a/AzureDevOps.Authentication/Proxy/packages.config b/AzureDevOps.Authentication/Proxy/packages.config
index 4756c7bb1..bb78bac8d 100644
--- a/AzureDevOps.Authentication/Proxy/packages.config
+++ b/AzureDevOps.Authentication/Proxy/packages.config
@@ -3,7 +3,7 @@
-
+
\ No newline at end of file
diff --git a/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj b/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj
index 7d81070b4..12803fd77 100644
--- a/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj
+++ b/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj
@@ -10,7 +10,8 @@
{19770407-D7D8-4A37-914C-F552FF4B90D4}
AzureDevOps.Authentication
AzureDevOps.Authentication
- v4.5.1
+ v4.5.2
+
@@ -18,8 +19,8 @@
$(ProjectDir)$(OutputPath)
-
- ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.19.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.5.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.19.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll
diff --git a/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj b/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj
index a5f75d085..ae4e06f44 100644
--- a/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj
+++ b/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj
@@ -7,7 +7,10 @@
Properties
Bitbucket.Authentication.Test
Bitbucket.Authentication.Proxy
-
+
+
+ v4.5.2
+
@@ -31,8 +34,8 @@
..\..\packages\Moq.4.8.2\lib\net45\Moq.dll
-
- ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll
+
+ ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll
diff --git a/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj b/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj
index 3f52f585e..bfefa0b41 100644
--- a/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj
+++ b/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj
@@ -10,7 +10,8 @@
{EE663736-5BAD-4CA6-A4F8-99978925AD8A}
Bitbucket.Authentication
Atlassian.Bitbucket.Authentication
- v4.5.1
+ v4.5.2
+
diff --git a/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj b/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj
index 53c57081d..06ffc35fa 100644
--- a/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj
+++ b/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj
@@ -14,7 +14,10 @@
$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
False
UnitTest
-
+
+
+ v4.5.2
+
diff --git a/Cli/Manager/Cli-Manager.csproj b/Cli/Manager/Cli-Manager.csproj
index 944e771d7..76dec81b6 100644
--- a/Cli/Manager/Cli-Manager.csproj
+++ b/Cli/Manager/Cli-Manager.csproj
@@ -6,7 +6,8 @@
Properties
git-credential-manager
true
-
+
+
Exe
{19770407-63D4-40A8-A9DF-F1C4B473308A}
Cli-Manager
@@ -24,10 +25,25 @@
get
+
+ ..\..\packages\Microsoft.Azure.Services.AppAuthentication.1.0.3\lib\net452\Microsoft.Azure.Services.AppAuthentication.dll
+
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.5.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
+
+
+
+
+
+
+
+
+
+
@@ -57,6 +73,9 @@
false
+
+ Designer
+
@@ -67,5 +86,7 @@
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
\ No newline at end of file
diff --git a/Cli/Manager/app.config b/Cli/Manager/app.config
index ff9950103..be876fec2 100644
--- a/Cli/Manager/app.config
+++ b/Cli/Manager/app.config
@@ -1,3 +1,20 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Cli/Manager/packages.config b/Cli/Manager/packages.config
index 644ec0093..e2e3ccbae 100644
--- a/Cli/Manager/packages.config
+++ b/Cli/Manager/packages.config
@@ -1,5 +1,7 @@
+
+
diff --git a/Cli/Test/ProgramTests.cs b/Cli/Test/ProgramTests.cs
index d76616ef6..b9d441d32 100644
--- a/Cli/Test/ProgramTests.cs
+++ b/Cli/Test/ProgramTests.cs
@@ -29,7 +29,7 @@
using Microsoft.Alm.Authentication;
using Moq;
using Xunit;
-using Azure = AzureDevOps.Authentication;
+using AzureDev = AzureDevOps.Authentication;
using Git = Microsoft.Alm.Authentication.Git;
namespace Microsoft.Alm.Cli.Test
@@ -108,7 +108,7 @@ public async Task LoadOperationArgumentsTest()
Assert.NotNull(opargs.DevOpsTokenScope);
- var expectedScope = Azure.TokenScope.BuildAccess | Azure.TokenScope.CodeWrite;
+ var expectedScope = AzureDev.TokenScope.BuildAccess | AzureDev.TokenScope.CodeWrite;
Assert.Equal(expectedScope, opargs.DevOpsTokenScope);
}
diff --git a/Cli/Test/app.config b/Cli/Test/app.config
index b546f120b..a7de87bc0 100644
--- a/Cli/Test/app.config
+++ b/Cli/Test/app.config
@@ -10,6 +10,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj b/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj
index 5a487378e..3b4357bbc 100644
--- a/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj
+++ b/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj
@@ -31,8 +31,8 @@
..\..\packages\Moq.4.8.2\lib\net45\Moq.dll
-
- ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll
+
+ ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll
diff --git a/GitHub.Authentication/Src/GitHub.Authentication.csproj b/GitHub.Authentication/Src/GitHub.Authentication.csproj
index b6b6d7769..9ec7d3af0 100644
--- a/GitHub.Authentication/Src/GitHub.Authentication.csproj
+++ b/GitHub.Authentication/Src/GitHub.Authentication.csproj
@@ -12,7 +12,7 @@
GitHub.Authentication
{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
GitHub.Authentication
- v4.5.1
+ v4.5.2
diff --git a/GitHub.Authentication/Test/AuthenticationTests.cs b/GitHub.Authentication/Test/AuthenticationTests.cs
index 922ce4f08..a61aef372 100644
--- a/GitHub.Authentication/Test/AuthenticationTests.cs
+++ b/GitHub.Authentication/Test/AuthenticationTests.cs
@@ -27,7 +27,7 @@ public async Task GetSetCredentialsNormalizesGistUrls(string writeUriString, str
new Authentication.AcquireAuthenticationCodeDelegate(prompts.AuthenticationCodeModalPrompt),
null);
- await authentication.SetCredentials(new Uri(writeUriString), new Credential("haacked"));
+ await authentication.SetCredentials(new Uri(writeUriString), new Credential("haacked", string.Empty));
var credentials = await authentication.GetCredentials(retrieveUri);
Assert.Equal("haacked", credentials.Username);
}
@@ -48,7 +48,7 @@ public async Task GetSetCredentialsDoesNotReturnCredentialForRandomUrl()
new Authentication.AcquireAuthenticationCodeDelegate(prompts.AuthenticationCodeModalPrompt),
null);
- await authentication.SetCredentials(new Uri("https://github.com/"), new Credential("haacked"));
+ await authentication.SetCredentials(new Uri("https://github.com/"), new Credential("haacked", string.Empty));
Assert.Null(await authentication.GetCredentials(retrieveUri));
}
diff --git a/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj b/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj
index fe11bc87f..500bcf67f 100644
--- a/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj
+++ b/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj
@@ -23,8 +23,8 @@
..\..\packages\Moq.4.8.2\lib\net45\Moq.dll
-
- ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll
+
+ ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll
diff --git a/Microsoft.Alm.Authentication/Src/Credential.cs b/Microsoft.Alm.Authentication/Src/Credential.cs
index 5d04b80e0..fddc7f7a2 100644
--- a/Microsoft.Alm.Authentication/Src/Credential.cs
+++ b/Microsoft.Alm.Authentication/Src/Credential.cs
@@ -42,25 +42,17 @@ public sealed class Credential : Secret, IEquatable
///
/// Creates a credential object with a username and password pair.
///
- /// The username value of the ``.
- /// The password value of the ``.
- public Credential(string username, string password)
+ /// The username value of the ``.
+ /// The password value of the ``.
+ public Credential(string Username, string Password)
{
- if (username is null)
- throw new ArgumentNullException(nameof(username));
+ if (Username is null)
+ throw new ArgumentNullException(nameof(Username));
- _password = password ?? string.Empty;
- _username = username;
+ _password = Password ?? string.Empty;
+ _username = Username;
}
- ///
- /// Creates a credential object with only a username.
- ///
- /// The username value of the ``.
- public Credential(string username)
- : this(username, string.Empty)
- { }
-
private readonly string _password;
private readonly string _username;
diff --git a/Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs b/Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs
new file mode 100644
index 000000000..b3369c7e3
--- /dev/null
+++ b/Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs
@@ -0,0 +1,256 @@
+//------------------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.Azure.Services.AppAuthentication;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+
+namespace Microsoft.Azure.KeyVault.Helper
+{
+ ///
+ /// This class provides access to secrets stored in the KeyVault
+ ///
+ public sealed class KeyVaultHelper : IDisposable
+ {
+ public struct Config
+ {
+ public string CertificateThumbprint { get; set; }
+ public string CertificateStoreType { get; set; }
+ public string KeyVaultUrl { get; set; }
+ public string ClientId { get; set; }
+ public bool? UseMsi { get; set; }
+ }
+
+ private static KeyVaultHelper _instance = null;
+ private static KeyVaultHelper.Config _config;
+
+ private readonly string _keyVaultUrl;
+ private readonly string _clientId;
+ private readonly string _certificateThumbprint;
+ private readonly StoreLocation _storeLocation;
+ private readonly bool? _useMsi;
+ private readonly KeyVaultClient _keyVaultClient;
+ private string _accessToken;
+ private DateTimeOffset _expiration;
+
+ private KeyVaultHelper()
+ {
+ _keyVaultUrl = _config.KeyVaultUrl;
+ if (string.IsNullOrWhiteSpace(_keyVaultUrl))
+ {
+ throw new KeyVaultHelperConfigurationException("KeyVault URL is not set in the git config file");
+ }
+
+ _useMsi = _config.UseMsi;
+ if (!_useMsi.HasValue)
+ {
+ _useMsi = false;
+ }
+
+ // using MSI (Managed Service Identity) method of authentication means we dont need any service principal arguments - appId, certificate or cert store
+ if (_useMsi == true)
+ {
+ var azureServiceTokenProvider = new AzureServiceTokenProvider();
+ _keyVaultClient = new KeyVaultClient(
+ new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
+ }
+ else
+ {
+ _certificateThumbprint = _config.CertificateThumbprint;
+ if (string.IsNullOrWhiteSpace(_certificateThumbprint))
+ {
+ throw new KeyVaultHelperConfigurationException("Certificate thumbprint is required for Azure AD application authentication. Please set KeyVaultAuthCertificateThumbprint setting in the git config file");
+ }
+ _certificateThumbprint = _certificateThumbprint.Replace(" ", "").Trim();
+
+ string certificateStoreType = _config.CertificateStoreType;
+ if (string.IsNullOrWhiteSpace(certificateStoreType))
+ {
+ throw new KeyVaultHelperConfigurationException("Certificate store type (CurrentUser or LocalMachine) is required for KeyVault access. Please set KeyVaultAuthCertificateStoreType setting in the git config file");
+ }
+
+ if (!Enum.TryParse(certificateStoreType, true, out _storeLocation))
+ throw new KeyVaultHelperConfigurationException("Invalid certificate store type in git config file:" + certificateStoreType);
+
+ _clientId = _config.ClientId;
+ if (string.IsNullOrWhiteSpace(_clientId))
+ {
+ throw new KeyVaultHelperConfigurationException("Client Id (applicationID) for KeyVault App is not set in the git config file");
+ }
+
+ _keyVaultClient = new KeyVaultClient(GetAccessToken);
+ }
+ }
+
+ public static void Configure(KeyVaultHelper.Config config)
+ {
+ // validate and clean up config
+ if (string.IsNullOrWhiteSpace(config.KeyVaultUrl))
+ {
+ throw new KeyVaultHelperConfigurationException("KeyVault URL is not set in the git config file");
+ }
+
+ config.KeyVaultUrl = config.KeyVaultUrl.Trim();
+
+ // if msi is not specified or value is false retrieve the cert details
+ if (config.UseMsi != true)
+ {
+ if (string.IsNullOrWhiteSpace(config.CertificateThumbprint))
+ {
+ throw new KeyVaultHelperConfigurationException("Certificate thumbprint is required for Azure AD application authentication. Please set KeyVaultAuthCertificateThumbprint setting in the git config file");
+ }
+
+ config.CertificateThumbprint = config.CertificateThumbprint.Replace(" ", "").Trim();
+
+ if (string.IsNullOrWhiteSpace(config.CertificateStoreType))
+ {
+ throw new KeyVaultHelperConfigurationException("Certificate store type (CurrentUser or LocalMachine) is required for KeyVault access. Please set KeyVaultAuthCertificateStoreType setting in the git config file");
+ }
+
+ config.CertificateStoreType = config.CertificateStoreType.Trim();
+ StoreLocation storeLocation;
+ if (!Enum.TryParse(config.CertificateStoreType, true, out storeLocation))
+ throw new KeyVaultHelperConfigurationException("Invalid certificate store type in the git config file:" + config.CertificateStoreType);
+
+ if (string.IsNullOrWhiteSpace(config.ClientId))
+ {
+ throw new KeyVaultHelperConfigurationException("Client Id (applicationID) for KeyVault App is not set in the git config file");
+ }
+ config.ClientId = config.ClientId.Trim();
+ }
+ _config = config;
+ }
+
+ public static KeyVaultHelper KeyVault
+ {
+ get
+ {
+ if (_instance == null)
+ _instance = new KeyVaultHelper();
+ return _instance;
+ }
+ }
+
+ public async Task GetSecretAsync(string secretName)
+ {
+ if (string.IsNullOrEmpty(secretName))
+ {
+ throw new ArgumentNullException("secretName");
+ }
+
+ string fixedSecretName = FixSecretName(secretName);
+
+ var secret = await _keyVaultClient.GetSecretAsync(_keyVaultUrl, fixedSecretName).ConfigureAwait(false);
+
+ return secret.Value;
+ }
+
+ public async Task SetSecretAsync(string secretName, string secretValue)
+ {
+ if (string.IsNullOrEmpty(secretName))
+ {
+ throw new ArgumentNullException("secretName");
+ }
+ // KeyVault doesn't allow dots in key name, allows only alphanumeric and dashes. Replace dots with dash
+ string fixedSecretName = FixSecretName(secretName);
+
+ var secret = await _keyVaultClient.SetSecretAsync(_keyVaultUrl, fixedSecretName, secretValue);
+ return secret.Value;
+ }
+
+ public async Task DeleteSecretAsync(string secretName)
+ {
+ if (string.IsNullOrEmpty(secretName))
+ {
+ throw new ArgumentNullException("secretName");
+ }
+ // KeyVault doesn't allow dots in key name, allows only alphanumeric and dashes. Replace dots with dash
+ string fixedSecretName = FixSecretName (secretName);
+
+ var deletedSecretResult = await _keyVaultClient.DeleteSecretAsync(_keyVaultUrl, fixedSecretName);
+ return deletedSecretResult.Value;
+ }
+
+ private static string FixSecretName (string secretName)
+ {
+ // KeyVault doesn't allow dots or slashes in key name, allows only alphanumeric and dashes. Replace dots and slashes with dash
+ return secretName.Replace('.', '-').Replace('/', '-').Replace(' ', '-'); ;
+ }
+ private async Task GetAccessToken(string authority, string resource, string scope)
+ {
+ // get new token if needed or current token expires soon
+ if (_accessToken == null ||
+ _expiration == null ||
+ _expiration.UtcDateTime > DateTime.Now.AddMinutes(1).ToUniversalTime())
+ {
+ var context = new AuthenticationContext(authority);
+ var cert = RetrieveCertificate();
+ var clientAssertionCertificate = new ClientAssertionCertificate(_clientId, cert);
+
+ var result = await context.AcquireTokenAsync(resource, clientAssertionCertificate);
+ _expiration = result.ExpiresOn;
+ _accessToken = result.AccessToken;
+ }
+ return _accessToken;
+ }
+
+ private X509Certificate2 RetrieveCertificate()
+ {
+ X509Store certStore = null;
+ try
+ {
+ certStore = new X509Store(_storeLocation);
+ certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
+ var userCertCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, _certificateThumbprint, false);
+
+ if (userCertCollection?.Count == 0)
+ {
+ throw new KeyVaultHelperConfigurationException(
+ $"Certificate with thumbprint '{_certificateThumbprint}' not found in store '{certStore.Location}/{certStore.Name}'");
+ }
+ return userCertCollection[0];
+ }
+ catch (KeyVaultHelperConfigurationException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new KeyVaultHelperConfigurationException(
+ $"An error occurred accessing the '{_storeLocation}' certificate store.", ex);
+ }
+ finally
+ {
+ certStore?.Close();
+ }
+ }
+
+ // Implement IDisposable.
+ public void Dispose()
+ {
+ Dispose(true);
+ // This object will be cleaned up by the Dispose method.
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose(bool disposing)
+ {
+ // Check to see if Dispose has already been called.
+ if (_keyVaultClient != null && _instance != null)
+ {
+ // If disposing equals true, dispose all managed
+ // and unmanaged resources.
+ if (disposing)
+ {
+ _keyVaultClient.Dispose();
+ }
+ }
+ _instance = null;
+ }
+ }
+}
diff --git a/Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs b/Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs
new file mode 100644
index 000000000..83b754796
--- /dev/null
+++ b/Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs
@@ -0,0 +1,29 @@
+// ------------------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// ------------------------------------------------------------------------------
+
+using System;
+
+namespace Microsoft.Azure.KeyVault.Helper
+{
+ ///
+ /// This exception is thrown when KeyVaultHelper class can't read configuration from
+ /// config file or can't access cerificate store and retrieve certificate for KeyVault access.
+ ///
+ [Serializable]
+ public class KeyVaultHelperConfigurationException : Exception
+ {
+ public KeyVaultHelperConfigurationException(string message)
+ : base(message)
+ {
+ }
+
+ public KeyVaultHelperConfigurationException(string message, Exception ex)
+ : base(message + " See inner exception for details.", ex)
+ {
+ }
+ }
+
+}
diff --git a/Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs b/Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs
new file mode 100644
index 000000000..dc985d0cd
--- /dev/null
+++ b/Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs
@@ -0,0 +1,220 @@
+/**** Git Credential Manager for Windows ****
+ *
+ * Copyright (c) Microsoft Corporation
+ * All rights reserved.
+ *
+ * MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the """"Software""""), to deal
+ * in the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
+**/
+
+using Newtonsoft.Json;
+using System;
+using System.Threading.Tasks;
+using Microsoft.Azure.KeyVault.Helper;
+
+namespace Microsoft.Alm.Authentication
+{
+ public class KeyVaultSecretStore : ICredentialStore
+ {
+ private readonly RuntimeContext _context;
+ private Secret.UriNameConversionDelegate _getTargetName;
+ private readonly string _namespace;
+ private readonly ICredentialStore _credentialCache;
+
+ protected Git.ITrace Trace
+ => _context.Trace;
+
+ public KeyVaultSecretStore(RuntimeContext context,
+ string @namespace,
+ string keyVaultUrl,
+ bool? useMsi,
+ string certAuthStoreType,
+ string certAuthThumbprint,
+ string certAuthClientId) :
+ this (context, @namespace, null, keyVaultUrl, useMsi, certAuthStoreType, certAuthThumbprint, certAuthClientId, null)
+ { }
+
+ public KeyVaultSecretStore(RuntimeContext context,
+ string @namespace,
+ ICredentialStore credentialCache,
+ string keyVaultUrl,
+ bool? useMsi,
+ string certAuthStoreType,
+ string certAuthThumbprint,
+ string certAuthClientId,
+ Secret.UriNameConversionDelegate getTargetName)
+ {
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+ _context = context;
+
+ if (@namespace is null)
+ throw new ArgumentNullException(nameof(@namespace));
+
+ if (@namespace.IndexOfAny(BaseSecureStore.IllegalCharacters) != -1)
+ {
+ var inner = new FormatException("Namespace contains illegal characters.");
+ throw new ArgumentException(inner.Message, nameof(@namespace), inner);
+ }
+
+ _getTargetName = getTargetName ?? Secret.UriToName;
+
+ _namespace = @namespace;
+ _credentialCache = credentialCache ?? new SecretCache(context, @namespace, _getTargetName);
+ this._getTargetName = getTargetName;
+
+ KeyVaultHelper.Config config = new KeyVaultHelper.Config()
+ {
+ KeyVaultUrl = keyVaultUrl,
+ UseMsi = useMsi,
+ CertificateThumbprint = certAuthThumbprint,
+ CertificateStoreType = certAuthStoreType,
+ ClientId = certAuthClientId
+ };
+
+ KeyVaultHelper.Configure(config);
+ }
+ public string Namespace
+ {
+ get { return _namespace; }
+ }
+
+ public Secret.UriNameConversionDelegate UriNameConversion
+ {
+ get { return _getTargetName; }
+ set
+ {
+ if (value is null)
+ throw new ArgumentNullException(nameof(UriNameConversion));
+
+ _getTargetName = value;
+ }
+ }
+
+ public async Task DeleteCredentials(TargetUri targetUri)
+ {
+#if DEBUG
+ Trace.WriteLine($"targetUri: '{targetUri}': Key: '{GetKeyVaultKey(targetUri)}'");
+#endif
+
+ if (targetUri is null || string.IsNullOrEmpty(targetUri.Host))
+ throw new ArgumentNullException(nameof(targetUri));
+
+ string secret = null;
+ try
+ {
+ secret = await KeyVaultHelper.KeyVault.DeleteSecretAsync(GetKeyVaultKey(targetUri));
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Exception deleting the secret from KeyVault:" + ex.Message);
+ }
+
+ return string.IsNullOrEmpty(secret)
+ && await _credentialCache.DeleteCredentials(targetUri);
+ }
+
+ public async Task ReadCredentials(TargetUri targetUri)
+ {
+#if DEBUG
+ Trace.WriteLine($"targetUri: '{targetUri}': Key: '{GetKeyVaultKey(targetUri)}'");
+#endif
+ if (targetUri is null || string.IsNullOrEmpty(targetUri.Host))
+ throw new ArgumentNullException(nameof(targetUri));
+
+ return await _credentialCache.ReadCredentials(targetUri)
+ ?? await this.ReadKeyVaultCredentials(targetUri);
+ }
+
+ public async Task WriteCredentials(TargetUri targetUri, Credential credentials)
+ {
+#if DEBUG
+ Trace.WriteLine($"targetUri: '{targetUri}', userName: '{credentials?.Username}' PAT: '{credentials?.Password}', : Key: '{GetKeyVaultKey(targetUri)}'");
+#endif
+ if (targetUri is null || string.IsNullOrEmpty (targetUri.Host))
+ throw new ArgumentNullException(nameof(targetUri));
+
+ if (credentials is null)
+ throw new ArgumentNullException(nameof(credentials));
+ return await WriteKeyVaultCredentials(targetUri, credentials)
+ && await _credentialCache.WriteCredentials(targetUri, credentials);
+ }
+
+ private async Task ReadKeyVaultCredentials(TargetUri targetUri)
+ {
+ string secret = null;
+ try
+ {
+ secret = await KeyVaultHelper.KeyVault.GetSecretAsync(GetKeyVaultKey(targetUri));
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Exception getting the secret from KeyVault:" + ex.Message);
+ }
+
+ if (string.IsNullOrEmpty(secret))
+ {
+ return null;
+ }
+
+ // parse secret from JSon
+ try
+ {
+ Credential credential = JsonConvert.DeserializeObject(secret);
+ return credential;
+ }
+ catch (JsonException)
+ {
+ Trace.WriteLine("Keyvault secret doesn't contain Json value, returning as is");
+ return new Credential("PersonalAccessToken", secret);
+ }
+ }
+
+ private async Task WriteKeyVaultCredentials(TargetUri targetUri, Credential credentials)
+ {
+ string secret = "{ \"Username\" : \"" + credentials.Username + "\", \"Password\" : \"" + credentials.Password + "\", \"Message\" : \"\" }";
+ try
+ {
+ await KeyVaultHelper.KeyVault.SetSecretAsync(GetKeyVaultKey(targetUri), secret);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Exception writing a secret to KeyVault:" + ex.Message);
+ }
+ return false;
+ }
+
+ private static string GetKeyVaultKey (TargetUri targetUri)
+ {
+ string path = null;
+
+ if (targetUri.HasPath && !string.IsNullOrEmpty(targetUri.AbsolutePath))
+ path = targetUri.Host + targetUri.AbsolutePath;
+ else if (targetUri.ContainsUserInfo && !string.IsNullOrEmpty(targetUri.UserInfo))
+ path = targetUri.UserInfo + "-" + targetUri.Host;
+ else
+ path = targetUri.Host;
+
+ string key = path.Replace('.', '-').Replace('/', '-').Replace(' ', '-');
+ return key;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj b/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj
index 9ce86dcbe..a5363cd4e 100644
--- a/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj
+++ b/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj
@@ -5,12 +5,14 @@
Properties
Microsoft.Alm.Authentication
-
+
+
Library
{19770407-B493-459D-BB4F-04FBEFB1BA13}
Microsoft.Alm.Authentication
Microsoft.Alm.Authentication
- v4.5.1
+ v4.5.2
+
@@ -18,16 +20,56 @@
$(ProjectDir)$(OutputPath)
+
+ ..\..\packages\Hyak.Common.1.2.2\lib\net452\Hyak.Common.dll
+
+
+ ..\..\packages\Microsoft.Azure.Common.2.2.1\lib\net452\Microsoft.Azure.Common.dll
+
+
+ ..\..\packages\Microsoft.Azure.KeyVault.3.0.3\lib\net452\Microsoft.Azure.KeyVault.dll
+
+
+ ..\..\packages\Microsoft.Azure.KeyVault.WebKey.3.0.3\lib\net452\Microsoft.Azure.KeyVault.WebKey.dll
+
+
+ ..\..\packages\Microsoft.Azure.Services.AppAuthentication.1.0.3\lib\net452\Microsoft.Azure.Services.AppAuthentication.dll
+
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.5.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
+
+
+ ..\..\packages\Microsoft.Rest.ClientRuntime.2.3.18\lib\net452\Microsoft.Rest.ClientRuntime.dll
+
+
+ ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.3.18\lib\net452\Microsoft.Rest.ClientRuntime.Azure.dll
+
+
+ ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -63,8 +105,10 @@
-
-
+
+ Designer
+
+
@@ -74,5 +118,7 @@
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
\ No newline at end of file
diff --git a/Microsoft.Alm.Authentication/Src/app.config b/Microsoft.Alm.Authentication/Src/app.config
index d5a97e2b1..bc8302afd 100644
--- a/Microsoft.Alm.Authentication/Src/app.config
+++ b/Microsoft.Alm.Authentication/Src/app.config
@@ -4,8 +4,12 @@
-
+
+
+
+
+
-
+
diff --git a/Microsoft.Alm.Authentication/Src/packages.config b/Microsoft.Alm.Authentication/Src/packages.config
index 339420105..4e1484912 100644
--- a/Microsoft.Alm.Authentication/Src/packages.config
+++ b/Microsoft.Alm.Authentication/Src/packages.config
@@ -1,4 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Alm.Authentication/Test/app.config b/Microsoft.Alm.Authentication/Test/app.config
index 6caa983bd..d77de4848 100644
--- a/Microsoft.Alm.Authentication/Test/app.config
+++ b/Microsoft.Alm.Authentication/Test/app.config
@@ -4,11 +4,11 @@
-
+
-
+
diff --git a/Shared/Cli/Functions/Common.cs b/Shared/Cli/Functions/Common.cs
index 5acdf04e2..08c3afc3f 100644
--- a/Shared/Cli/Functions/Common.cs
+++ b/Shared/Cli/Functions/Common.cs
@@ -29,7 +29,7 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.Alm.Authentication;
-using Azure = AzureDevOps.Authentication;
+using AzureDev = AzureDevOps.Authentication;
using Bitbucket = Atlassian.Bitbucket.Authentication;
using Git = Microsoft.Alm.Authentication.Git;
using Github = GitHub.Authentication;
@@ -85,25 +85,25 @@ public static async Task CreateAuthentication(Program progra
program.Trace.WriteLine($"detecting authority type for '{operationArguments.TargetUri}'.");
// Detect the authority.
- authority = await Azure.Authentication.GetAuthentication(program.Context,
+ authority = await AzureDev.Authentication.GetAuthentication(program.Context,
operationArguments.TargetUri,
Program.DevOpsCredentialScope,
- new SecretStore(program.Context,
- secretsNamespace,
- Azure.Authentication.UriNameConversion))
+ CreateSecretStore(program.Context,
+ operationArguments,
+ AzureDev.Authentication.UriNameConversion))
?? Github.Authentication.GetAuthentication(program.Context,
operationArguments.TargetUri,
Program.GitHubCredentialScope,
- new SecretStore(program.Context,
- secretsNamespace,
+ CreateSecretStore(program.Context,
+ operationArguments,
Secret.UriToName),
githubCredentialCallback,
githubAuthcodeCallback,
null)
?? Bitbucket.Authentication.GetAuthentication(program.Context,
operationArguments.TargetUri,
- new SecretStore(program.Context,
- secretsNamespace,
+ CreateSecretStore(program.Context,
+ operationArguments,
Secret.UriToIdentityUrl),
bitbucketCredentialCallback,
bitbucketOauthCallback);
@@ -111,12 +111,12 @@ public static async Task CreateAuthentication(Program progra
if (authority != null)
{
// Set the authority type based on the returned value.
- if (authority is Azure.MsaAuthentication)
+ if (authority is AzureDev.MsaAuthentication)
{
operationArguments.Authority = AuthorityType.MicrosoftAccount;
goto case AuthorityType.MicrosoftAccount;
}
- else if (authority is Azure.AadAuthentication)
+ else if (authority is AzureDev.AadAuthentication)
{
operationArguments.Authority = AuthorityType.AzureDirectory;
goto case AuthorityType.AzureDirectory;
@@ -144,7 +144,7 @@ public static async Task CreateAuthentication(Program progra
Guid tenantId = Guid.Empty;
// Get the identity of the tenant.
- var result = await Azure.Authentication.DetectAuthority(program.Context, operationArguments.TargetUri);
+ var result = await AzureDev.Authentication.DetectAuthority(program.Context, operationArguments.TargetUri);
if (result.HasValue)
{
@@ -152,12 +152,12 @@ public static async Task CreateAuthentication(Program progra
}
// Create the authority object.
- authority = new Azure.AadAuthentication(program.Context,
+ authority = new AzureDev.AadAuthentication(program.Context,
tenantId,
operationArguments.DevOpsTokenScope,
- new SecretStore(program.Context,
- secretsNamespace,
- Azure.AadAuthentication.UriNameConversion));
+ CreateSecretStore(program.Context,
+ operationArguments,
+ AzureDev.AadAuthentication.UriNameConversion));
}
// Return the allocated authority or a generic AAD backed Azure DevOps authentication object.
@@ -179,8 +179,8 @@ public static async Task CreateAuthentication(Program progra
return authority ?? new Github.Authentication(program.Context,
operationArguments.TargetUri,
Program.GitHubCredentialScope,
- new SecretStore(program.Context,
- secretsNamespace,
+ CreateSecretStore(program.Context,
+ operationArguments,
Secret.UriToName),
githubCredentialCallback,
githubAuthcodeCallback,
@@ -193,8 +193,8 @@ public static async Task CreateAuthentication(Program progra
// Return a Bitbucket authentication object.
return authority ?? new Bitbucket.Authentication(program.Context,
- new SecretStore(program.Context,
- secretsNamespace,
+ CreateSecretStore(program.Context,
+ operationArguments,
Secret.UriToIdentityUrl),
bitbucketCredentialCallback,
bitbucketOauthCallback);
@@ -205,11 +205,11 @@ public static async Task CreateAuthentication(Program progra
program.Trace.WriteLine($"authority for '{operationArguments.TargetUri}' is Microsoft Live.");
// Return the allocated authority or a generic MSA backed Azure DevOps authentication object.
- return authority ?? new Azure.MsaAuthentication(program.Context,
+ return authority ?? new AzureDev.MsaAuthentication(program.Context,
operationArguments.DevOpsTokenScope,
- new SecretStore(program.Context,
- secretsNamespace,
- Azure.MsaAuthentication.UriNameConversion));
+ CreateSecretStore(program.Context,
+ operationArguments,
+ AzureDev.MsaAuthentication.UriNameConversion));
}
case AuthorityType.Ntlm:
@@ -225,8 +225,8 @@ public static async Task CreateAuthentication(Program progra
// Return a generic username + password authentication object.
return authority ?? new BasicAuthentication(program.Context,
- new SecretStore(program.Context,
- secretsNamespace,
+ CreateSecretStore(program.Context,
+ operationArguments,
Secret.UriToIdentityUrl),
basicNtlmSupport,
basicCredentialCallback,
@@ -235,6 +235,20 @@ public static async Task CreateAuthentication(Program progra
}
}
+ private static ICredentialStore CreateSecretStore (RuntimeContext context, OperationArguments operationArguments, Secret.UriNameConversionDelegate getTargetName)
+ {
+ string secretsNamespace = operationArguments.CustomNamespace ?? Program.SecretsNamespace;
+
+ if (string.IsNullOrEmpty(operationArguments.KeyVaultUrl))
+ {
+ return new SecretStore(context, secretsNamespace, getTargetName);
+ }
+ else
+ {
+ return new KeyVaultSecretStore(context, secretsNamespace, operationArguments.KeyVaultUrl, operationArguments.KeyVaultUseMsi, operationArguments.KeyVaulyAuthCertificateStoreType, operationArguments.KeyVaultAuthCertificateThumbprint, operationArguments.KeyVaultAuthClientId);
+ }
+ }
+
public static async Task DeleteCredentials(Program program, OperationArguments operationArguments)
{
if (program is null)
@@ -257,7 +271,7 @@ public static async Task DeleteCredentials(Program program, OperationArgum
case AuthorityType.MicrosoftAccount:
{
program.Trace.WriteLine($"deleting Azure DevOps credentials for '{operationArguments.TargetUri}'.");
- var adoAuth = authentication as Azure.Authentication;
+ var adoAuth = authentication as AzureDev.Authentication;
return await adoAuth.DeleteCredentials(operationArguments.TargetUri);
}
@@ -607,14 +621,14 @@ public static async Task LoadOperationArguments(Program program, OperationArgume
{
program.Trace.WriteLine($"{program.KeyTypeName(KeyType.DevOpsScope)} = '{value}'.");
- Azure.TokenScope devopsTokenScope = Azure.TokenScope.None;
+ AzureDev.TokenScope devopsTokenScope = AzureDev.TokenScope.None;
var scopes = value.Split(TokenScopeSeparatorCharacters.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < scopes.Length; i += 1)
{
scopes[i] = scopes[i].Trim();
- if (Azure.TokenScope.Find(scopes[i], out Azure.TokenScope scope))
+ if (AzureDev.TokenScope.Find(scopes[i], out AzureDev.TokenScope scope))
{
devopsTokenScope = devopsTokenScope | scope;
}
@@ -631,14 +645,14 @@ public static async Task LoadOperationArguments(Program program, OperationArgume
program.Trace.WriteLine($"GCM_VSTS_SCOPE = '{value}'.");
program.WriteLine($"WARNING: the 'GCM_VSTS_SCOPE' variable has been deprecated, use 'GCM_DEVOPS_SCOPE' instead.");
- Azure.TokenScope devopsTokenScope = Azure.TokenScope.None;
+ AzureDev.TokenScope devopsTokenScope = AzureDev.TokenScope.None;
var scopes = value.Split(TokenScopeSeparatorCharacters.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < scopes.Length; i += 1)
{
scopes[i] = scopes[i].Trim();
- if (Azure.TokenScope.Find(scopes[i], out Azure.TokenScope scope))
+ if (AzureDev.TokenScope.Find(scopes[i], out AzureDev.TokenScope scope))
{
devopsTokenScope = devopsTokenScope | scope;
}
@@ -652,7 +666,7 @@ public static async Task LoadOperationArguments(Program program, OperationArgume
}
// Check for configuration supplied user-info.
- if (program.TryReadString(operationArguments, KeyType.Username, out value))
+ if (program.TryReadString(operationArguments, KeyType.Username, out value))
{
program.Trace.WriteLine($"{program.KeyTypeName(KeyType.Username)} = '{value}'.");
@@ -695,6 +709,37 @@ public static async Task LoadOperationArguments(Program program, OperationArgume
Global.RequestTimeout = milliseconds;
}
}
+
+ // KeyVault Credential Store Parameters
+ if (program.TryReadString(operationArguments, KeyType.KeyVaultUrl, out value))
+ {
+ program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultUrl)} = '{value}'.");
+ operationArguments.KeyVaultUrl = value;
+ }
+
+ if (program.TryReadBoolean(operationArguments, KeyType.KeyVaultUseMsi, out yesno))
+ {
+ program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultUseMsi)} = '{yesno}'.");
+ operationArguments.KeyVaultUseMsi = yesno;
+ }
+
+ if (program.TryReadString(operationArguments, KeyType.KeyVaultAuthCertificateStoreType, out value))
+ {
+ program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultAuthCertificateStoreType)} = '{value}'.");
+ operationArguments.KeyVaulyAuthCertificateStoreType = value;
+ }
+
+ if (program.TryReadString(operationArguments, KeyType.KeyVaultAuthCertificateThumbprint, out value))
+ {
+ program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultAuthCertificateThumbprint)} = '{value}'.");
+ operationArguments.KeyVaultAuthCertificateThumbprint = value;
+ }
+
+ if (program.TryReadString(operationArguments, KeyType.KeyVaultAuthClientId, out value))
+ {
+ program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultAuthClientId)} = '{value}'.");
+ operationArguments.KeyVaultAuthClientId = value;
+ }
}
public static void LogEvent(Program program, string message, EventLogEntryType eventType)
@@ -791,8 +836,8 @@ public static async Task QueryCredentials(Program program, Operation
case AuthorityType.AzureDirectory:
{
- var aadAuth = authentication as Azure.AadAuthentication;
- var patOptions = new Azure.PersonalAccessTokenOptions()
+ var aadAuth = authentication as AzureDev.AadAuthentication;
+ var patOptions = new AzureDev.PersonalAccessTokenOptions()
{
RequireCompactToken = true,
TokenDuration = operationArguments.TokenDuration,
@@ -827,8 +872,8 @@ public static async Task QueryCredentials(Program program, Operation
case AuthorityType.MicrosoftAccount:
{
- var msaAuth = authentication as Azure.MsaAuthentication;
- var patOptions = new Azure.PersonalAccessTokenOptions()
+ var msaAuth = authentication as AzureDev.MsaAuthentication;
+ var patOptions = new AzureDev.PersonalAccessTokenOptions()
{
RequireCompactToken = true,
TokenDuration = operationArguments.TokenDuration,
diff --git a/Shared/Cli/OperationArguments.cs b/Shared/Cli/OperationArguments.cs
index 38b719523..4b065e555 100644
--- a/Shared/Cli/OperationArguments.cs
+++ b/Shared/Cli/OperationArguments.cs
@@ -30,7 +30,7 @@
using System.Threading.Tasks;
using Microsoft.Alm.Authentication;
using static System.Globalization.CultureInfo;
-using Azure = AzureDevOps.Authentication;
+using AzureDev = AzureDevOps.Authentication;
using Git = Microsoft.Alm.Authentication.Git;
namespace Microsoft.Alm.Cli
@@ -69,7 +69,7 @@ public OperationArguments()
private Git.Configuration _configuration;
private Credential _credentials;
private string _customNamespace;
- private Azure.TokenScope _devopsTokenScope;
+ private AzureDev.TokenScope _devopsTokenScope;
private Dictionary _environmentVariables;
private string _gitRemoteHttpCommandLine;
private Interactivity _interactivity;
@@ -128,7 +128,7 @@ public virtual string CustomNamespace
///
/// Default value is ``.
///
- public virtual Azure.TokenScope DevOpsTokenScope
+ public virtual AzureDev.TokenScope DevOpsTokenScope
{
get { return _devopsTokenScope; }
set { _devopsTokenScope = value; }
@@ -445,6 +445,20 @@ public virtual bool WriteLog
set { _writeLog = value; }
}
+ ///
+ /// KeyVaultUrl for storing credentials and PAT tokens in KeyVault.
+ /// If specified, KeyVault is used as a storage mechanism for secrets.
+ ///
+ public virtual string KeyVaultUrl { get; set; }
+
+ public virtual bool? KeyVaultUseMsi { get; set; }
+
+ public virtual string KeyVaulyAuthCertificateStoreType { get; set; }
+
+ public virtual string KeyVaultAuthCertificateThumbprint { get; set; }
+
+ public virtual string KeyVaultAuthClientId { get; set; }
+
internal string DebuggerDisplay
{
get { return $"{nameof(OperationArguments)}: TargetUri: {TargetUri}, Authority: {Authority}, Credentials: {Credentials}"; }
@@ -655,20 +669,29 @@ public override string ToString()
.Append(_queryPath ?? string.Empty)
.Append('\n');
- // Only write out username if we know it.
- if (_credentials?.Username != null)
+ if (_credentials == null)
{
builder.Append("username=")
- .Append(_credentials.Username)
- .Append('\n');
+ .Append(_username ?? string.Empty)
+ .Append('\n');
}
-
- // Only write out password if we know it.
- if (_credentials?.Password != null)
+ else
{
- builder.Append("password=")
- .Append(_credentials.Password)
- .Append('\n');
+ // Only write out username if we know it.
+ if (_credentials.Username != null)
+ {
+ builder.Append("username=")
+ .Append(_credentials.Username)
+ .Append('\n');
+ }
+
+ // Only write out password if we know it.
+ if (_credentials.Password != null)
+ {
+ builder.Append("password=")
+ .Append(_credentials.Password)
+ .Append('\n');
+ }
}
builder.Append('\n');
diff --git a/Shared/Cli/Program.cs b/Shared/Cli/Program.cs
index 215ede353..0b8ab8ce4 100644
--- a/Shared/Cli/Program.cs
+++ b/Shared/Cli/Program.cs
@@ -32,7 +32,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Alm.Authentication;
-using Azure = AzureDevOps.Authentication;
+using AzureDev = AzureDevOps.Authentication;
using Bitbucket = Atlassian.Bitbucket.Authentication;
using Git = Microsoft.Alm.Authentication.Git;
using Github = GitHub.Authentication;
@@ -61,6 +61,11 @@ enum KeyType
ParentHwnd,
VstsScope,
Writelog,
+ KeyVaultUrl,
+ KeyVaultUseMsi,
+ KeyVaultAuthCertificateThumbprint,
+ KeyVaultAuthCertificateStoreType,
+ KeyVaultAuthClientId
}
partial class Program
@@ -79,7 +84,7 @@ partial class Program
internal const string ConfigPrefix = "credential";
internal const string SecretsNamespace = "git";
- internal static readonly Azure.TokenScope DevOpsCredentialScope = Azure.TokenScope.CodeWrite | Azure.TokenScope.PackagingRead;
+ internal static readonly AzureDev.TokenScope DevOpsCredentialScope = AzureDev.TokenScope.CodeWrite | AzureDev.TokenScope.PackagingRead;
internal static readonly Github.TokenScope GitHubCredentialScope = Github.TokenScope.Gist | Github.TokenScope.Repo;
internal BasicCredentialPromptDelegate _basicCredentialPrompt = ConsoleFunctions.CredentialPrompt;
@@ -135,6 +140,11 @@ partial class Program
{ KeyType.Validate, "validate" },
{ KeyType.VstsScope,"vstsScope" },
{ KeyType.Writelog, "writeLog" },
+ { KeyType.KeyVaultUrl, "keyvaultUrl" },
+ { KeyType.KeyVaultUseMsi, "keyVaultUseMsi" },
+ { KeyType.KeyVaultAuthCertificateStoreType, "keyvaultAuthCertificateStoreType" },
+ { KeyType.KeyVaultAuthCertificateThumbprint, "keyvaultAuthCertificateThumbprint" },
+ { KeyType.KeyVaultAuthClientId, "keyvaultAuthClientId" },
};
internal readonly Dictionary _environmentKeys = new Dictionary()
{
diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml
new file mode 100644
index 000000000..106bb0483
--- /dev/null
+++ b/azure-pipelines-1.yml
@@ -0,0 +1,38 @@
+# Starter pipeline
+# Start with a minimal pipeline that you can customize to build and deploy your code.
+# Add steps that build, run tests, deploy, and more:
+# https://aka.ms/yaml
+
+
+pool:
+ vmImage: 'windows-latest'
+
+steps:
+- task: PowerShell@2
+ inputs:
+ targetType: 'inline'
+ script: |
+ Write-Host "Build.BuildUri:" $(Build.BuildUri)
+ Write-Host "System.CollectionUri:" $(System.CollectionUri)
+ Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri)
+ Write-Host "Build.Repository.Uri:" $(Build.Repository.Uri)
+ Write-Host "Build Source Directory:" $(Build.SourcesDirectory)
+- task: UseDotNet@2
+ inputs:
+ packageType: 'runtime'
+ version: '3.1.x'
+
+- task: NuGetCommand@2
+ displayName: NuGet restore
+ inputs:
+ restoreSolution: 'GitCredentialManager.sln'
+ verbosityRestore: 'quiet'
+
+- task: MSBuild@1
+ displayName: 'Core Build'
+ inputs:
+ solution: "GitCredentialManager.sln"
+ msbuildArguments: /nologo "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog"
+ platform: 'Any CPU'
+ configuration: 'Debug'
+ maximumCpuCount: false
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 000000000..f7cc46389
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,40 @@
+# Starter pipeline
+# Start with a minimal pipeline that you can customize to build and deploy your code.
+# Add steps that build, run tests, deploy, and more:
+# https://aka.ms/yaml
+
+
+pool:
+ vmImage: 'windows-latest'
+
+steps:
+
+- task: PowerShell@2
+ inputs:
+ targetType: 'inline'
+ script: |
+ Write-Host "Build.BuildUri:" $(Build.BuildUri)
+ Write-Host "System.CollectionUri:" $(System.CollectionUri)
+ Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri)
+ Write-Host "Build.Repository.Uri:" $(Build.Repository.Uri)
+ Write-Host "Build Source Directory:" $(Build.SourcesDirectory)
+ java
+- task: UseDotNet@2
+ inputs:
+ packageType: 'runtime'
+ version: '3.1.x'
+
+- task: NuGetCommand@2
+ displayName: NuGet restore
+ inputs:
+ restoreSolution: 'GitCredentialManager.sln'
+ verbosityRestore: 'quiet'
+
+- task: MSBuild@1
+ displayName: 'Core Build'
+ inputs:
+ solution: "GitCredentialManager.sln"
+ msbuildArguments: /nologo "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog"
+ platform: 'Any CPU'
+ configuration: 'Debug'
+ maximumCpuCount: false