1
0
Форкнуть 0
Git-Credential-Manager-for-.../Shared/Cli/Program.cs

694 строки
26 KiB
C#

/**** 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 System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Alm.Authentication;
using Azure = AzureDevOps.Authentication;
using Bitbucket = Atlassian.Bitbucket.Authentication;
using Git = Microsoft.Alm.Authentication.Git;
using Github = GitHub.Authentication;
namespace Microsoft.Alm.Cli
{
enum KeyType
{
Authority,
ConfigNoLocal,
ConfigNoSystem,
DevOpsScope,
HttpPath,
HttpProxy,
HttpsProxy,
HttpTimeout,
HttpUserAgent,
Interactive,
ModalPrompt,
Namespace,
PreserveCredentials,
TokenDuration,
UrlOverride,
Username,
Validate,
ParentHwnd,
VstsScope,
Writelog,
}
partial class Program
{
public const string SourceUrl = "https://github.com/Microsoft/Git-Credential-Manager-for-Windows";
public const string EventSource = "Git Credential Manager";
internal static readonly StringComparer ConfigKeyComparer = StringComparer.OrdinalIgnoreCase;
internal static readonly StringComparer ConfigValueComparer = StringComparer.OrdinalIgnoreCase;
internal const string EnvironConfigDebugKey = "GCM_DEBUG";
internal const string EnvironConfigTraceKey = "GCM_TRACE";
internal static readonly StringComparer EnvironKeyComparer = StringComparer.OrdinalIgnoreCase;
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 Github.TokenScope GitHubCredentialScope = Github.TokenScope.Gist | Github.TokenScope.Repo;
internal BasicCredentialPromptDelegate _basicCredentialPrompt = ConsoleFunctions.CredentialPrompt;
internal BitbucketCredentialPromptDelegate _bitbucketCredentialPrompt = BitbucketFunctions.CredentialPrompt;
internal BitbucketOAuthPromptDelegate _bitbucketOauthPrompt = BitbucketFunctions.OAuthPrompt;
internal CreateAuthenticationDelegate _createAuthentication = CommonFunctions.CreateAuthentication;
internal DeleteCredentialsDelegate _deleteCredentials = CommonFunctions.DeleteCredentials;
internal DieExceptionDelegate _dieException = CommonFunctions.DieException;
internal DieMessageDelegate _dieMessage = CommonFunctions.DieMessage;
internal EnableTraceLoggingDelegate _enableTraceLogging = CommonFunctions.EnableTraceLogging;
internal EnableTraceLoggingFileDelegate _enableTraceLoggingFile = CommonFunctions.EnableTraceLoggingFile;
internal ExitDelegate _exit = ConsoleFunctions.Exit;
internal GitHubAuthCodePromptDelegate _gitHubAuthCodePrompt = GitHubFunctions.AuthCodePrompt;
internal GitHubCredentialPromptDelegate _gitHubCredentialPrompt = GitHubFunctions.CredentialPrompt;
internal LoadOperationArgumentsDelegate _loadOperationArguments = CommonFunctions.LoadOperationArguments;
internal LogEventDelegate _logEvent = CommonFunctions.LogEvent;
internal ModalPromptDisplayDialogDelegate _modalPromptDisplayDialog = DialogFunctions.DisplayModal;
internal ModalPromptForCredentialsDelegate _modalPromptForCredentials = DialogFunctions.CredentialPrompt;
internal ModalPromptForPasswordDelegate _modalPromptForPassword = DialogFunctions.PasswordPrompt;
internal OpenStandardHandleDelegate _openStandardErrorStream = ConsoleFunctions.OpenStandardErrorStream;
internal GetStandardWriterDelegate _openStandardErrorWriter = ConsoleFunctions.OpenStandardErrorWriter;
internal OpenStandardHandleDelegate _openStandardInputStream = ConsoleFunctions.OpenStandardInputStream;
internal GetStandardReaderDelegate _openStandardInputReader = ConsoleFunctions.OpenStandardInputReader;
internal OpenStandardHandleDelegate _openStandardOutputStream = ConsoleFunctions.OpenStandardOutputStream;
internal GetStandardWriterDelegate _openStandardOutputWriter = ConsoleFunctions.OpenStandardOutputWriter;
internal PrintArgsDelegate _printArgs = CommonFunctions.PrintArgs;
internal QueryCredentialsDelegate _queryCredentials = CommonFunctions.QueryCredentials;
internal ReadGitRemoteDetailsDelegate _readGitRemoteDetails = CommonFunctions.ReadGitRemoteDetails;
internal ReadKeyDelegate _readKey = ConsoleFunctions.ReadKey;
internal SetStandardWriterDelegate _setStandardErrorWriter = ConsoleFunctions.SetStandardErrorWriter;
internal SetStandardReaderDelegate _setStandardInputReader = ConsoleFunctions.SetStandardInputReader;
internal SetStandardWriterDelegate _setStandardOutputWriter = ConsoleFunctions.SetStandardOutputWriter;
internal StandardHandleIsTtyDelegate _standardHandleIsTty = ConsoleFunctions.StandardHandleIsTty;
internal TryReadBooleanDelegate _tryReadBoolean = CommonFunctions.TryReadBoolean;
internal TryReadStringDelegate _tryReadString = CommonFunctions.TryReadString;
internal WriteDelegate _write = ConsoleFunctions.Write;
internal WriteLineDelegate _writeLine = ConsoleFunctions.WriteLine;
internal readonly Dictionary<KeyType, string> _configurationKeys = new Dictionary<KeyType, string>()
{
{ KeyType.Authority, "authority" },
{ KeyType.DevOpsScope, "devopsScope" },
{ KeyType.HttpProxy, "httpProxy" },
{ KeyType.HttpsProxy, "httpsProxy" },
{ KeyType.Interactive, "interactive" },
{ KeyType.ModalPrompt, "modalPrompt" },
{ KeyType.Namespace, "namespace" },
{ KeyType.PreserveCredentials, "preserve" },
{ KeyType.TokenDuration, "tokenDuration" },
{ KeyType.HttpPath, "useHttpPath" },
{ KeyType.HttpTimeout, "httpTimeout" },
{ KeyType.Username, "username" },
{ KeyType.Validate, "validate" },
{ KeyType.VstsScope,"vstsScope" },
{ KeyType.Writelog, "writeLog" },
};
internal readonly Dictionary<KeyType, string> _environmentKeys = new Dictionary<KeyType, string>()
{
{ KeyType.Authority, "GCM_AUTHORITY" },
{ KeyType.ConfigNoLocal, "GCM_CONFIG_NOLOCAL" },
{ KeyType.ConfigNoSystem, "GCM_CONFIG_NOSYSTEM" },
{ KeyType.DevOpsScope, "GCM_DEVOPS_SCOPE" },
{ KeyType.HttpProxy, "HTTP_PROXY" },
{ KeyType.HttpsProxy, "HTTPS_PROXY" },
{ KeyType.HttpTimeout, "GCM_HTTP_TIMEOUT" },
{ KeyType.HttpUserAgent, "GCM_HTTP_USER_AGENT" },
{ KeyType.Interactive, "GCM_INTERACTIVE" },
{ KeyType.ModalPrompt, "GCM_MODAL_PROMPT" },
{ KeyType.Namespace, "GCM_NAMESPACE" },
{ KeyType.ParentHwnd, "GCM_MODAL_PARENTHWND" },
{ KeyType.PreserveCredentials, "GCM_PRESERVE" },
{ KeyType.TokenDuration, "GCM_TOKEN_DURATION" },
{ KeyType.UrlOverride, "GCM_URL_OVERRIDE" },
{ KeyType.Validate, "GCM_VALIDATE" },
{ KeyType.VstsScope, "GCM_VSTS_SCOPE" },
{ KeyType.Writelog, "GCM_WRITELOG" },
};
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static Program()
{
ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12;
}
private string _executablePath;
private readonly RuntimeContext _context;
private string _location;
private string _name;
private char[] _newlineChars;
private IntPtr _parentHwnd;
private Stream _stdErrStream;
private TextWriter _stdErrWriter;
private Stream _stdInStream;
private TextReader _stdInReader;
private Stream _stdOutStream;
private TextWriter _stdOutWriter;
private readonly object _syncpoint = new object();
private string _title;
private Version _version;
public IReadOnlyDictionary<KeyType, string> ConfigurationKeys
=> _configurationKeys;
public IReadOnlyDictionary<KeyType, string> EnvironmentKeys
=> _environmentKeys;
/// <summary>
/// Gets the path to the executable.
/// </summary>
public string ExecutablePath
{
get
{
lock (_syncpoint)
{
if (_executablePath is null)
{
LoadAssemblyInformation();
}
return _executablePath;
}
}
}
/// <summary>
/// Gets the directory where the executable is contained.
/// </summary>
public string Location
{
get
{
lock (_syncpoint)
{
if (_location is null)
{
LoadAssemblyInformation();
}
return _location;
}
}
}
/// <summary>
/// Gets the name of the application.
/// </summary>
public string Name
{
get
{
lock (_syncpoint)
{
if (_name is null)
{
LoadAssemblyInformation();
}
return _name;
}
}
}
/// <summary>
/// <para>Gets <see langword="true"/> if stderr is a TTY device; otherwise <see langword="false"/>.</para>
/// <para>
/// If TTY, then it is very likely stderr is attached to a console and interactions with the
/// user are possible.
/// </para>
/// </summary>
public bool StandardErrorIsTty
{
get { return StandardHandleIsTty(NativeMethods.StandardHandleType.Error); }
}
/// <summary>
/// <para>Gets <see langword="true"/> if stdin is a TTY device; otherwise <see langword="false"/>.</para>
/// <para>
/// If TTY, then it is very likely stdin is attached to a console and interactions with the
/// user are possible.
/// </para>
/// </summary>
public bool StandardInputIsTty
{
get { return StandardHandleIsTty(NativeMethods.StandardHandleType.Input); }
}
/// <summary>
/// <para>Gets <see langword="true"/> if stdout is a TTY device; otherwise <see langword="false"/>.</para>
/// <para>
/// If TTY, then it is very likely stdout is attached to a console and interaction with the
/// user are possible.
/// </para>
/// </summary>
public bool StandardOutputIsTty
{
get { return StandardHandleIsTty(NativeMethods.StandardHandleType.Output); }
}
/// <summary>
/// Gets the titles of the application.
/// </summary>
public string Title
{
get { return _title; }
private set
{
if (value is null)
throw new ArgumentNullException(nameof(Title));
_title = value;
}
}
/// <summary>
/// Gets the version of the application.
/// </summary>
public Version Version
{
get
{
lock (_syncpoint)
{
if (_version is null)
{
LoadAssemblyInformation();
}
return _version;
}
}
}
internal RuntimeContext Context
=> _context;
internal TextWriter Error
{
get
{
lock (_syncpoint)
{
if (_stdErrWriter is null)
{
_stdErrWriter = _openStandardErrorWriter(this);
}
return _stdErrWriter;
}
}
}
internal Stream ErrorStream
{
get
{
lock (_syncpoint)
{
if (_stdErrStream is null)
{
_stdErrStream = _openStandardErrorStream(this);
}
return _stdErrStream;
}
}
}
internal TextReader In
{
get
{
lock (_syncpoint)
{
if (_stdInReader is null)
{
_stdInReader = _openStandardInputReader(this);
}
return _stdInReader;
}
}
}
internal Stream InStream
{
get
{
lock (_syncpoint)
{
if (_stdInStream is null)
{
_stdInStream = _openStandardInputStream(this);
}
return _stdInStream;
}
}
}
internal INetwork Network
=> _context.Network;
internal char[] NewLineChars
{
get
{
lock (_syncpoint)
{
if (_newlineChars is null)
{
_newlineChars = Settings.NewLine.ToCharArray();
}
return _newlineChars;
}
}
}
internal TextWriter Out
{
get
{
lock (_syncpoint)
{
if (_stdOutWriter is null)
{
_stdOutWriter = _openStandardOutputWriter(this);
}
return _stdOutWriter;
}
}
}
internal Stream OutStream
{
get
{
lock (_syncpoint)
{
if (_stdOutStream is null)
{
_stdOutStream = _openStandardOutputStream(this);
}
return _stdOutStream;
}
}
}
internal IntPtr ParentHwnd
{
get { lock (_syncpoint) return _parentHwnd; }
set { lock (_syncpoint) _parentHwnd = value; }
}
internal ISettings Settings
=> _context.Settings;
internal IStorage Storage
=> _context.Storage;
internal Git.ITrace Trace
=> _context.Trace;
internal Git.IUtilities Utilities
=> _context.Utilities;
internal Git.IWhere Where
=> _context.Where;
internal static void DebuggerLaunch(Program program)
{
if (program is null)
throw new ArgumentNullException(nameof(program));
if (Debugger.IsAttached)
return;
string debug = program.Settings.GetEnvironmentVariable(EnvironConfigDebugKey);
if (debug != null
&& (StringComparer.OrdinalIgnoreCase.Equals(debug, "true")
|| StringComparer.OrdinalIgnoreCase.Equals(debug, "1")
|| StringComparer.OrdinalIgnoreCase.Equals(debug, "debug")))
{
program.Trace.WriteLine($"'{EnvironConfigDebugKey}': '{debug}', launching debugger...");
Debugger.Launch();
}
}
internal void DetectTraceEnvironmentKey(string environmentKey)
{
if (environmentKey is null)
throw new ArgumentNullException(nameof(environmentKey));
try
{
string traceValue = Settings.GetEnvironmentVariable(environmentKey);
if (traceValue is null)
return;
// If the value is true or a number greater than zero, then trace to standard error.
if (Git.Configuration.PaserBoolean(traceValue))
{
Trace.AddListener(Error);
}
// If the value is a rooted path, then trace to that file and not to the console.
else if (Path.IsPathRooted(traceValue))
{
// Open or create the log file.
var stream = Storage.FileOpen(traceValue, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
// Create the writer and add it to the list.
var writer = new StreamWriter(stream, System.Text.Encoding.UTF8, 4096, true);
Trace.AddListener(writer);
}
}
catch { /* squelch */ }
}
internal void Die(Exception exception,
[CallerFilePath] string path = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string name = "")
=> _dieException(this, exception, path, line, name);
internal void Die(string message,
[CallerFilePath] string path = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string name = "")
=> _dieMessage(this, message, path, line, name);
internal void Exit(int exitcode = 0,
string message = null,
[CallerFilePath] string path = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string name = "")
=> _exit(this, exitcode, message, path, line, name);
internal Task LoadOperationArguments(OperationArguments operationArguments)
=> _loadOperationArguments(this, operationArguments);
internal void LogEvent(string message, EventLogEntryType eventType)
=> _logEvent(this, message, eventType);
internal Task<Credential> QueryCredentials(OperationArguments operationArguments)
=> _queryCredentials(this, operationArguments);
internal ConsoleKeyInfo ReadKey(bool intercept = true)
=> _readKey(this, intercept);
internal void Write(string message)
=> _write(this, message);
internal void WriteLine(string message = null)
=> _writeLine(this, message);
internal Credential BasicCredentialPrompt(TargetUri targetUri)
{
string message = "Please enter your credentials for ";
return BasicCredentialPrompt(targetUri, message);
}
internal Credential BasicCredentialPrompt(TargetUri targetUri, string titleMessage)
=> _basicCredentialPrompt(this, targetUri, titleMessage);
internal Task<BaseAuthentication> CreateAuthentication(OperationArguments operationArguments)
=> _createAuthentication(this, operationArguments);
internal Task<bool> DeleteCredentials(OperationArguments operationArguments)
=> _deleteCredentials(this, operationArguments);
internal void PrintArgs(string[] args)
=> _printArgs(this, args);
[Conditional("DEBUG")]
internal void EnableDebugTrace()
{
// Use the stderr stream for the trace as stdout is used in the cross-process
// communications protocol
_context.Trace.AddListener(Error);
}
internal void EnableTraceLogging(OperationArguments operationArguments)
=> _enableTraceLogging(this, operationArguments);
internal void EnableTraceLogging(OperationArguments operationArguments, string logFilePath)
=> _enableTraceLoggingFile(this, operationArguments, logFilePath);
internal bool BitbucketCredentialPrompt(string titleMessage, TargetUri targetUri, out string username, out string password)
=> _bitbucketCredentialPrompt(this, titleMessage, targetUri, out username, out password);
internal bool BitbucketOAuthPrompt(string title, TargetUri targetUri, Bitbucket.AuthenticationResultType resultType, string username)
=> _bitbucketOauthPrompt(this, title, targetUri, resultType, username);
internal string KeyTypeName(KeyType type)
{
string value = "UNKNOWN";
if (!_configurationKeys.TryGetValue(type, out value)
&& !_environmentKeys.TryGetValue(type, out value))
{
_context.Trace.WriteLine($"BUG: unknown {nameof(KeyType)}: '{type}' encountered.");
}
return value;
}
internal bool GitHubAuthCodePrompt(TargetUri targetUri, Github.GitHubAuthenticationResultType resultType, string username, out string authenticationCode)
=> _gitHubAuthCodePrompt(this, targetUri, resultType, username, out authenticationCode);
internal bool GitHubCredentialPrompt(TargetUri targetUri, out string username, out string password)
=> _gitHubCredentialPrompt(this, targetUri, out username, out password);
private void LoadAssemblyInformation()
{
Debug.Assert(Monitor.IsEntered(_syncpoint), "Expected lock not held.");
var assembly = System.Reflection.Assembly.GetEntryAssembly();
var asseName = assembly.GetName();
_executablePath = assembly.Location;
_location = Path.GetDirectoryName(_executablePath);
_name = asseName.Name;
_version = asseName.Version;
}
internal bool ModalPromptDisplayDialog(
ref NativeMethods.CredentialUiInfo credUiInfo,
ref NativeMethods.CredentialPackFlags authPackage,
IntPtr packedAuthBufferPtr,
uint packedAuthBufferSize,
IntPtr inBufferPtr,
int inBufferSize,
bool saveCredentials,
NativeMethods.CredentialUiWindowsFlags flags,
out string username,
out string password)
=> _modalPromptDisplayDialog(this,
ref credUiInfo,
ref authPackage,
packedAuthBufferPtr,
packedAuthBufferSize,
inBufferPtr,
inBufferSize,
saveCredentials,
flags,
out username,
out password);
internal Credential ModalPromptForCredentials(TargetUri targetUri, string message)
=> _modalPromptForCredentials(this, targetUri, message);
internal Credential ModalPromptForCredentials(TargetUri targetUri)
{
string message = string.Format("Enter your credentials for {0}.", targetUri.ToString(username: false, port: true, path: true));
if (!string.IsNullOrEmpty(targetUri.QueryUri.UserInfo))
{
string username = targetUri.QueryUri.UserInfo;
if (!targetUri.QueryUri.UserEscaped)
{
username = Uri.UnescapeDataString(username);
}
return ModalPromptForPassword(targetUri, message, username);
}
return ModalPromptForCredentials(targetUri, message);
}
private Credential ModalPromptForPassword(TargetUri targetUri, string message, string username)
=> _modalPromptForPassword(this, targetUri, message, username);
internal void PrintVersion()
{
#if DEBUG
WriteLine($"{Title} version {Version.ToString(4)}");
#else
WriteLine($"{Title} version {Version.ToString(3)}");
#endif
}
internal void ReadGitRemoteDetails(OperationArguments operationArguments)
=> _readGitRemoteDetails(this, operationArguments);
internal void SetError(TextWriter writer)
{
_setStandardErrorWriter(this, writer);
}
internal void SetIn(TextReader reader)
{
_setStandardInputReader(this, reader);
}
internal void SetOut(TextWriter writer)
{
_setStandardOutputWriter(this, writer);
}
internal bool StandardHandleIsTty(NativeMethods.StandardHandleType handleType)
=> _standardHandleIsTty(this, handleType);
internal bool TryReadBoolean(OperationArguments operationArguments, KeyType key, out bool? value)
=> _tryReadBoolean(this, operationArguments, key, out value);
internal bool TryReadString(OperationArguments operationArguments, KeyType key, out string value)
=> _tryReadString(this, operationArguments, key, out value);
}
}