Repository: git-ecosystem/git-credential-manager Branch: main Commit: ee7363015260 Files: 496 Total size: 2.3 MB Directory structure: gitextract_0a_1gga6/ ├── .azure-pipelines/ │ └── release.yml ├── .code-coverage/ │ └── coverlet.settings.xml ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── auth-issue.yml │ │ └── feature-req.yml │ ├── dependabot.yml │ └── workflows/ │ ├── codeql-analysis.yml │ ├── continuous-integration.yml │ ├── lint-docs.yml │ ├── maintainer-absence.yml │ └── validate-install-from-source.yml ├── .gitignore ├── .lycheeignore ├── .markdownlint.jsonc ├── .vscode/ │ ├── launch.json │ └── tasks.json ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Build.targets ├── Git-Credential-Manager.sln ├── Git-Credential-Manager.sln.DotSettings ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── VERSION ├── assets/ │ └── gcm.xaml ├── build/ │ ├── GCM.MSBuild.csproj │ ├── GCM.tasks │ ├── GenerateWindowsAppManifest.cs │ └── GetVersion.cs ├── docs/ │ ├── README.md │ ├── architecture.md │ ├── autodetect.md │ ├── azrepos-misp.md │ ├── azrepos-users-and-tokens.md │ ├── bitbucket-authentication.md │ ├── bitbucket-development.md │ ├── configuration.md │ ├── credstores.md │ ├── development.md │ ├── enterprise-config.md │ ├── environment.md │ ├── faq.md │ ├── generic-oauth.md │ ├── github-apideprecation.md │ ├── gitlab.md │ ├── hostprovider.md │ ├── install.md │ ├── linux-fromsrc-uninstall.md │ ├── linux-validate-gpg.md │ ├── migration.md │ ├── multiple-users.md │ ├── netconfig.md │ ├── ntlm-kerberos.md │ ├── rename.md │ ├── usage.md │ ├── windows-broker.md │ └── wsl.md ├── global.json ├── nuget.config └── src/ ├── linux/ │ ├── Directory.Build.props │ └── Packaging.Linux/ │ ├── Packaging.Linux.csproj │ ├── build.sh │ ├── install-from-source.sh │ ├── layout.sh │ └── pack.sh ├── osx/ │ ├── .gitignore │ ├── Directory.Build.props │ └── Installer.Mac/ │ ├── Installer.Mac.csproj │ ├── build.sh │ ├── codesign.sh │ ├── dist.sh │ ├── distribution.arm64.xml │ ├── distribution.x64.xml │ ├── entitlements.xml │ ├── layout.sh │ ├── notarize.sh │ ├── pack.sh │ ├── resources/ │ │ └── en.lproj/ │ │ ├── conclusion.html │ │ └── welcome.html │ ├── scripts/ │ │ └── postinstall │ └── uninstall.sh ├── shared/ │ ├── Atlassian.Bitbucket/ │ │ ├── Atlassian.Bitbucket.csproj │ │ ├── AuthenticationMethod.cs │ │ ├── BitbucketAuthentication.cs │ │ ├── BitbucketConstants.cs │ │ ├── BitbucketHelper.cs │ │ ├── BitbucketHostProvider.cs │ │ ├── BitbucketOAuth2Client.cs │ │ ├── BitbucketResources.Designer.cs │ │ ├── BitbucketResources.resx │ │ ├── BitbucketRestApiRegistry.cs │ │ ├── BitbucketTokenEndpointResponseJson.cs │ │ ├── Cloud/ │ │ │ ├── BitbucketOAuth2Client.cs │ │ │ ├── BitbucketRestApi.cs │ │ │ ├── CloudConstants.cs │ │ │ └── UserInfo.cs │ │ ├── DataCenter/ │ │ │ ├── BitbucketOAuth2Client.cs │ │ │ ├── BitbucketRestApi.cs │ │ │ ├── DataCenterConstants.cs │ │ │ ├── LoginOption.cs │ │ │ ├── LoginOptions.cs │ │ │ └── UserInfo.cs │ │ ├── IBitbucketRestApi.cs │ │ ├── IRegistry.cs │ │ ├── IUserInfo.cs │ │ ├── InternalsVisibleTo.cs │ │ ├── OAuth2ClientRegistry.cs │ │ ├── RestApiResult.cs │ │ └── UI/ │ │ ├── Commands/ │ │ │ └── CredentialsCommand.cs │ │ ├── ViewModels/ │ │ │ └── CredentialsViewModel.cs │ │ └── Views/ │ │ ├── CredentialsView.axaml │ │ └── CredentialsView.axaml.cs │ ├── Atlassian.Bitbucket.Tests/ │ │ ├── Atlassian.Bitbucket.Tests.csproj │ │ ├── BitbucketAuthenticationTest.cs │ │ ├── BitbucketHelperTest.cs │ │ ├── BitbucketHostProviderTest.cs │ │ ├── BitbucketRestApiRegistryTest.cs │ │ ├── BitbucketTokenEndpointResponseJsonTest.cs │ │ ├── Cloud/ │ │ │ ├── BitbucketOAuth2ClientTest.cs │ │ │ ├── BitbucketRestApiTest.cs │ │ │ └── UserInfoTest.cs │ │ ├── DataCenter/ │ │ │ ├── BitbucketOAuth2ClientTest.cs │ │ │ ├── BitbucketRestApiTest.cs │ │ │ ├── LoginOptionsTest.cs │ │ │ └── UserInfoTest.cs │ │ └── OAuth2ClientRegistryTest.cs │ ├── Core/ │ │ ├── Application.cs │ │ ├── ApplicationBase.cs │ │ ├── AssemblyUtils.cs │ │ ├── Authentication/ │ │ │ ├── AuthenticationBase.cs │ │ │ ├── BasicAuthentication.cs │ │ │ ├── MicrosoftAuthentication.cs │ │ │ ├── OAuth/ │ │ │ │ ├── HttpListenerExtensions.cs │ │ │ │ ├── IOAuth2WebBrowser.cs │ │ │ │ ├── Json/ │ │ │ │ │ ├── DeviceAuthorizationEndpointResponseJson.cs │ │ │ │ │ ├── ErrorResponseJson.cs │ │ │ │ │ └── TokenEndpointResponseJson.cs │ │ │ │ ├── OAuth2AuthorizationCodeResult.cs │ │ │ │ ├── OAuth2Client.cs │ │ │ │ ├── OAuth2Constants.cs │ │ │ │ ├── OAuth2CryptographicGenerator.cs │ │ │ │ ├── OAuth2DeviceCodeResult.cs │ │ │ │ ├── OAuth2Exception.cs │ │ │ │ ├── OAuth2ServerEndpoints.cs │ │ │ │ ├── OAuth2SystemWebBrowser.cs │ │ │ │ └── OAuth2TokenResult.cs │ │ │ ├── OAuthAuthentication.cs │ │ │ └── WindowsIntegratedAuthentication.cs │ │ ├── Base64UrlConvert.cs │ │ ├── ChildProcess.cs │ │ ├── CommandContext.cs │ │ ├── CommandExtensions.cs │ │ ├── Commands/ │ │ │ ├── ConfigurationCommands.cs │ │ │ ├── DiagnoseCommand.cs │ │ │ ├── EraseCommand.cs │ │ │ ├── GetCommand.cs │ │ │ ├── GitCommandBase.cs │ │ │ ├── ProviderCommand.cs │ │ │ └── StoreCommand.cs │ │ ├── ConfigurationService.cs │ │ ├── Constants.cs │ │ ├── ConvertUtils.cs │ │ ├── Core.csproj │ │ ├── Credential.cs │ │ ├── CredentialCacheStore.cs │ │ ├── CredentialStore.cs │ │ ├── CurlCookie.cs │ │ ├── Diagnostics/ │ │ │ ├── CredentialStoreDiagnostic.cs │ │ │ ├── Diagnostic.cs │ │ │ ├── EnvironmentDiagnostic.cs │ │ │ ├── FileSystemDiagnostic.cs │ │ │ ├── GitDiagnostic.cs │ │ │ ├── IDiagnosticProvider.cs │ │ │ ├── MicrosoftAuthenticationDiagnostic.cs │ │ │ └── NetworkingDiagnostic.cs │ │ ├── DictionaryExtensions.cs │ │ ├── DisposableObject.cs │ │ ├── EncodingEx.cs │ │ ├── EnsureArgument.cs │ │ ├── EnumerableExtensions.cs │ │ ├── EnvironmentBase.cs │ │ ├── FileCredential.cs │ │ ├── FileSystem.cs │ │ ├── GenericHostProvider.cs │ │ ├── GenericOAuthConfig.cs │ │ ├── Git.cs │ │ ├── GitConfiguration.cs │ │ ├── GitConfigurationEntry.cs │ │ ├── GitConfigurationKeyComparer.cs │ │ ├── GitStreamReader.cs │ │ ├── GitVersion.cs │ │ ├── Gpg.cs │ │ ├── HostProvider.cs │ │ ├── HostProviderRegistry.cs │ │ ├── HttpClientExtensions.cs │ │ ├── HttpClientFactory.cs │ │ ├── HttpContentExtensions.cs │ │ ├── HttpRequestExtensions.cs │ │ ├── ICredentialStore.cs │ │ ├── ISessionManager.cs │ │ ├── ISystemPrompts.cs │ │ ├── ITerminal.cs │ │ ├── ITrace2Writer.cs │ │ ├── IniFile.cs │ │ ├── InputArguments.cs │ │ ├── InternalsVisibleTo.cs │ │ ├── Interop/ │ │ │ ├── InteropException.cs │ │ │ ├── InteropUtils.cs │ │ │ ├── Linux/ │ │ │ │ ├── LinuxConfigParser.cs │ │ │ │ ├── LinuxFileSystem.cs │ │ │ │ ├── LinuxSessionManager.cs │ │ │ │ ├── LinuxSettings.cs │ │ │ │ ├── LinuxTerminal.cs │ │ │ │ ├── Native/ │ │ │ │ │ ├── Glib.cs │ │ │ │ │ ├── Gobject.cs │ │ │ │ │ ├── Libsecret.cs │ │ │ │ │ └── termios_Linux.cs │ │ │ │ ├── SecretServiceCollection.cs │ │ │ │ └── SecretServiceCredential.cs │ │ │ ├── MacOS/ │ │ │ │ ├── MacOSEnvironment.cs │ │ │ │ ├── MacOSFileSystem.cs │ │ │ │ ├── MacOSKeychain.cs │ │ │ │ ├── MacOSKeychainCredential.cs │ │ │ │ ├── MacOSPreferences.cs │ │ │ │ ├── MacOSSessionManager.cs │ │ │ │ ├── MacOSSettings.cs │ │ │ │ ├── MacOSTerminal.cs │ │ │ │ └── Native/ │ │ │ │ ├── CoreFoundation.cs │ │ │ │ ├── LibC.cs │ │ │ │ ├── LibSystem.cs │ │ │ │ ├── SecurityFramework.cs │ │ │ │ └── termios_MacOS.cs │ │ │ ├── Posix/ │ │ │ │ ├── GpgPassCredentialStore.cs │ │ │ │ ├── Native/ │ │ │ │ │ ├── Fcntl.cs │ │ │ │ │ ├── Signal.cs │ │ │ │ │ ├── Stat.cs │ │ │ │ │ ├── Stdio.cs │ │ │ │ │ ├── Stdlib.cs │ │ │ │ │ ├── Termios.cs │ │ │ │ │ └── Unistd.cs │ │ │ │ ├── PosixEnvironment.cs │ │ │ │ ├── PosixFileDescriptor.cs │ │ │ │ ├── PosixFileSystem.cs │ │ │ │ ├── PosixSessionManager.cs │ │ │ │ └── PosixTerminal.cs │ │ │ ├── U8StringConverter.cs │ │ │ ├── U8StringMarshaler.cs │ │ │ └── Windows/ │ │ │ ├── DpapiCredentialStore.cs │ │ │ ├── Native/ │ │ │ │ ├── Advapi32.cs │ │ │ │ ├── CredUi.cs │ │ │ │ ├── Kernel32.cs │ │ │ │ ├── Ole32.cs │ │ │ │ ├── Shell32.cs │ │ │ │ ├── User32.cs │ │ │ │ └── Win32Error.cs │ │ │ ├── WindowsCredential.cs │ │ │ ├── WindowsCredentialManager.cs │ │ │ ├── WindowsEnvironment.cs │ │ │ ├── WindowsFileSystem.cs │ │ │ ├── WindowsProcessManager.cs │ │ │ ├── WindowsSessionManager.cs │ │ │ ├── WindowsSettings.cs │ │ │ ├── WindowsSystemPrompts.cs │ │ │ └── WindowsTerminal.cs │ │ ├── NameValueCollectionExtensions.cs │ │ ├── NullCredentialStore.cs │ │ ├── PlaintextCredentialStore.cs │ │ ├── PlatformUtils.cs │ │ ├── ProcessManager.cs │ │ ├── Settings.cs │ │ ├── StandardStreams.cs │ │ ├── StreamExtensions.cs │ │ ├── StringExtensions.cs │ │ ├── TerminalMenu.cs │ │ ├── Trace.cs │ │ ├── Trace2.cs │ │ ├── Trace2CollectorWriter.cs │ │ ├── Trace2Exception.cs │ │ ├── Trace2FileWriter.cs │ │ ├── Trace2Message.cs │ │ ├── Trace2StreamWriter.cs │ │ ├── TraceUtils.cs │ │ ├── UI/ │ │ │ ├── Assets/ │ │ │ │ ├── Base.axaml │ │ │ │ ├── ButtonHyperlink.axaml │ │ │ │ ├── Controls.axaml │ │ │ │ └── Images.axaml │ │ │ ├── AvaloniaApp.axaml │ │ │ ├── AvaloniaApp.axaml.cs │ │ │ ├── AvaloniaUi.cs │ │ │ ├── Commands/ │ │ │ │ ├── CredentialsCommand.cs │ │ │ │ ├── DefaultAccountCommand.cs │ │ │ │ ├── DeviceCodeCommand.cs │ │ │ │ └── OAuthCommand.cs │ │ │ ├── Controls/ │ │ │ │ ├── AboutWindow.axaml │ │ │ │ ├── AboutWindow.axaml.cs │ │ │ │ ├── DialogWindow.axaml │ │ │ │ ├── DialogWindow.axaml.cs │ │ │ │ ├── IFocusable.cs │ │ │ │ ├── ProgressWindow.axaml │ │ │ │ └── ProgressWindow.axaml.cs │ │ │ ├── Converters/ │ │ │ │ ├── BoolConvertersEx.cs │ │ │ │ └── WindowClientAreaConverters.cs │ │ │ ├── Dispatcher.cs │ │ │ ├── HelperApplication.cs │ │ │ ├── HelperCommand.cs │ │ │ ├── RelayCommand.cs │ │ │ ├── ViewModels/ │ │ │ │ ├── CredentialsViewModel.cs │ │ │ │ ├── DefaultAccountViewModel.cs │ │ │ │ ├── DeviceCodeViewModel.cs │ │ │ │ ├── EnableNtlmViewModel.cs │ │ │ │ ├── OAuthViewModel.cs │ │ │ │ ├── ViewModel.cs │ │ │ │ └── WindowViewModel.cs │ │ │ └── Views/ │ │ │ ├── CredentialsView.axaml │ │ │ ├── CredentialsView.axaml.cs │ │ │ ├── DefaultAccountView.axaml │ │ │ ├── DefaultAccountView.axaml.cs │ │ │ ├── DeviceCodeView.axaml │ │ │ ├── DeviceCodeView.axaml.cs │ │ │ ├── EnableNtlmView.axaml │ │ │ ├── EnableNtlmView.cs │ │ │ ├── OAuthView.axaml │ │ │ └── OAuthView.axaml.cs │ │ ├── UriExtensions.cs │ │ ├── WslUtils.cs │ │ └── X509Utils.cs │ ├── Core.Tests/ │ │ ├── ApplicationTests.cs │ │ ├── Authentication/ │ │ │ ├── AuthenticationBaseTests.cs │ │ │ ├── BasicAuthenticationTests.cs │ │ │ ├── MicrosoftAuthenticationTests.cs │ │ │ ├── OAuth2ClientTests.cs │ │ │ ├── OAuth2CryptographicCodeGeneratorTests.cs │ │ │ ├── OAuth2SystemWebBrowserTests.cs │ │ │ └── WindowsIntegratedAuthenticationTests.cs │ │ ├── Base64UrlConvertTests.cs │ │ ├── Commands/ │ │ │ ├── ConfigureCommandTests.cs │ │ │ ├── DiagnoseCommandTests.cs │ │ │ ├── EraseCommandTests.cs │ │ │ ├── GetCommandTests.cs │ │ │ ├── GitCommandBaseTests.cs │ │ │ ├── StoreCommandTests.cs │ │ │ └── UnconfigureCommandTests.cs │ │ ├── ConfigurationServiceTests.cs │ │ ├── Core.Tests.csproj │ │ ├── CurlCookieTests.cs │ │ ├── EnsureArgumentTests.cs │ │ ├── EnumerableExtensionsTests.cs │ │ ├── EnvironmentTests.cs │ │ ├── GenericHostProviderTests.cs │ │ ├── GenericOAuthConfigTests.cs │ │ ├── GitConfigurationKeyComparerTests.cs │ │ ├── GitConfigurationTests.cs │ │ ├── GitStreamReaderTests.cs │ │ ├── GitTests.cs │ │ ├── GitVersionTests.cs │ │ ├── HostProviderRegistryTests.cs │ │ ├── HostProviderTests.cs │ │ ├── HttpClientExtensionsTests.cs │ │ ├── HttpClientFactoryTests.cs │ │ ├── HttpRequestExtensionsTests.cs │ │ ├── IniFileTests.cs │ │ ├── InputArgumentsTests.cs │ │ ├── Interop/ │ │ │ ├── Linux/ │ │ │ │ ├── LinuxConfigParserTests.cs │ │ │ │ ├── LinuxFileSystemTests.cs │ │ │ │ ├── LinuxSettingsTests.cs │ │ │ │ └── SecretServiceCollectionTests.cs │ │ │ ├── MacOS/ │ │ │ │ ├── MacOSFileSystemTests.cs │ │ │ │ ├── MacOSKeychainTests.cs │ │ │ │ └── MacOSPreferencesTests.cs │ │ │ ├── Posix/ │ │ │ │ ├── GnuPassCredentialStoreTests.cs │ │ │ │ └── PosixFileSystemTests.cs │ │ │ ├── U8StringConverterTests.cs │ │ │ └── Windows/ │ │ │ ├── DpapiCredentialStoreTests.cs │ │ │ ├── WindowsCredentialManagerTests.cs │ │ │ ├── WindowsFileSystemTests.cs │ │ │ └── WindowsSystemPromptsTests.cs │ │ ├── PlaintextCredentialStoreTests.cs │ │ ├── ProcessManagerTests.cs │ │ ├── SettingsTests.cs │ │ ├── StreamExtensionsTests.cs │ │ ├── StringExtensionsTests.cs │ │ ├── TestProcessManager.cs │ │ ├── TokenEndpointResponseJsonTest.cs │ │ ├── Trace2MessageTests.cs │ │ ├── Trace2Tests.cs │ │ ├── TraceTests.cs │ │ ├── TraceUtilsTests.cs │ │ ├── UriExtensionsTests.cs │ │ └── WslUtilsTests.cs │ ├── Directory.Build.props │ ├── DotnetTool/ │ │ ├── DotnetTool.csproj │ │ ├── DotnetToolSettings.xml │ │ ├── dotnet-tool.nuspec │ │ ├── layout.ps1 │ │ └── pack.ps1 │ ├── Git-Credential-Manager/ │ │ ├── Git-Credential-Manager.csproj │ │ ├── NOTICE │ │ └── Program.cs │ ├── GitHub/ │ │ ├── AuthenticationResult.cs │ │ ├── Diagnostics/ │ │ │ └── GitHubApiDiagnostic.cs │ │ ├── GitHub.csproj │ │ ├── GitHubAuthChallenge.cs │ │ ├── GitHubAuthentication.cs │ │ ├── GitHubConstants.cs │ │ ├── GitHubHostProvider.Commands.cs │ │ ├── GitHubHostProvider.cs │ │ ├── GitHubOAuth2Client.cs │ │ ├── GitHubResources.Designer.cs │ │ ├── GitHubResources.resx │ │ ├── GitHubRestApi.cs │ │ ├── InternalsVisibleTo.cs │ │ └── UI/ │ │ ├── Commands/ │ │ │ ├── CredentialsCommand.cs │ │ │ ├── DeviceCommand.cs │ │ │ ├── SelectAccountCommand.cs │ │ │ └── TwoFactorCommand.cs │ │ ├── Controls/ │ │ │ ├── HorizontalShadowDivider.axaml │ │ │ ├── HorizontalShadowDivider.axaml.cs │ │ │ ├── SixDigitInput.axaml │ │ │ └── SixDigitInput.axaml.cs │ │ ├── ViewModels/ │ │ │ ├── CredentialsViewModel.cs │ │ │ ├── DeviceCodeViewModel.cs │ │ │ ├── SelectAccountViewModel.cs │ │ │ └── TwoFactorViewModel.cs │ │ └── Views/ │ │ ├── CredentialsView.axaml │ │ ├── CredentialsView.axaml.cs │ │ ├── DeviceCodeView.axaml │ │ ├── DeviceCodeView.axaml.cs │ │ ├── SelectAccountView.axaml │ │ ├── SelectAccountView.axaml.cs │ │ ├── TwoFactorView.axaml │ │ └── TwoFactorView.axaml.cs │ ├── GitHub.Tests/ │ │ ├── GitHub.Tests.csproj │ │ ├── GitHubAuthChallengeTests.cs │ │ ├── GitHubAuthenticationTests.cs │ │ ├── GitHubHostProviderTests.cs │ │ └── GitHubRestApiTests.cs │ ├── GitHub.UI.Avalonia/ │ │ └── Commands/ │ │ └── SelectAccountCommandImpl.cs │ ├── GitLab/ │ │ ├── GitLab.csproj │ │ ├── GitLabAuthentication.cs │ │ ├── GitLabConstants.cs │ │ ├── GitLabHostProvider.cs │ │ ├── GitLabOAuth2Client.cs │ │ ├── InternalsVisibleTo.cs │ │ └── UI/ │ │ ├── Assets/ │ │ │ └── Images.axaml │ │ ├── Commands/ │ │ │ └── CredentialsCommand.cs │ │ ├── ViewModels/ │ │ │ └── CredentialsViewModel.cs │ │ └── Views/ │ │ ├── CredentialsView.axaml │ │ └── CredentialsView.axaml.cs │ ├── GitLab.Tests/ │ │ ├── GitLab.Tests.csproj │ │ ├── GitLabAuthenticationTests.cs │ │ └── GitLabHostProviderTests.cs │ ├── Microsoft.AzureRepos/ │ │ ├── AzureDevOpsAuthorityCache.cs │ │ ├── AzureDevOpsConstants.cs │ │ ├── AzureDevOpsRestApi.cs │ │ ├── AzureReposBindingManager.cs │ │ ├── AzureReposHostProvider.cs │ │ ├── InternalsVisibleTo.cs │ │ ├── Microsoft.AzureRepos.csproj │ │ └── UriHelpers.cs │ ├── Microsoft.AzureRepos.Tests/ │ │ ├── AzureDevOpsApiTests.cs │ │ ├── AzureReposAuthorityCacheTests.cs │ │ ├── AzureReposBindingManagerTests.cs │ │ ├── AzureReposHostProviderTests.cs │ │ ├── Microsoft.AzureRepos.Tests.csproj │ │ └── UriHelpersTests.cs │ └── TestInfrastructure/ │ ├── AssertEx.cs │ ├── GitTestUtilities.cs │ ├── Objects/ │ │ ├── NullTrace.cs │ │ ├── TestCommandContext.cs │ │ ├── TestCredentialStore.cs │ │ ├── TestEnvironment.cs │ │ ├── TestFileSystem.cs │ │ ├── TestGit.cs │ │ ├── TestGitConfiguration.cs │ │ ├── TestGpg.cs │ │ ├── TestHostProvider.cs │ │ ├── TestHostProviderRegistry.cs │ │ ├── TestHttpClientFactory.cs │ │ ├── TestHttpMessageHandler.cs │ │ ├── TestOAuth2Server.cs │ │ ├── TestOAuth2WebBrowser.cs │ │ ├── TestSessionManager.cs │ │ ├── TestSettings.cs │ │ ├── TestStandardStreams.cs │ │ └── TestTerminal.cs │ ├── PlatformAttributes.cs │ ├── RestTestUtilities.cs │ ├── TestInfrastructure.csproj │ └── TestUtils.cs └── windows/ ├── Directory.Build.props └── Installer.Windows/ ├── Installer.Windows.csproj ├── Setup.iss └── layout.ps1 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .azure-pipelines/release.yml ================================================ name: $(Date:yyyyMMdd)$(Rev:.r) trigger: none pr: none resources: repositories: - repository: 1ESPipelines type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release parameters: - name: 'esrp' type: boolean default: true displayName: 'Enable ESRP code signing' - name: 'github' type: boolean default: true displayName: 'Enable GitHub release publishing' - name: 'nuget' type: boolean default: true displayName: 'Enable NuGet package publishing' # # 1ES Pipeline Templates do not allow using a matrix strategy so we create # a YAML object parameter with and foreach to create jobs for each entry. # Each OS has its own matrix object since their build steps differ. # - name: windows_matrix type: object default: - id: windows_x86 jobName: 'Windows (x86)' runtime: win-x86 pool: GitClientPME-1ESHostedPool-intel-pc image: win-x86_64-ado1es os: windows - id: windows_x64 jobName: 'Windows (x64)' runtime: win-x64 pool: GitClientPME-1ESHostedPool-intel-pc image: win-x86_64-ado1es os: windows - id: windows_arm64 jobName: 'Windows (ARM64)' runtime: win-arm64 pool: GitClientPME-1ESHostedPool-arm64-pc image: win-arm64-ado1es os: windows - name: macos_matrix type: object default: - id: macos_x64 jobName: 'macOS (x64)' runtime: osx-x64 pool: 'Azure Pipelines' image: macOS-latest os: macos - id: macos_arm64 jobName: 'macOS (ARM64)' runtime: osx-arm64 pool: 'Azure Pipelines' image: macOS-latest os: macos - name: linux_matrix type: object default: - id: linux_x64 jobName: 'Linux (x64)' runtime: linux-x64 pool: GitClientPME-1ESHostedPool-intel-pc image: ubuntu-x86_64-ado1es os: linux - id: linux_arm64 jobName: 'Linux (ARM64)' runtime: linux-arm64 pool: GitClientPME-1ESHostedPool-arm64-pc image: ubuntu-arm64-ado1es os: linux variables: - name: 'esrpAppConnectionName' value: '1ESGitClient-ESRP-App' - name: 'esrpMIConnectionName' value: '1ESGitClient-ESRP-MI' - name: 'githubConnectionName' value: 'GitHub-GitCredentialManager' - name: 'nugetConnectionName' value: '1ESGitClient-NuGet' # ESRP signing variables set in the pipeline settings: # - esrpEndpointUrl # - esrpClientId # - esrpTenantId # - esrpKeyVaultName # - esrpSignReqCertName extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelines parameters: sdl: # SDL source analysis tasks only run on Windows images sourceAnalysisPool: name: GitClientPME-1ESHostedPool-intel-pc image: win-x86_64-ado1es os: windows stages: - stage: build displayName: 'Build and Sign' jobs: # # Windows build jobs # - ${{ each dim in parameters.windows_matrix }}: - job: ${{ dim.id }} displayName: ${{ dim.jobName }} pool: name: ${{ dim.pool }} image: ${{ dim.image }} os: ${{ dim.os }} templateContext: outputs: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)\_final' artifactName: '${{ dim.runtime }}' steps: - checkout: self - task: PowerShell@2 displayName: 'Read version file' inputs: targetType: inline script: | $version = (Get-Content .\VERSION) -replace '\.\d+$', '' Write-Host "##vso[task.setvariable variable=version;isReadOnly=true]$version" - task: UseDotNet@2 displayName: 'Use .NET 8 SDK' inputs: packageType: sdk version: '8.x' - task: PowerShell@2 displayName: 'Build payload' inputs: targetType: filePath filePath: '.\src\windows\Installer.Windows\layout.ps1' arguments: | -Configuration Release ` -Output $(Build.ArtifactStagingDirectory)\payload ` -SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw ` -RuntimeIdentifier ${{ dim.runtime }} - task: ArchiveFiles@2 displayName: 'Archive symbols' inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\symbols_raw' includeRootFolder: false archiveType: zip archiveFile: '$(Build.ArtifactStagingDirectory)\symbols\gcm-${{ dim.runtime }}-$(version)-symbols.zip' - ${{ if eq(parameters.esrp, true) }}: - task: EsrpCodeSigning@5 displayName: 'Sign payload' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)\payload' pattern: | **/*.exe **/*.dll useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-230012", "OperationCode": "SigntoolSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { "OpusName": "Microsoft", "OpusInfo": "https://www.microsoft.com", "FileDigest": "/fd SHA256", "PageHash": "/NPH", "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" } }, { "KeyCode": "CP-230012", "OperationCode": "SigntoolVerify", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": {} } ] - task: PowerShell@2 displayName: 'Clean up code signing artifacts' inputs: targetType: inline script: | Remove-Item "$(Build.ArtifactStagingDirectory)\payload\CodeSignSummary-*.md" - task: PowerShell@2 displayName: 'Build installers' inputs: targetType: inline script: | dotnet build '.\src\windows\Installer.Windows\Installer.Windows.csproj' ` --configuration Release ` --no-dependencies ` -p:NoLayout=true ` -p:PayloadPath="$(Build.ArtifactStagingDirectory)\payload" ` -p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" ` -p:RuntimeIdentifier="${{ dim.runtime }}" - ${{ if eq(parameters.esrp, true) }}: - task: EsrpCodeSigning@5 condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) displayName: 'Sign installers' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)\installers' pattern: '**/*.exe' useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-230012", "OperationCode": "SigntoolSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { "OpusName": "Microsoft", "OpusInfo": "https://www.microsoft.com", "FileDigest": "/fd SHA256", "PageHash": "/NPH", "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" } }, { "KeyCode": "CP-230012", "OperationCode": "SigntoolVerify", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": {} } ] - task: ArchiveFiles@2 displayName: 'Archive payload' inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\payload' includeRootFolder: false archiveType: zip archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-${{ dim.runtime }}-$(version).zip' - task: PowerShell@2 displayName: 'Collect artifacts for publishing' inputs: targetType: inline script: | New-Item -Path "$(Build.ArtifactStagingDirectory)\_final" -ItemType Directory -Force Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.exe" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\symbols\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\payload" -Destination "$(Build.ArtifactStagingDirectory)\_final" -Recurse # # macOS build jobs # - ${{ each dim in parameters.macos_matrix }}: - job: ${{ dim.id }} displayName: ${{ dim.jobName }} pool: name: ${{ dim.pool }} image: ${{ dim.image }} os: ${{ dim.os }} templateContext: outputs: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/_final' artifactName: '${{ dim.runtime }}' steps: - checkout: self - task: Bash@3 displayName: 'Read version file' inputs: targetType: inline script: | echo "##vso[task.setvariable variable=version;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" - task: UseDotNet@2 displayName: 'Use .NET 8 SDK' inputs: packageType: sdk version: '8.x' - task: Bash@3 displayName: 'Build payload' inputs: targetType: filePath filePath: './src/osx/Installer.Mac/layout.sh' arguments: | --runtime="${{ dim.runtime }}" \ --configuration="Release" \ --output="$(Build.ArtifactStagingDirectory)/payload" \ --symbol-output="$(Build.ArtifactStagingDirectory)/symbols_raw" - task: ArchiveFiles@2 displayName: 'Archive symbols' inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/symbols_raw' includeRootFolder: false archiveType: tar tarCompression: gz archiveFile: '$(Build.ArtifactStagingDirectory)/symbols/gcm-${{ dim.runtime }}-$(version)-symbols.tar.gz' - ${{ if eq(parameters.esrp, true) }}: - task: AzureKeyVault@2 displayName: 'Download developer certificate' inputs: azureSubscription: '$(esrpMIConnectionName)' keyVaultName: '$(esrpKeyVaultName)' secretsFilter: 'mac-developer-certificate,mac-developer-certificate-password,mac-developer-certificate-identity' - task: Bash@3 displayName: 'Import developer certificate' inputs: targetType: inline script: | # Create and unlock a keychain for the developer certificate security create-keychain -p pwd $(Agent.TempDirectory)/buildagent.keychain security default-keychain -s $(Agent.TempDirectory)/buildagent.keychain security unlock-keychain -p pwd $(Agent.TempDirectory)/buildagent.keychain echo $(mac-developer-certificate) | base64 -D > $(Agent.TempDirectory)/cert.p12 echo $(mac-developer-certificate-password) > $(Agent.TempDirectory)/cert.password # Import the developer certificate security import $(Agent.TempDirectory)/cert.p12 \ -k $(Agent.TempDirectory)/buildagent.keychain \ -P "$(mac-developer-certificate-password)" \ -T /usr/bin/codesign # Clean up the cert file immediately after import rm $(Agent.TempDirectory)/cert.p12 # Set ACLs to allow codesign to access the private key security set-key-partition-list \ -S apple-tool:,apple:,codesign: \ -s -k pwd \ $(Agent.TempDirectory)/buildagent.keychain - task: Bash@3 displayName: 'Developer sign payload files' inputs: targetType: inline script: | mkdir -p $(Build.ArtifactStagingDirectory)/tosign/payload # Copy the files that need signing (Mach-o executables and dylibs) pushd $(Build.ArtifactStagingDirectory)/payload find . -type f -exec file --mime {} + \ | sed -n '/mach/s/: .*//p' \ | while IFS= read -r f; do rel="${f#./}" tgt="$(Build.ArtifactStagingDirectory)/tosign/payload/$rel" mkdir -p "$(dirname "$tgt")" cp -- "$f" "$tgt" done popd # Developer sign the files ./src/osx/Installer.Mac/codesign.sh \ "$(Build.ArtifactStagingDirectory)/tosign/payload" \ "$(mac-developer-certificate-identity)" \ "$PWD/src/osx/Installer.Mac/entitlements.xml" # ESRP code signing for macOS requires the files be packaged in a zip file for submission - task: ArchiveFiles@2 displayName: 'Archive files for signing' inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/tosign/payload' includeRootFolder: false archiveType: zip archiveFile: '$(Build.ArtifactStagingDirectory)/tosign/payload.zip' - task: EsrpCodeSigning@5 displayName: 'Sign payload' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)/tosign' pattern: 'payload.zip' useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-401337-Apple", "OperationCode": "MacAppDeveloperSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { "Hardening": "Enable" } } ] # Extract signed files, overwriting the unsigned files, ready for packaging - task: Bash@3 displayName: 'Extract signed payload files' inputs: targetType: inline script: | unzip -uo $(Build.ArtifactStagingDirectory)/tosign/payload.zip -d $(Build.ArtifactStagingDirectory)/payload - task: Bash@3 displayName: 'Build component package' inputs: targetType: filePath filePath: './src/osx/Installer.Mac/pack.sh' arguments: | --version="$(version)" \ --payload="$(Build.ArtifactStagingDirectory)/payload" \ --output="$(Build.ArtifactStagingDirectory)/pkg/com.microsoft.gitcredentialmanager.component.pkg" - task: Bash@3 displayName: 'Build installer package' inputs: targetType: filePath filePath: './src/osx/Installer.Mac/dist.sh' arguments: | --version="$(version)" \ --runtime="${{ dim.runtime }}" \ --package-path="$(Build.ArtifactStagingDirectory)/pkg" \ --output="$(Build.ArtifactStagingDirectory)/installers/gcm-${{ dim.runtime }}-$(version).pkg" - ${{ if eq(parameters.esrp, true) }}: # ESRP code signing for macOS requires the files be packaged in a zip file first - task: Bash@3 displayName: 'Prepare installer package for signing' inputs: targetType: inline script: | mkdir -p $(Build.ArtifactStagingDirectory)/tosign cd $(Build.ArtifactStagingDirectory)/installers zip -rX $(Build.ArtifactStagingDirectory)/tosign/installers.zip *.pkg - task: EsrpCodeSigning@5 displayName: 'Sign installer package' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)/tosign' pattern: 'installers.zip' useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-401337-Apple", "OperationCode": "MacAppDeveloperSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { "Hardening": "Enable" } } ] # Extract signed installer, overwriting the unsigned installer - task: Bash@3 displayName: 'Extract signed installer package' inputs: targetType: inline script: | unzip -uo $(Build.ArtifactStagingDirectory)/tosign/installers.zip -d $(Build.ArtifactStagingDirectory)/installers - task: Bash@3 displayName: 'Prepare installer package for notarization' inputs: targetType: inline script: | mkdir -p $(Build.ArtifactStagingDirectory)/tosign cd $(Build.ArtifactStagingDirectory)/installers # Remove previous installers.zip to avoid any confusion rm -f $(Build.ArtifactStagingDirectory)/tosign/installers.zip zip -rX $(Build.ArtifactStagingDirectory)/tosign/installers.zip *.pkg - task: EsrpCodeSigning@5 displayName: 'Notarize installer package' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)/tosign' pattern: 'installers.zip' useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-401337-Apple", "OperationCode": "MacAppNotarize", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { "BundleId": "com.microsoft.gitcredentialmanager" } } ] # Extract signed and notarized installer pkg files, overwriting the unsigned files, ready for upload - task: Bash@3 displayName: 'Extract signed and notarized installer package' inputs: targetType: inline script: | unzip -uo $(Build.ArtifactStagingDirectory)/tosign/installers.zip -d $(Build.ArtifactStagingDirectory)/installers - task: ArchiveFiles@2 displayName: 'Archive payload' inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/payload' includeRootFolder: false archiveType: tar tarCompression: gz archiveFile: '$(Build.ArtifactStagingDirectory)/installers/gcm-${{ dim.runtime }}-$(version).tar.gz' - task: Bash@3 displayName: 'Collect artifacts for publishing' inputs: targetType: inline script: | mkdir -p $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/installers/*.pkg $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final # # Linux build jobs # - ${{ each dim in parameters.linux_matrix }}: - job: ${{ dim.id }} displayName: ${{ dim.jobName }} pool: name: ${{ dim.pool }} image: ${{ dim.image }} os: ${{ dim.os }} templateContext: outputs: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/_final' artifactName: '${{ dim.runtime }}' steps: - checkout: self - task: Bash@3 displayName: 'Read version file' inputs: targetType: inline script: | echo "##vso[task.setvariable variable=version;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" - task: UseDotNet@2 displayName: 'Use .NET 8 SDK' inputs: packageType: sdk version: '8.x' - task: Bash@3 displayName: 'Build payload' inputs: targetType: filePath filePath: './src/linux/Packaging.Linux/layout.sh' arguments: | --runtime="${{ dim.runtime }}" \ --configuration="Release" \ --output="$(Build.ArtifactStagingDirectory)/payload" \ --symbol-output="$(Build.ArtifactStagingDirectory)/symbols_raw" - task: Bash@3 displayName: 'Build packages' inputs: targetType: filePath filePath: './src/linux/Packaging.Linux/pack.sh' arguments: | --version="$(version)" \ --runtime="${{ dim.runtime }}" \ --payload="$(Build.ArtifactStagingDirectory)/payload" \ --symbols="$(Build.ArtifactStagingDirectory)/symbols_raw" \ --output="$(Build.ArtifactStagingDirectory)/pkg" - task: Bash@3 displayName: 'Move packages' inputs: targetType: inline script: | # Move symbols mkdir -p $(Build.ArtifactStagingDirectory)/symbols mv $(Build.ArtifactStagingDirectory)/pkg/tar/gcm-*-symbols.tar.gz $(Build.ArtifactStagingDirectory)/symbols # Move binary packages mkdir -p $(Build.ArtifactStagingDirectory)/installers mv $(Build.ArtifactStagingDirectory)/pkg/tar/*.tar.gz $(Build.ArtifactStagingDirectory)/installers mv $(Build.ArtifactStagingDirectory)/pkg/deb/*.deb $(Build.ArtifactStagingDirectory)/installers - ${{ if eq(parameters.esrp, true) }}: - task: EsrpCodeSigning@5 displayName: 'Sign Debian package' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)/installers' pattern: | **/*.deb useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-453387-Pgp", "OperationCode": "LinuxSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": {} } ] - task: Bash@3 displayName: 'Collect artifacts for publishing' inputs: targetType: inline script: | mkdir -p $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/installers/*.deb $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final # # .NET Tool build job # - job: dotnet_tool displayName: '.NET Tool NuGet Package' pool: name: GitClientPME-1ESHostedPool-intel-pc image: win-x86_64-ado1es os: windows templateContext: outputs: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/packages' artifactName: 'dotnet-tool' steps: - checkout: self - task: PowerShell@2 displayName: 'Read version file' inputs: targetType: inline script: | $version = (Get-Content .\VERSION) -replace '\.\d+$', '' Write-Host "##vso[task.setvariable variable=version;isReadOnly=true]$version" - task: UseDotNet@2 displayName: 'Use .NET 8 SDK' inputs: packageType: sdk version: '8.x' - task: NuGetToolInstaller@1 displayName: 'Install NuGet CLI' inputs: versionSpec: '>= 6.0' - task: PowerShell@2 displayName: 'Build payload' inputs: targetType: filePath filePath: './src/shared/DotnetTool/layout.ps1' arguments: | -Configuration Release ` -Output "$(Build.ArtifactStagingDirectory)/nupkg" - ${{ if eq(parameters.esrp, true) }}: - task: EsrpCodeSigning@5 condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) displayName: 'Sign payload' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)/nupkg' pattern: | **/*.exe **/*.dll useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-230012", "OperationCode": "SigntoolSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { "OpusName": "Microsoft", "OpusInfo": "https://www.microsoft.com", "FileDigest": "/fd SHA256", "PageHash": "/NPH", "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" } }, { "KeyCode": "CP-230012", "OperationCode": "SigntoolVerify", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": {} } ] - task: PowerShell@2 displayName: 'Create NuGet packages' inputs: targetType: filePath filePath: './src/shared/DotnetTool/pack.ps1' arguments: | -Configuration Release ` -Version "$(version)" ` -PackageRoot "$(Build.ArtifactStagingDirectory)/nupkg" ` -Output "$(Build.ArtifactStagingDirectory)/packages" - ${{ if eq(parameters.esrp, true) }}: - task: EsrpCodeSigning@5 condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) displayName: 'Sign NuGet packages' inputs: connectedServiceName: '$(esrpAppConnectionName)' useMSIAuthentication: true appRegistrationClientId: '$(esrpClientId)' appRegistrationTenantId: '$(esrpTenantId)' authAkvName: '$(esrpKeyVaultName)' authSignCertName: '$(esrpSignReqCertName)' serviceEndpointUrl: '$(esrpEndpointUrl)' folderPath: '$(Build.ArtifactStagingDirectory)/packages' pattern: | **/*.nupkg **/*.snupkg useMinimatch: true signConfigType: inlineSignParams inlineOperation: | [ { "KeyCode": "CP-401405", "OperationCode": "NuGetSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": {} } ] - stage: release displayName: 'Release' dependsOn: [build] condition: and(succeeded(), or(eq('${{ parameters.github }}', true), eq('${{ parameters.nuget }}', true))) jobs: - job: release_validation displayName: 'Release validation' pool: name: GitClientPME-1ESHostedPool-intel-pc image: ubuntu-x86_64-ado1es os: linux steps: - task: Bash@3 displayName: 'Read version file' name: version inputs: targetType: inline script: | echo "##vso[task.setvariable variable=value;isOutput=true;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" - job: github displayName: 'Publish GitHub release' dependsOn: release_validation condition: and(succeeded(), eq('${{ parameters.github }}', true)) pool: name: GitClientPME-1ESHostedPool-intel-pc image: ubuntu-x86_64-ado1es os: linux variables: version: $[dependencies.release_validation.outputs['version.value']] templateContext: type: releaseJob isProduction: true inputs: # Installers and packages - input: pipelineArtifact artifactName: 'win-x86' targetPath: $(Pipeline.Workspace)/assets/win-x86 - input: pipelineArtifact artifactName: 'win-x64' targetPath: $(Pipeline.Workspace)/assets/win-x64 - input: pipelineArtifact artifactName: 'win-arm64' targetPath: $(Pipeline.Workspace)/assets/win-arm64 - input: pipelineArtifact artifactName: 'osx-x64' targetPath: $(Pipeline.Workspace)/assets/osx-x64 - input: pipelineArtifact artifactName: 'osx-arm64' targetPath: $(Pipeline.Workspace)/assets/osx-arm64 - input: pipelineArtifact artifactName: 'linux-x64' targetPath: $(Pipeline.Workspace)/assets/linux-x64 - input: pipelineArtifact artifactName: 'linux-arm64' targetPath: $(Pipeline.Workspace)/assets/linux-arm64 - input: pipelineArtifact artifactName: 'dotnet-tool' targetPath: $(Pipeline.Workspace)/assets/dotnet-tool steps: - task: GitHubRelease@1 displayName: 'Create Draft GitHub Release' condition: and(succeeded(), eq('${{ parameters.github }}', true)) inputs: gitHubConnection: $(githubConnectionName) repositoryName: git-ecosystem/git-credential-manager tag: 'v$(version)' tagSource: userSpecifiedTag target: release title: 'GCM $(version)' isDraft: true addChangeLog: false assets: | $(Pipeline.Workspace)/assets/win-x86/*.exe $(Pipeline.Workspace)/assets/win-x86/*.zip $(Pipeline.Workspace)/assets/win-x64/*.exe $(Pipeline.Workspace)/assets/win-x64/*.zip $(Pipeline.Workspace)/assets/win-arm64/*.exe $(Pipeline.Workspace)/assets/win-arm64/*.zip $(Pipeline.Workspace)/assets/osx-x64/*.pkg $(Pipeline.Workspace)/assets/osx-x64/*.tar.gz $(Pipeline.Workspace)/assets/osx-arm64/*.pkg $(Pipeline.Workspace)/assets/osx-arm64/*.tar.gz $(Pipeline.Workspace)/assets/linux-x64/*.deb $(Pipeline.Workspace)/assets/linux-x64/*.tar.gz $(Pipeline.Workspace)/assets/linux-arm64/*.deb $(Pipeline.Workspace)/assets/linux-arm64/*.tar.gz $(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg $(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg - job: nuget displayName: 'Publish NuGet package' dependsOn: release_validation condition: and(succeeded(), eq('${{ parameters.nuget }}', true)) pool: name: GitClientPME-1ESHostedPool-intel-pc image: ubuntu-x86_64-ado1es os: linux variables: version: $[dependencies.release_validation.outputs['version.value']] templateContext: inputs: - input: pipelineArtifact artifactName: 'dotnet-tool' targetPath: $(Pipeline.Workspace)/assets/dotnet-tool outputs: - output: nuget condition: and(succeeded(), eq('${{ parameters.nuget }}', true)) displayName: 'Publish .NET Tool NuGet package' packagesToPush: '$(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg;$(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg' packageParentPath: $(Pipeline.Workspace)/assets/dotnet-tool nuGetFeedType: external publishPackageMetadata: true publishFeedCredentials: $(nugetConnectionName) ================================================ FILE: .code-coverage/coverlet.settings.xml ================================================ cobertura,lcov ================================================ FILE: .github/ISSUE_TEMPLATE/auth-issue.yml ================================================ name: Authentication issue description: An authentication problem occurred when running a Git command. labels: ["auth-issue"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this issue! Please answer as many of the below questions as you can - this helps us better understand what the problem is, and consequently how to resolve your problem. - type: input id: version attributes: label: Version description: | What version of Git Credential Manager are you using? Run `git credential-manager --version` from a terminal to see the current version. If you are on an older version of GCM please try updating before creating an issue as the problem you are experiencing may have already been fixed. placeholder: | ex: 2.0.8-beta+e1f8492d04 validations: required: true - type: dropdown id: os attributes: label: Operating system description: What operating system are you using? options: - Windows - macOS - Linux - Other - please describe below validations: required: true - type: input id: os-version attributes: label: OS version or distribution description: Please describe the version, CPU architecture (x64, ARM, etc), or Linux distribution you are using. placeholder: | ex: Windows 11 Pro, Monterey 12.5, Ubuntu 22.04 validations: required: true - type: dropdown id: provider attributes: label: Git hosting provider(s) description: What Git host provider are you trying to connect to? multiple: true options: - Azure DevOps - Azure DevOps Server (TFS/on-prem) - Bitbucket Cloud - Bitbucket Server/DC - GitHub - GitHub Enterprise Server - GitLab - Other - please describe below validations: required: true - type: input id: provider-other attributes: label: Other hosting provider description: If you selected "Other" above, please describe the Git host you are using. - type: dropdown id: azdo-urlformat attributes: label: | (Azure DevOps only) What format is your remote URL? description: | Tip: to see your remote URL run `git remote -v` from a terminal. options: - https://dev.azure.com/{org} - https://{org}@dev.azure.com/{org} - https://{org}.visualstudio.com - type: dropdown id: web-access attributes: label: Can you access the remote repository directly in the browser? description: | If you are unable to access the repository via a web browser then it is likely GCM will also be unable to access the repository with your user account. options: - Yes, I can access the repository - No, I get a permission error - No, for a different reason - please describe behavior below validations: required: true - type: textarea id: expected attributes: label: Expected behavior description: A clear and concise description of what your expectation are. placeholder: | ex: I am authenticated and my Git operation completes successfully. validations: required: true - type: textarea id: actual attributes: label: Actual behavior description: | A clear and concise description of what actually happens. Feel free to include screenshots of dialogs or errors here, but remember to **redact any sensitive information**! placeholder: | ex: An exception "FooException" is thrown, UI freezes, etc. validations: required: true - type: textarea id: logs attributes: label: Logs description: | To capture trace logs, set the environment variables `GCM_TRACE=1` and `GIT_TRACE=1` and re-run your Git command. If you are running inside of Windows Subsystem for Linux (WSL), you must also set an additional environment variable to enable tracing: `WSLENV=$WSLENV:GCM_TRACE`. For example: ```shell WSLENV=$WSLENV:GCM_TRACE:GIT_TRACE GCM_TRACE=1 GIT_TRACE=1 git fetch ``` If you are using GCM version 2.0.567 onwards you can also run `git credential-manager diagnose` to collect useful diagnostic information that can be attached here. :warning: **Please review and redact any private information before attaching logs and files!** ================================================ FILE: .github/ISSUE_TEMPLATE/feature-req.yml ================================================ name: Feature request description: A suggestion for a new feature in Git Credential Manager. labels: ["enhancement"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this feature request! Please be as detailed as possible describing your idea; how it fixes a problem or makes something easier. Although we cannot guarentee we will accept all requests, we will still take time to consider your ideas. Whilst we may be supportive of an idea in principal, as maintainers we may not always be able to dedicate time to implementing them. We always welcome community support and contributions however! :heart: - type: textarea id: description attributes: label: Feature description description: | A clear and concise description of the new feature. placeholder: | ex: Add spline reticulation option to widget authentication mechanism. validations: required: true ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: # Enable version updates for GitHub ecosystem - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [ main, release ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'csharp' ] steps: - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.1.0 with: dotnet-version: 8.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - run: | dotnet build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/continuous-integration.yml ================================================ name: ci on: workflow_dispatch: push: branches: [ main ] pull_request: branches: [ main ] jobs: # ================================ # Windows # ================================ windows: name: Windows runs-on: ${{ matrix.os }} strategy: matrix: include: - runtime: win-x86 os: windows-latest - runtime: win-x64 os: windows-latest - runtime: win-arm64 os: windows-11-arm steps: - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.1.0 with: dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore - name: Build run: | dotnet build src/windows/Installer.Windows/Installer.Windows.csproj ` --configuration=Release ` --runtime=${{ matrix.runtime }} - name: Test run: | dotnet test --verbosity normal ` --configuration=WindowsRelease ` --runtime=${{ matrix.runtime }} - name: Prepare artifacts shell: bash run: | mkdir -p artifacts/bin mv out/windows/Installer.Windows/bin/Release/net472/gcm*.exe artifacts/ mv out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }} artifacts/bin/ cp out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }}.sym/* artifacts/bin/${{ matrix.runtime }}/ - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: ${{ matrix.runtime }} path: | artifacts # ================================ # Linux # ================================ linux: name: Linux runs-on: ubuntu-latest strategy: matrix: runtime: [ linux-x64, linux-arm64, linux-arm ] steps: - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.1.0 with: dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore - name: Build run: | dotnet build src/linux/Packaging.Linux/*.csproj \ --configuration=Release --no-self-contained \ --runtime=${{ matrix.runtime }} - name: Test run: | dotnet test --verbosity normal --configuration=LinuxRelease - name: Prepare artifacts run: | mkdir -p artifacts mv out/linux/Packaging.Linux/Release/deb/*.deb artifacts/ mv out/linux/Packaging.Linux/Release/tar/*.tar.gz artifacts/ - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: ${{ matrix.runtime }} path: | artifacts # ================================ # macOS # ================================ osx: name: macOS runs-on: macos-latest strategy: matrix: runtime: [ osx-x64, osx-arm64 ] steps: - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5.1.0 with: dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore - name: Build run: | dotnet build src/osx/Installer.Mac/*.csproj \ --configuration=Release --no-self-contained \ --runtime=${{ matrix.runtime }} - name: Test run: | dotnet test --verbosity normal --configuration=MacRelease - name: Prepare artifacts run: | mkdir -p artifacts/bin mv out/osx/Installer.Mac/pkg/Release/payload "artifacts/bin/${{ matrix.runtime }}" cp out/osx/Installer.Mac/pkg/Release/payload.sym/* "artifacts/bin/${{ matrix.runtime }}/" mv out/osx/Installer.Mac/pkg/Release/gcm*.pkg artifacts/ - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: ${{ matrix.runtime }} path: | artifacts ================================================ FILE: .github/workflows/lint-docs.yml ================================================ name: "Lint documentation" on: workflow_dispatch: push: branches: [ main, linux ] paths: - '**.md' - '.github/workflows/lint-docs.yml' pull_request: branches: [ main, linux ] paths: - '**.md' - '.github/workflows/lint-docs.yml' jobs: lint-markdown: name: Lint markdown files runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: DavidAnson/markdownlint-cli2-action@07035fd053f7be764496c0f8d8f9f41f98305101 with: globs: | "**/*.md" "!.github/ISSUE_TEMPLATE" check-links: name: Check for broken links runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Run link checker # For any troubleshooting, see: # https://github.com/lycheeverse/lychee/blob/master/docs/TROUBLESHOOTING.md uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 with: # user-agent: if a user agent is not specified, some websites (e.g. # GitHub Docs) return HTTP errors which Lychee will interpret as # a broken link. # no-progress: do not show progress bar. Recommended for # non-interactive shells (e.g. for CI) # inputs: by default (.), this action checks files matching the # patterns: './**/*.md' './**/*.html' args: >- --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36" --no-progress . fail: true env: # A token is used to avoid GitHub rate limiting. A personal token with # no extra permissions is enough to be able to check public repos # See: https://github.com/lycheeverse/lychee#github-token GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} ================================================ FILE: .github/workflows/maintainer-absence.yml ================================================ name: maintainer-absence on: workflow_dispatch: inputs: startDate: description: 'First day of maintainer absence [mm-dd-yyyy]' required: true endDate: description: 'Last day of maintainer absence [mm-dd-yyyy]' required: true permissions: issues: write jobs: create-issue: name: create-issue runs-on: ubuntu-latest steps: - uses: actions/github-script@v8 with: script: | const startDate = new Date('${{ github.event.inputs.startDate }}'); const endDate = new Date('${{ github.event.inputs.endDate }}'); if (startDate > endDate) { throw 'Start date cannot be later than end date.'; } // Calculate total days of absence const differenceInDays = endDate.getTime() - startDate.getTime(); const lengthOfAbsence = differenceInDays/(1000 * 3600 * 24); // Create issue issue = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, // Use the briefer input date format in title (instead of JavaScript's full date string) title: `Maintainer(s) will be away from ${{ github.event.inputs.startDate }} until ${{ github.event.inputs.endDate }}`, body: `The ${context.repo.repo} maintainer(s) will be away for ${lengthOfAbsence} day${lengthOfAbsence > 1 ? 's' : ''} beginning on ${startDate.toDateString()} and ending on ${endDate.toDateString()}. During this time, the maintainer(s) will not be actively monitoring PRs, discussions, etc. Please report any issues requiring immediate attention to [@GitCredManager](https://twitter.com/GitCredManager) on Twitter.` }); // Pin issue - we use GraphQL since there is no GitHub API available for this const mutation = `mutation($issueId: ID!) { pinIssue(input: { issueId: $issueId }) { issue { repository { id } } } }`; const variables = { issueId: issue.data.node_id } const result = await github.graphql(mutation, variables) ================================================ FILE: .github/workflows/validate-install-from-source.yml ================================================ name: validate-install-from-source on: workflow_dispatch: push: branches: - main jobs: docker: name: ${{matrix.vector.image}} runs-on: ubuntu-latest strategy: fail-fast: false matrix: vector: - image: ubuntu - image: debian:bullseye - image: fedora # Centos no longer officially maintains images on Docker Hub. However, # tgagor is a contributor who pushes updated images weekly, which should # be sufficient for our validation needs. - image: tgagor/centos - image: redhat/ubi8 - image: alpine - image: alpine:3.19.8 - image: opensuse/leap - image: opensuse/tumbleweed - image: registry.suse.com/suse/sle15:15.4.27.11.31 - image: archlinux - image: mcr.microsoft.com/cbl-mariner/base/core:2.0 - image: mcr.microsoft.com/azurelinux/base/core:3.0 container: ${{matrix.vector.image}} steps: - run: | if [[ ${{matrix.vector.image}} == *"suse"* ]]; then zypper -n install tar gzip elif [[ ${{matrix.vector.image}} == *"centos"* ]]; then dnf install which -y elif [[ ${{matrix.vector.image}} == *"mariner"* || ${{matrix.vector.image}} == *"azurelinux"* ]]; then GNUPGHOME=/root/.gnupg tdnf update -y && GNUPGHOME=/root/.gnupg tdnf install tar -y # needed for `actions/checkout` fi - uses: actions/checkout@v6 - run: | sh "${GITHUB_WORKSPACE}/src/linux/Packaging.Linux/install-from-source.sh" -y git-credential-manager --help || exit 1 ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET project.lock.json project.fragment.lock.json artifacts/ **/Properties/launchSettings.json # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # macOS Finder files .DS_Store # GCM build output out/ # Temporary build directory .tmp/ # dotnet local tools .tools/ # Signing generated Files auth.json input.json ================================================ FILE: .lycheeignore ================================================ godaddy\.com[\\/]?$ gitlab\.com/-/profile/applications file:[\\/][\\/][\\/].* ================================================ FILE: .markdownlint.jsonc ================================================ // For information on writing markdownlint configuration see: // https://github.com/DavidAnson/markdownlint/blob/main/README.md#optionsconfig { "MD013": { "line_length": 80, "code_blocks": false, "headings": false, "tables": false }, "MD024": false // The format for some files require repeated headings, e.g. "Example" } ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to find out which attributes exist for C# debugging // Use hover for the description of the existing attributes // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "version": "0.2.0", "configurations": [ { "name": "Git Credential Manager (get)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/net8.0/git-credential-manager.dll", "args": ["get"], "cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager", "console": "integratedTerminal", "stopAtEntry": false, }, { "name": "Git Credential Manager (store)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/net8.0/git-credential-manager.dll", "args": ["store"], "cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager", "console": "integratedTerminal", "stopAtEntry": false, }, { "name": ".NET Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" } ] } ================================================ FILE: .vscode/tasks.json ================================================ { "version": "2.0.0", "tasks": [ { "label": "build", "command": "dotnet", "type": "process", "group":{ "kind": "build", "isDefault": true }, "args": [ "build", "${workspaceFolder}/Git-Credential-Manager.sln", ], "problemMatcher": "$msCompile" }, { "label": "test", "command": "dotnet", "type": "shell", "group":{ "kind": "test", "isDefault": true }, "args": [ "test", "${workspaceFolder}/Git-Credential-Manager.sln" ], "presentation": { "reveal": "always", "panel": "dedicated" } }, { "label": "test with coverage", "command": "dotnet", "type": "shell", "group": "test", "args": [ "test", "${workspaceFolder}/Git-Credential-Manager.sln", "--collect", "'XPlat Code Coverage'", "--settings", "${workspaceFolder}/.code-coverage/coverlet.settings.xml" ], "presentation": { "reveal": "always", "panel": "dedicated" } }, { "label": "report coverage - nix", "command": "dotnet", "type": "shell", "group": "test", "args": [ "~/.nuget/packages/reportgenerator/*/*/net8.0/ReportGenerator.dll", "-reports:${workspaceFolder}/**/TestResults/**/coverage.cobertura.xml", "-targetdir:${workspaceFolder}/out/code-coverage" ], "presentation": { "reveal": "always", "panel": "dedicated" } }, { "label": "report coverage - win", "command": "dotnet", "type": "shell", "group": "test", "args": [ "${env:USERROFILE}/.nuget/packages/reportgenerator/*/*/net8.0/ReportGenerator.dll", "-reports:${workspaceFolder}/**/TestResults/**/coverage.cobertura.xml", "-targetdir:${workspaceFolder}/out/code-coverage" ], "presentation": { "reveal": "always", "panel": "dedicated" } } ] } ================================================ FILE: CODEOWNERS ================================================ * @git-ecosystem/git-client /src/shared/Microsoft.AzureRepos/ @git-ecosystem/git-client @git-ecosystem/gcm-azure-maintainers /src/shared/Microsoft.AzureRepos.Tests/ @git-ecosystem/git-client @git-ecosystem/gcm-azure-maintainers /src/shared/GitHub/ @git-ecosystem/git-client @git-ecosystem/hubbers /src/shared/GitHub.Tests/ @git-ecosystem/git-client @git-ecosystem/hubbers ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource@github.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][cc-homepage], version 1.4, available at [Contributor Covenant Code of Conduct][cc-coc]. For answers to common questions about this code of conduct, see the [Contributor Covenant FAQ][cc-faq] [cc-coc]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [cc-faq]: https://www.contributor-covenant.org/faq [cc-homepage]: https://www.contributor-covenant.org ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Hi there! We're thrilled that you'd like to contribute to GCM :tada:. Your help is essential for keeping it great. Contributions to GCM are [released][contribute-under-repo-license] to the public under the [project's open source license][license]. Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. ## Start with an issue 1. Open an [issue][issue] to discuss the change you want to see. This helps us coordinate and reduce duplication. 1. Once we've had some discussion, you're ready to code! ## Submitting a pull request 1. [Fork][fork] and clone the repository 1. Configure and install the dependencies: `dotnet restore` 1. Make sure the tests pass on your machine: `dotnet test` 1. Create a new branch: `git switch -c my-branch-name` 1. Make your change, add tests, and make sure the tests still pass 1. For UI updates, test your changes by executing a `dotnet run` in applicable UI-related project directories: - `Atlassian.Bitbucket.UI.Avalonia` - `GitHub.UI.Avalonia` - `Atlassian.Bitbucket.UI.Windows` - `GitHub.UI.Windows` 1. Organize your changes into one or more [logical, descriptive commits][commits]. 1. Push to your fork and [submit a pull request][pr] 1. Pat your self on the back and wait for your pull request to be reviewed and merged. Here are a few things you can do that will increase the likelihood of your pull request being accepted: - Match existing code style. - Write tests. - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. ## Resources - [How to Contribute to Open Source][how-to-contribute] - [Using Pull Requests][prs] - [GitHub Help][github-help] [code-of-conduct]: CODE_OF_CONDUCT.md [commits]: https://www.youtube.com/watch?v=4qLtKx9S9a8 [contribute-under-repo-license]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license [fork]: https://github.com/git-ecosystem/git-credential-manager/fork [github-help]: https://help.github.com [how-to-contribute]: https://opensource.guide/how-to-contribute/ [issue]: https://github.com/git-ecosystem/git-credential-manager/issues/new/choose [license]: LICENSE [pr]: https://github.com/git-ecosystem/git-credential-manager/compare [prs]: https://help.github.com/articles/about-pull-requests/ ================================================ FILE: Directory.Build.props ================================================  windows osx linux true false $(MSBuildThisFileDirectory) $(RepoPath)src\ $(RepoPath)out\ $(RepoPath)assets\ <_IsExeProject Condition="'$(OutputType)' == 'Exe' OR '$(OutputType)' == 'WinExe'">true true 8.0.5 ================================================ FILE: Directory.Build.targets ================================================ $(IntermediateOutputPath)app.manifest ================================================ FILE: Git-Credential-Manager.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29927.169 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A7FC1234-95E3-4496-B5F7-4306F41E6A0E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Git-Credential-Manager", "src\shared\Git-Credential-Manager\Git-Credential-Manager.csproj", "{28F06D44-AB25-4CF5-93F9-978C23FAA9D6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\shared\Core\Core.csproj", "{31BCFC70-B767-4274-873F-1A076D422FC3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Tests", "src\shared\Core.Tests\Core.Tests.csproj", "{AD41FA1E-51F5-4E4F-B7DA-32F921491313}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AzureRepos", "src\shared\Microsoft.AzureRepos\Microsoft.AzureRepos.csproj", "{714AF9EB-44E6-4058-BD3E-9039F29F4D7A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AzureRepos.Tests", "src\shared\Microsoft.AzureRepos.Tests\Microsoft.AzureRepos.Tests.csproj", "{97DC6241-1240-4A85-8035-F8404A983A82}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "windows", "windows", "{66722747-1B61-40E4-A89B-1AC8E6D62EA9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestInfrastructure", "src\shared\TestInfrastructure\TestInfrastructure.csproj", "{5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHub", "src\shared\GitHub\GitHub.csproj", "{3C840B06-A595-4FD9-9A76-56CD45B14780}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{D5277A0E-997E-453A-8CB9-4EFCC8B16A29}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHub.Tests", "src\shared\GitHub.Tests\GitHub.Tests.csproj", "{3E524EA8-D31A-4394-997C-14B522E3D6FD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "osx", "osx", "{3D279E2D-E011-45CF-8EA8-3D71D1300443}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Installer.Mac", "src\osx\Installer.Mac\Installer.Mac.csproj", "{74FA0AA4-B5C1-4F3B-B182-277FC2D50715}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Installer.Windows", "src\windows\Installer.Windows\Installer.Windows.csproj", "{85903170-9E52-4B53-A6E4-3F416F684FAE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atlassian.Bitbucket", "src\shared\Atlassian.Bitbucket\Atlassian.Bitbucket.csproj", "{B49881A6-E734-490E-8EA7-FB0D9E296CFB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atlassian.Bitbucket.Tests", "src\shared\Atlassian.Bitbucket.Tests\Atlassian.Bitbucket.Tests.csproj", "{025E5329-A0B1-4BA9-9203-B70B44A5F9E0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packaging.Linux", "src\linux\Packaging.Linux\Packaging.Linux.csproj", "{AD2A935F-3720-4802-8119-6A9B35B254DF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "linux", "linux", "{8F9D7E67-7DD7-4E32-9134-423281AF00E9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitLab", "src\shared\GitLab\GitLab.csproj", "{570897DC-A85C-4598-B793-9A00CF710119}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitLab.Tests", "src\shared\GitLab.Tests\GitLab.Tests.csproj", "{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU MacDebug|Any CPU = MacDebug|Any CPU MacRelease|Any CPU = MacRelease|Any CPU Release|Any CPU = Release|Any CPU WindowsDebug|Any CPU = WindowsDebug|Any CPU WindowsRelease|Any CPU = WindowsRelease|Any CPU LinuxDebug|Any CPU = LinuxDebug|Any CPU LinuxRelease|Any CPU = LinuxRelease|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.MacRelease|Any CPU.Build.0 = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.Release|Any CPU.Build.0 = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {28F06D44-AB25-4CF5-93F9-978C23FAA9D6}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.MacRelease|Any CPU.Build.0 = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.Release|Any CPU.Build.0 = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {31BCFC70-B767-4274-873F-1A076D422FC3}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.MacRelease|Any CPU.Build.0 = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.Release|Any CPU.Build.0 = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {AD41FA1E-51F5-4E4F-B7DA-32F921491313}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.MacRelease|Any CPU.Build.0 = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.Release|Any CPU.ActiveCfg = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.Release|Any CPU.Build.0 = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {714AF9EB-44E6-4058-BD3E-9039F29F4D7A}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.Debug|Any CPU.Build.0 = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.MacRelease|Any CPU.Build.0 = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.Release|Any CPU.ActiveCfg = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.Release|Any CPU.Build.0 = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {97DC6241-1240-4A85-8035-F8404A983A82}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.MacRelease|Any CPU.Build.0 = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.Release|Any CPU.Build.0 = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.Debug|Any CPU.Build.0 = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.MacRelease|Any CPU.Build.0 = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.Release|Any CPU.ActiveCfg = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.Release|Any CPU.Build.0 = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {3C840B06-A595-4FD9-9A76-56CD45B14780}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.MacRelease|Any CPU.Build.0 = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.Release|Any CPU.Build.0 = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {3E524EA8-D31A-4394-997C-14B522E3D6FD}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.MacRelease|Any CPU.Build.0 = Release|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.Release|Any CPU.ActiveCfg = Release|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {74FA0AA4-B5C1-4F3B-B182-277FC2D50715}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {85903170-9E52-4B53-A6E4-3F416F684FAE}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.Release|Any CPU.Build.0 = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.MacRelease|Any CPU.Build.0 = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {B49881A6-E734-490E-8EA7-FB0D9E296CFB}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.Release|Any CPU.Build.0 = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.MacRelease|Any CPU.Build.0 = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {025E5329-A0B1-4BA9-9203-B70B44A5F9E0}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {AD2A935F-3720-4802-8119-6A9B35B254DF}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.Debug|Any CPU.Build.0 = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.Release|Any CPU.ActiveCfg = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.Release|Any CPU.Build.0 = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.MacRelease|Any CPU.Build.0 = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {570897DC-A85C-4598-B793-9A00CF710119}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacDebug|Any CPU.Build.0 = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Release|Any CPU.ActiveCfg = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Release|Any CPU.Build.0 = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacRelease|Any CPU.Build.0 = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {28F06D44-AB25-4CF5-93F9-978C23FAA9D6} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {31BCFC70-B767-4274-873F-1A076D422FC3} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {AD41FA1E-51F5-4E4F-B7DA-32F921491313} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {714AF9EB-44E6-4058-BD3E-9039F29F4D7A} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {97DC6241-1240-4A85-8035-F8404A983A82} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {66722747-1B61-40E4-A89B-1AC8E6D62EA9} = {A7FC1234-95E3-4496-B5F7-4306F41E6A0E} {5A7D9E8B-C1D2-4C5C-BE98-648C41D1F8BD} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {3C840B06-A595-4FD9-9A76-56CD45B14780} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} = {A7FC1234-95E3-4496-B5F7-4306F41E6A0E} {3E524EA8-D31A-4394-997C-14B522E3D6FD} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {3D279E2D-E011-45CF-8EA8-3D71D1300443} = {A7FC1234-95E3-4496-B5F7-4306F41E6A0E} {74FA0AA4-B5C1-4F3B-B182-277FC2D50715} = {3D279E2D-E011-45CF-8EA8-3D71D1300443} {85903170-9E52-4B53-A6E4-3F416F684FAE} = {66722747-1B61-40E4-A89B-1AC8E6D62EA9} {B49881A6-E734-490E-8EA7-FB0D9E296CFB} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {025E5329-A0B1-4BA9-9203-B70B44A5F9E0} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {8F9D7E67-7DD7-4E32-9134-423281AF00E9} = {A7FC1234-95E3-4496-B5F7-4306F41E6A0E} {AD2A935F-3720-4802-8119-6A9B35B254DF} = {8F9D7E67-7DD7-4E32-9134-423281AF00E9} {570897DC-A85C-4598-B793-9A00CF710119} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} {1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0EF9FC65-E6BA-45D4-A455-262A9EA4366B} EndGlobalSection EndGlobal ================================================ FILE: Git-Credential-Manager.sln.DotSettings ================================================  OS No True True ================================================ FILE: LICENSE ================================================ Git Credential Manager Copyright © GitHub, Inc. and contributors 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 ================================================ FILE: NOTICE ================================================ NOTICES AND INFORMATION Do Not Translate or Localize This repository for Git Credential Manager includes material from the projects listed below. -------------------------------------------------------------------------------- 1. GitHub/VisualStudio (https://github.com/github/VisualStudio) Copyright (c) GitHub Inc. 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. -------------------------------------------------------------------------------- 2. dotnet/runtime (https://github.com/dotnet/runtime) The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. ================================================ FILE: README.md ================================================ # Git Credential Manager [![Build Status][build-status-badge]][workflow-status] --- [Git Credential Manager][gcm] (GCM) is a secure [Git credential helper][git-credential-helper] built on [.NET][dotnet] that runs on Windows, macOS, and Linux. It aims to provide a consistent and secure authentication experience, including multi-factor auth, to every major source control hosting service and platform. GCM supports (in alphabetical order) [Azure DevOps][azure-devops], Azure DevOps Server (formerly Team Foundation Server), Bitbucket, GitHub, and GitLab. Compare to Git's [built-in credential helpers][git-tools-credential-storage] (Windows: wincred, macOS: osxkeychain, Linux: gnome-keyring/libsecret), which provide single-factor authentication support for username/password only. GCM replaces both the .NET Framework-based [Git Credential Manager for Windows][gcm-for-windows] and the Java-based [Git Credential Manager for Mac and Linux][gcm-for-mac-and-linux]. ## Install See the [installation instructions][install] for the current version of GCM for install options for your operating system. ## Current status Git Credential Manager is currently available for Windows, macOS, and Linux\*. GCM only works with HTTP(S) remotes; you can still use Git with SSH: - [Azure DevOps SSH][azure-devops-ssh] - [GitHub SSH][github-ssh] - [Bitbucket SSH][bitbucket-ssh] Feature|Windows|macOS|Linux\* -|:-:|:-:|:-: Installer/uninstaller|✓|✓|✓ Secure platform credential storage [(see more)][gcm-credstores]|✓|✓|✓ Multi-factor authentication support for Azure DevOps|✓|✓|✓ Two-factor authentication support for GitHub|✓|✓|✓ Two-factor authentication support for Bitbucket|✓|✓|✓ Two-factor authentication support for GitLab|✓|✓|✓ Windows Integrated Authentication (NTLM/Kerberos) support|✓|_N/A_|_N/A_ Basic HTTP authentication support|✓|✓|✓ Proxy support|✓|✓|✓ `amd64` support|✓|✓|✓ `x86` support|✓|_N/A_|✗ `arm64` support|best effort|✓|✓ `armhf` support|_N/A_|_N/A_|✓ (\*) GCM guarantees support only for [the Linux distributions that are officially supported by dotnet][dotnet-distributions]. ## Supported Git versions Git Credential Manager tries to be compatible with the broadest set of Git versions (within reason). However there are some known problematic releases of Git that are not compatible. - Git 1.x The initial major version of Git is not supported or tested with GCM. - Git 2.26.2 This version of Git introduced a breaking change with parsing credential configuration that GCM relies on. This issue was fixed in commit [`12294990`][gcm-commit-12294990] of the Git project, and released in Git 2.27.0. ## How to use Once it's installed and configured, Git Credential Manager is called implicitly by Git. You don't have to do anything special, and GCM isn't intended to be called directly by the user. For example, when pushing (`git push`) to [Azure DevOps][azure-devops], [Bitbucket][bitbucket], or [GitHub][github], a window will automatically open and walk you through the sign-in process. (This process will look slightly different for each Git host, and even in some cases, whether you've connected to an on-premises or cloud-hosted Git host.) Later Git commands in the same repository will re-use existing credentials or tokens that GCM has stored for as long as they're valid. Read full command line usage [here][gcm-usage]. ### Configuring a proxy See detailed information [here][gcm-http-proxy]. ## Additional Resources See the [documentation index][docs-index] for links to additional resources. ## Experimental Features - [Windows broker (experimental)][gcm-windows-broker] ## Future features Curious about what's coming next in the GCM project? Take a look at the [project roadmap][roadmap]! You can find more details about the construction of the roadmap and how to interpret it [here][roadmap-announcement]. ## Contributing This project welcomes contributions and suggestions. See the [contributing guide][gcm-contributing] to get started. This project follows [GitHub's Open Source Code of Conduct][gcm-coc]. ## License We're [MIT][gcm-license] licensed. When using GitHub logos, please be sure to follow the [GitHub logo guidelines][github-logos]. [azure-devops]: https://azure.microsoft.com/en-us/products/devops [azure-devops-ssh]: https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops [bitbucket]: https://bitbucket.org [bitbucket-ssh]: https://confluence.atlassian.com/bitbucket/ssh-keys-935365775.html [build-status-badge]: https://github.com/git-ecosystem/git-credential-manager/actions/workflows/continuous-integration.yml/badge.svg [docs-index]: https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/README.md [dotnet]: https://dotnet.microsoft.com [dotnet-distributions]: https://learn.microsoft.com/en-us/dotnet/core/install/linux [git-credential-helper]: https://git-scm.com/docs/gitcredentials [gcm]: https://github.com/git-ecosystem/git-credential-manager [gcm-coc]: CODE_OF_CONDUCT.md [gcm-commit-12294990]: https://github.com/git/git/commit/12294990c90e043862be9eb7eb22c3784b526340 [gcm-contributing]: CONTRIBUTING.md [gcm-credstores]: https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/credstores.md [gcm-for-mac-and-linux]: https://github.com/microsoft/Git-Credential-Manager-for-Mac-and-Linux [gcm-for-windows]: https://github.com/microsoft/Git-Credential-Manager-for-Windows [gcm-http-proxy]: https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/netconfig.md#http-proxy [gcm-license]: LICENSE [gcm-usage]: https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/usage.md [gcm-windows-broker]: https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/windows-broker.md [git-tools-credential-storage]: https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage [github]: https://github.com [github-ssh]: https://help.github.com/en/articles/connecting-to-github-with-ssh [github-logos]: https://github.com/logos [install]: https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/install.md [ms-package-repos]: https://packages.microsoft.com/repos/ [roadmap]: https://github.com/git-ecosystem/git-credential-manager/milestones?direction=desc&sort=due_date&state=open [roadmap-announcement]: https://github.com/git-ecosystem/git-credential-manager/discussions/1203 [workflow-status]: https://github.com/git-ecosystem/git-credential-manager/actions/workflows/continuous-integration.yml ================================================ FILE: SECURITY.md ================================================ Thanks for helping make GitHub safe for everyone. ## Security GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. ## Reporting Security Issues If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** Instead, please send an email to opensource-security[@]github.com. Please include as much of the information listed below as you can to help us better understand and resolve the issue: * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. ## Policy See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor) ================================================ FILE: VERSION ================================================ 2.7.3.0 ================================================ FILE: assets/gcm.xaml ================================================ ================================================ FILE: build/GCM.MSBuild.csproj ================================================ net8.0 false ================================================ FILE: build/GCM.tasks ================================================ <_TaskAssembly>$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll <_TaskFactory>CodeTaskFactory <_TaskAssembly>$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll <_TaskFactory>RoslynCodeTaskFactory ================================================ FILE: build/GenerateWindowsAppManifest.cs ================================================ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System.IO; namespace GitCredentialManager.MSBuild { public class GenerateWindowsAppManifest : Task { [Required] public string Version { get; set; } [Required] public string ApplicationName { get; set; } [Required] public string OutputFile { get; set; } public override bool Execute() { Log.LogMessage(MessageImportance.Normal, "Creating application manifest file for '{0}'...", ApplicationName); string manifestDirectory = Path.GetDirectoryName(OutputFile); if (!Directory.Exists(manifestDirectory)) { Directory.CreateDirectory(manifestDirectory); } File.WriteAllText( OutputFile, $@" "); return true; } } } ================================================ FILE: build/GetVersion.cs ================================================ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System.IO; namespace GitCredentialManager.MSBuild { public class GetVersion : Task { [Required] public string VersionFile { get; set; } [Output] public string Version { get; set; } [Output] public string AssemblyVersion { get; set; } [Output] public string FileVersion { get; set; } public override bool Execute() { Log.LogMessage(MessageImportance.Normal, "Reading VERSION file..."); string textVersion = File.ReadAllText(VersionFile); if (!System.Version.TryParse(textVersion, out System.Version fullVersion)) { Log.LogError("Invalid version '{0}' specified.", textVersion); return false; } // System.Version names its version components as follows: // major.minor[.build[.revision]] // The main version number we use for GCM contains the first three // components. // The assembly and file version numbers contain all components, as // omitting the revision portion from these properties causes // runtime failures on Windows. Version = $"{fullVersion.Major}.{fullVersion.Minor}.{fullVersion.Build}"; AssemblyVersion = FileVersion = fullVersion.ToString(); return true; } } } ================================================ FILE: docs/README.md ================================================ # User documentation The following are links to GCM user support documentation: - [Frequently asked questions][gcm-faq] - [Command-line usage][gcm-usage] - [Configuration options][gcm-config] - [Environment variables][gcm-env] - [Enterprise configuration][gcm-enterprise-config] - [Network and HTTP configuration][gcm-net-config] - [Credential stores][gcm-credstores] - [Host provider specification][gcm-host-provider] - [Azure Repos OAuth tokens][gcm-azure-tokens] - [Azure Managed Identities and Service Principals][gcm-misp] - [GitLab support][gcm-gitlab] - [Generic OAuth support][gcm-oauth] - [NTLM and Kerberos authentication][gcm-ntlm-kerberos] [gcm-azure-tokens]: azrepos-users-and-tokens.md [gcm-config]: configuration.md [gcm-credstores]: credstores.md [gcm-enterprise-config]: enterprise-config.md [gcm-env]: environment.md [gcm-faq]: faq.md [gcm-gitlab]: gitlab.md [gcm-host-provider]: hostprovider.md [gcm-misp]: azrepos-misp.md [gcm-net-config]: netconfig.md [gcm-oauth]: generic-oauth.md [gcm-usage]: usage.md [gcm-ntlm-kerberos]: ntlm-kerberos.md ================================================ FILE: docs/architecture.md ================================================ # Architecture ## Overview ```text +------------------------------------------------------------------------------+ | | | Git-Credential-Manager | | | +-+-------------+--------------+-----+---------------------+-----------------+-+ | | | | | | | | | | Windows | Windows | | | | | | | | +-----------v-----------+ | | +----------------v---------------+ | | | | | | | | | | | GitHub <-------------+ GitHub.UI.Windows | | | | | | | | | | | +-+---------------------+ | | +-+------------------------------+ | | | | | | | | | +---------------------v-+ | | +------------------------------v-+ | | | | | | | | | | | Atlassian.Bitbucket <------------+ Atlassian.Bitbucket.UI.Windows | | | | | | | | | | | +-+---------------------+ | | +---------------+----------------+ | | | | | | | | | +----------------------v-+ | | | | | | | | | | | | | Microsoft.AzureRepos | | | | | | | | | | | | | +-----------+------------+ | | | | | | | | +-v---v----v--------------v------------+ +-v-----------------v----------------+ | | | | | Core <--+ Core.UI | | | | | +--------------------------------------+ +------------------------------------+ ``` Git Credential Manager (GCM) is built to be Git host and platform/OS agnostic. Most of the shared logic (command execution, the abstract platform subsystems, etc) can be found in the `Core` class library (C#). The library targets .NET Standard as well as .NET Framework. > **Note** > > The reason for also targeting .NET Framework directly is that the > `Microsoft.Identity.Client` ([MSAL.NET][msal]) > library requires a .NET Framework target to be able to show the embedded web > browser auth pop-up on Windows platforms. > > There are extension points that now exist in MSAL.NET meaning we can plug-in > our own browser pop-up handling code on .NET meaning both Windows and > Mac. We haven't yet gotten around to exploring this. > > See [GCM issue 113][issue-113] for more information. The entry-point for GCM can be found in the `Git-Credential-Manager` project, a console application that targets both .NET and .NET Framework. This project emits the `git-credential-manager(.exe)` executable, and contains very little code - registration of all supported host providers and running the `Application` object found in `Core`. Providers have their own projects/assemblies that take dependencies on the `Core` core assembly, and are dependents of the main entry point application `Git-Credential-Manager`. Code in these binaries is expected to run on all supported platforms and typically (see MSAL.NET note above) does not include any graphical user interface; they use terminal prompts only. Where a provider needs some platform-specific interaction or graphical user interface, the recommended model is to have a separate 'helper' executable that the shared, core binaries shell out to. Currently the Bitbucket and GitHub providers each have a WPF (Windows only) helper executable that shows authentication prompts and messages. The `Core.UI` project is a WPF (Windows only) assembly that contains common WPF components and styles that are shared between provider helpers on Windows. ### Cross-platform UI We hope to be able to migrate the WPF/Windows only helpers to [Avalonia][avalonia] in order to gain cross-platform graphical user interface support. See [GCM issue 136][issue-136] for up-to-date progress on this effort. ### Microsoft authentication For authentication using Microsoft Accounts or Azure Active Directory, things are a little different. The `MicrosoftAuthentication` component is present in the `Core` core assembly, rather than bundled with a specific host provider. This was done to allow any service that may wish to in the future integrate with Microsoft Accounts or Azure Active Directory can make use of this reusable authentication component. ## Asynchronous programming GCM makes use of the `async`/`await` model of .NET and C# in almost all parts of the codebase where appropriate as usually requests end up going to the network at some point. ## Command execution ```text +---------------+ | | | Git | | | +---+-------^---+ | | +---v---+---+---+ | stdin | stdout| +---+---+---^---+ | | (2) | | (7) Select | | Serialize Command | | Result | | (3) | | Select | | +---------------+ Provider +---v-------+---+ | Host Provider | | | | Registry <------------+ Command | | | | | +-------^-------+ +----+------^---+ | | | | (4) | | (6) | Execute | | Return | Operation | | Result | (1) | | | Register +----v------+---+ | | | +--------------------+ Host Provider | | | +-------^-------+ | (5) Use services | | +-------v-------+ | Command | | Context | +---------------+ ``` Git Credential Manager maintains a set of known commands including `Get|Store|EraseCommand`, as well as commands for install and help/usage. GCM also maintains a set of known, registered host providers that implement the `IHostProvider` interface. Providers register themselves by adding an instance of the provider to the `Application` object via the `RegisterProvider` method in [`Core.Program`][core-program]. The `GenericHostProvider` is registered last so that it can handle all other HTTP-based remotes as a catch-all, and provide basic username/password auth and detect the presence of Windows Integrated Authentication (Kerberos, NTLM, Negotiate) support (1). For each invocation of GCM, the first argument on the command-line is matched against the known commands and if there is a successful match, the input from Git (over standard input) is deserialized and the command is executed (2). The `Get|Store|EraseCommand`s consult the host provider registry for the most appropriate host provider. The default registry implementation select the a host provider by asking each registered provider in turn if they understand the request. The provider selection can be overridden by the user via the [`credential.provider`][credential-provider] or [`GCM_PROVIDER`][gcm-provider] configuration and environment variable respectively (3). The `Get|Store|EraseCommand`s call the corresponding `Get|Store|EraseCredentialAsync` methods on the `IHostProvider`, passing the request from Git together with an instance of the `ICommandContext` (4). The host provider can then make use of various services available on the command context to complete the requested operation (5). Once a credential has been created, retrieved, stored or erased, the host provider returns the credential (for `get` operations only) to the calling command (6). The credential is then serialized and returned to Git over standard output (7) and GCM terminates with a successful exit code. ## Host provider Host providers implement the `IHostProvider` interface. They can choose to directly implement the interface they can also derive from the `HostProvider` abstract class (which itself implements the `IHostProvider` interface). The `HostProvider` abstract class implements the `Get|Store|EraseCredentialAsync` methods and instead has the `GenerateCredentialAsync` abstract method, and the `GetServiceName` virtual method. Calls to `get`, `store`, or `erase` result in first a call to `GetServiceName` which should return a stable and unique value for the provider and request. This value forms part of the attributes associated with any stored credential in the credential store. During a `get` operation the credential store is queried for an existing credential with such service name. If a credential is found it is returned immediately. Similarly, calls to `store` and `erase` are handles automatically to store credentials against, and erase credentials matching the service name. Methods are implemented as `virtual` meaning you can always override this behaviour, for example to clear other custom caches on an `erase` request, without having to reimplement the lookup/store credential logic. The default implementation of `GetServiceName` is usually sufficient for most providers. It returns the computed remote URL (without a trailing slash) from the input arguments from Git - `://[/]` - no username is included even if present. Host providers are queried in turn, by priority (then registration order) via the `IHostProvider.IsSupported(InputArguments)` method and passed the input received from Git. If the provider recognises the request, for example by a matching known host name, they can return `true`. If the provider wants to cancel and abort an authentication request, for example if this is a HTTP (not HTTPS) request for a known host, they should still return `true` and later cancel the request. Host providers can also be queried via the `IHostProvider.IsSupported(HttpResponseMessage)` method and passed the response message from a HEAD call made to the remote URI. This is useful for detecting on-premises instances based on header values. GCM will only query a provider via this method overload if no other provider at the same registration priority has returned `true` to the `InputArguments` overload. Depending on the request from Git, one of `GetCredentialAsync` (for `get` requests), `StoreCredentialAsync` (for `store` requests) or `EraseCredentialAsync` (for `erase` requests) will be called. The argument `InputArguments` contains the request information passed over standard input from Git/the caller; the same as was passed to `IsSupported`. The return value for the `get` operation must be an `ICredential` that Git can use to complete authentication. > **Note:** > > The credential can also be an instance where both username and password are > the empty string, to signal to Git it should let cURL use "any auth" > detection - typically to use Windows Integrated Authentication. There are no return values for the `store` and `erase` operations as Git ignores any output or exit codes for these commands. Failures for these operations are best communicated via writing to the Standard Error stream via `ICommandContext.Streams.Error`. ## Command context The `ICommandContext` which contains numerous services which are useful for interacting with various platform subsystems, such as the file system or environment variables. All services on the command context are exposed as interfaces for ease of testing and portability between different operating systems and platforms. Component|Description -|- CredentialStore|A secure operating system controlled location for storing and retrieving `ICredential` objects. Settings|Abstraction over all GCM settings. Streams|Abstraction over standard input, output and error streams connected to the parent process (typically Git). Terminal|Provides interactions with an attached terminal, if it exists. SessionManager|Provides information about the current user session. Trace|Provides tracing information that may be useful for debugging issues in the wild. Secret information MUST be filtered out completely or via the `Write___Secret` method(s). FileSystem|Abstraction over file system operations. HttpClientFactory|Factory for creating `HttpClient` instances that are configured with the correct user agent, headers, and proxy settings. Git|Provides interactions with Git and Git configuration. Environment|Abstraction over the current system/user environment variables. SystemPrompts|Provides services for showing system/OS native credential prompts. ## Error handling and tracing GCM operates a 'fail fast' approach to unrecoverable errors. This usually means throwing an `Exception` which will propagate up to the entry-point and be caught, a non-zero exit code returned, and the error message printed with the "fatal:" prefix. For errors originating from interop/native code, you should throw an exception of the `InteropException` type. Error messages in exceptions should be human readable. When there is a known or user-fixable issue, instructions on how to self-remedy the issue, or links to relevant documentation should be given. Warnings can be emitted over the standard error stream (`ICommandContext.Streams.Error`) when you want to alert the user to a potential issue with their configuration that does not necessarily stop the operation/authentication. The `ITrace` component can be found on the `ICommandContext` object or passed in directly to some constructors. Verbose and diagnostic information is be written to the trace object in most places of GCM. [avalonia]: https://avaloniaui.net/ [core-program]: ../src/shared/Git-Credential-Manager/Program.cs [credential-provider]: configuration.md#credentialprovider [issue-113]: https://github.com/git-ecosystem/git-credential-manager/issues/113 [issue-136]: https://github.com/git-ecosystem/git-credential-manager/issues/136 [gcm-provider]: environment.md#GCM_PROVIDER [msal]: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet ================================================ FILE: docs/autodetect.md ================================================ # Host provider auto-detection Git Credential Manager (GCM) supports authentication with multiple different Git host providers including: GitHub, Bitbucket, and Azure Repos. As well as the hosted/cloud offerings, GCM can also work with the self-hosted or "on-premises" versions of these services: GitHub Enterprise Server, Bitbucket DC Server, and Azure DevOps Server (TFS). By default, GCM will attempt to automatically detect which particular provider is behind the Git remote URL you're interacting with. For the cloud versions of the supported providers this is done by matching the hostname of the remote URL to the well-known hostnames of the services. For example "github.com" or "dev.azure.com". ## Self-hosted/on-prem detection In order to detect which host provider to use for a self-hosted instance, each provider can provide some heuristic matching of the hostname. For example any hostname that begins "github.*" will be matched to the GitHub host provider. If a heuristic matches incorrectly, you can always [explicitly configure][explicit-config] GCM to use a particular provider. ## Remote URL probing In addition to heuristic matching, GCM will make a network call to the remote URL and inspect HTTP response headers to try and detect a self-hosted instance. This network call is only performed if neither an exact nor fuzzy match by hostname can be made. Only one HTTP `HEAD` call is made per credential request received by Git. To avoid this network call, please [explicitly configure][explicit-config] the host provider for your self-hosted instance. After a successful detection of the host provider, GCM will automatically set the [`credential.provider`][credential-provider] configuration entry for that remote to avoid needing to perform this expensive network call in future requests. ### Timeout You can control how long GCM will wait for a response to the remote network call by setting the [`GCM_AUTODETECT_TIMEOUT`][gcm-autodetect-timeout] environment variable, or the [`credential.autoDetectTimeout`][credential-autoDetectTimeout] Git configuration setting to the maximum number of milliseconds to wait. The default value is 2000 milliseconds (2 seconds). You can prevent the network call altogether by setting a zero or negative value, for example -1. ## Manual configuration If the auto-detection mechanism fails to select the correct host provider, or if the remote probing network call is causing performance issues, you can configure GCM to always use a particular host provider, for a given remote URL. You can either use the the [`GCM_PROVIDER`][gcm-provider] environment variable, or the [`credential.provider`][credential-provider] Git configuration setting for this purpose. For example to tell GCM to always use the GitHub host provider for the "ghe.example.com" hostname, you can run the following command: ```shell git config --global credential.ghe.example.com.provider github ``` [credential-autoDetectTimeout]: configuration.md#credentialautodetecttimeout [credential-provider]: configuration.md#credentialprovider [explicit-config]: #manual-configuration [gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT [gcm-provider]: environment.md#GCM_PROVIDER ================================================ FILE: docs/azrepos-misp.md ================================================ # Azure Managed Identities and Service Principals Git Credential Manager supports Managed Identities and Service Principals for authentication with Azure Repos. This document provides an overview of Managed Identities and Service Principals and how to use them with GCM. ## Managed Identities Azure Managed Identities can be used to authenticate and authorize applications and services to access Azure resources. Managed Identities are a secure way to access Azure resources without needing to store credentials in code or configuration files. There are two types of Managed Identities: **System-assigned** System-assigned Managed Identities are tied to a specific Azure resource, such as a Virtual Machine or App Service. When a system-assigned Managed Identity is enabled, Azure creates an identity for the resource in the Azure AD tenant that's trusted by the subscription. The lifecycle of the identity is tied to the resource to which it's assigned. **User-assigned** User-assigned Managed Identities are created as standalone Azure resources and can be assigned to one or more Azure resources. This allows you to use the same Managed Identity across multiple resources. You can read more about Managed Identities in the [Azure documentation][az-mi]. ### How to configure Managed Identities In order to use a Managed Identity with GCM, you need to ensure that the Managed Identity has the necessary permissions to access the Azure Repos repository. You can read more about how to configure Managed Identities in the [Azure Repos documentation][azdo-misp]. Once you have configured the Managed Identity, you can use it with GCM by simply setting one of the following environment variables or Git configuration options: **Git configuration:** [`credential.azreposManagedIdentity`][gcm-mi-config] **Environment variable:** [`GCM_AZREPOS_MANAGEDIDENTITY`][gcm-mi-env] Value|Description -|- `system`|System-Assigned Managed Identity `[guid]`|User-Assigned Managed Identity with the specified client ID `id://[guid]` **|User-Assigned Managed Identity with the specified client ID `resource://[guid]` **|User-Assigned Managed Identity for the associated resource You can obtain the `[guid]` from the Azure Portal or by using the Azure CLI to inspect the Managed Identity or resource. ** Note there is an open issue that prevents successfull authentication when using these formats: https://github.com/git-ecosystem/git-credential-manager/issues/1570 ## Service Principals Azure Service Principals are used to authenticate and authorize applications and services to access Azure resources. Service Principals are similar in many ways to Managed Identities (in fact Service Principals are used under the hood to implement Managed Identities), but they have expliclty defined credentials that are not managed by Azure. There are a number of different ways to create and configure Service Principals, including using the Azure Portal or Azure CLI. You can read more about Service Principals in the [Azure documentation][az-sp]. ### How to configure Service Principals Much like with Managed Identities, to use a Service Principal with GCM you first need to ensure that the principal has the necessary permissions to access the Azure Repos repository. You can read more about how to configure Service Principals in the [Azure Repos documentation][azdo-misp]. Once you have configured the Service Principal, you can use it with GCM by setting one of the following environment variables or Git configuration options: **Git configuration:** [`credential.azreposServicePrincipal`][gcm-sp-config] **Environment variable:** [`GCM_AZREPOS_SERVICE_PRINCIPAL`][gcm-sp-env] The format of the value for these options must be in the format: ```text {tenantId}/{clientId} ``` Where `{tenantId}` is the Azure tenant ID and `{clientId}` is the client ID of the Service Principal. These values can be found in the Azure Portal or by using the Azure CLI to inspect the Service Principal. #### Authentication with Service Principals When using a Service Principal with GCM, you will also need to provide the client secret or certificate that is associated with the Service Principal. You can provide the client secret or certificate to GCM by setting one of the following environment variables or Git configuration options. Type|Git Configuration|Environment Variable -|-|- Client Secret|[`credential.azreposServicePrincipalSecret`][gcm-sp-secret-config]|[`GCM_AZREPOS_SP_SECRET`][gcm-sp-secret-env] Certificate|[`credential.azreposServicePrincipalCertificateThumbprint`][gcm-sp-cert-config]|[`GCM_AZREPOS_SP_CERT_THUMBPRINT`][gcm-sp-cert-env] Send X5C|[`credential.azreposServicePrincipalCertificateSendX5C`][gcm-sp-cert-x5c-config]|[`GCM_AZREPOS_SP_CERT_SEND_X5C`][gcm-sp-cert-x5c-env] The value for these options should be the client secret or the thumbrint of the certificate that is associated with the Service Principal. The certificate itself should be installed on the machine where GCM is running and should be installed in personal store the certificate store for either the current user or the local machine. [az-mi]: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview [az-sp]: https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser [azdo-misp]: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops [gcm-mi-config]: https://gh.io/gcm/config#credentialazreposmanagedidentity [gcm-mi-env]: https://gh.io/gcm/env#GCM_AZREPOS_MANAGEDIDENTITY [gcm-sp-config]: https://gh.io/gcm/config#credentialazreposserviceprincipal [gcm-sp-env]: https://gh.io/gcm/env#GCM_AZREPOS_SERVICE_PRINCIPAL [gcm-sp-secret-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalsecret [gcm-sp-secret-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_SECRET [gcm-sp-cert-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalcertificatethumbprint [gcm-sp-cert-x5c-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalcertificatesendx5c [gcm-sp-cert-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_CERT_THUMBPRINT [gcm-sp-cert-x5c-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_CERT_SEND_X5C ================================================ FILE: docs/azrepos-users-and-tokens.md ================================================ # Azure Repos: Access tokens and Accounts ## Different credential types The Azure Repos host provider supports creating multiple types of credential: - Azure DevOps personal access tokens - Microsoft identity OAuth tokens To select which type of credential the Azure Repos host provider will create and use, you can set the [`credential.azreposCredentialType`][credential-azreposCredentialType] configuration entry (or [`GCM_AZREPOS_CREDENTIALTYPE`][gcm-azrepos-credential-type] environment variable). ### Azure DevOps personal access tokens Historically, the only option supported by the Azure Repos host provider was Azure DevOps Personal Access Tokens (PATs). These PATs are only used by Azure DevOps, and must be [managed through the Azure DevOps user settings page][azure-devops-pats] or [REST API][azure-devops-api]. PATs have a limited lifetime and new tokens must be created once they expire. In Git Credential Manager, when a PAT expired (or was manually revoked) this resulted in a new authentication prompt. ### Microsoft identity OAuth tokens "Microsoft identity OAuth token" is the generic term for OAuth-based access tokens issued by Azure Active Directory for either Work and School Accounts (AAD tokens) or Personal Accounts (Microsoft Account/MSA tokens). Azure DevOps supports Git authentication using Microsoft identity OAuth tokens as well as PATs. Microsoft identity OAuth tokens created by Git Credential Manager are scoped to Azure DevOps only. Unlike PATs, Microsoft identity OAuth tokens get automatically refreshed and renewed as long as you are actively using them to perform Git operations. These tokens are also securely shared with other Microsoft developer tools including the Visual Studio IDE and Azure CLI. This means that as long as you're using Git or one of these tools with the same account, you'll never need to re-authenticate due to expired tokens! #### User accounts In versions of Git Credential Manager that support Microsoft identity OAuth tokens, the user account used to authenticate for a particular Azure DevOps organization will now be remembered. The first time you clone, fetch or push from/to an Azure DevOps organization you will be prompted to sign-in and select a user account. Git Credential Manager will remember which account you used and continue to use that for all future remote Git operations (clone/fetch/push). An account is said to be "bound" to an Azure DevOps organization. --- **Note:** If GCM is set to use PAT credentials, this account will **NOT** be used and you will continue to be prompted to select a user account to renew the credential. This may change in the future. --- Normally you won't need to worry about managing which user accounts Git Credential Manager is using as this is configured automatically when you first authenticate for a particular Azure DevOps organization. In advanced scenarios (such as using multiple accounts) you can interact with and manage remembered user accounts using the 'azure-repos' provider command: ```shell git-credential-manager azure-repos [ list | bind | unbind | ... ] ``` ##### Listing remembered accounts You can list all bound user accounts by Git Credential Manager for each Azure DevOps organization using the `list` command: ```shell $ git-credential-manager azure-repos list contoso: (global) -> alice@contoso.com fabrikam: (global) -> user42@fabrikam.com ``` In the above example, the `contoso` Azure DevOps organization is associated with the `alice@contoso.com` user account, while the `fabrikam` organization is associated to the `user42@fabrikam.com` user account. Global "bindings" apply to all remote Git operations for the current computer user profile and are stored in `~/.gitconfig` or `%USERPROFILE%\.gitconfig`. ##### Using different accounts within a repository If you generally use one account for an Azure DevOps organization, the default global bindings will be sufficient. However, if you wish to use a different user account for an organization in a particular repository you can use a local binding. Local account bindings only apply within a single repository and are stored in the `.git/config` file. If there are local bindings in a repository you can show them with the `list` command: ```shell ~/myrepo$ git-credential-manager azure-repos list contoso: (global) -> alice@contoso.com (local) -> alice-alt@contoso.com ``` Within the `~/myrepo` repository, the `alice-alt@contoso.com` account will be used by Git and GCM for the `contoso` Azure DevOps organization. To create a local binding, use the `bind` command with the `--local` option when inside a repository: ```shell ~/myrepo$ git-credential-manager azure-repos bind --local contoso alice-alt@contso.com ``` ```diff contoso: (global) -> alice@contoso.com + (local) -> alice-alt@contoso.com ``` ##### Forget an account To have Git Credential Manager forget a user account, use the `unbind` command: ```shell git-credential-manager azure-repos unbind fabrikam ``` ```diff contoso: (global) -> alice@contoso.com - fabrikam: - (global) -> user42@fabrikam.com ``` In the above example, and global account binding for the `fabrikam` organization will be forgotten. The next time you need to renew a PAT (if using PATs) or perform any remote Git operation (is using Azure tokens) you will be prompted to authenticate again. To forget or remove a local binding, within the repository run the `unbind` command with the `--local` option: ```shell ~/myrepo$ git-credential-manager azure-repos unbind --local contoso ``` ```diff contoso: (global) -> alice@contoso.com - (local) -> alice-alt@contoso.com ``` ##### Using different accounts for specific Git remotes As well as global and local user account bindings, you can instruct Git Credential Manager to use a specific user account for an individual Git remotes within the same local repository. To show which accounts are being used for each Git remote in a repository use the `list` command with the `--show-remotes` option: ```shell ~/myrepo$ git-credential-manager azure-repos list --show-remotes contoso: (global) -> alice@contoso.com origin: (fetch) -> (inherit) (push) -> (inherit) fabrikam: (global) -> alice@fabrikam.com ``` In the above example, the `~/myrepo` repository has a single Git remote named `origin` that points to the `contoso` Azure DevOps organization. There is no user account specifically associated with the `origin` remote, so the global user account binding for `contoso` will be used (the global binding is inherited). To associate a user account with a particular Git remote you must manually edit the remote URL using `git config` commands to include the username in the [user information][rfc3986-s321] part of the URL. ```shell git config --local remote.origin.url https://alice-alt%40contoso.com@contoso.visualstudio.com/project/_git/repo ``` In the above example the `alice-alt@contoso.com` account is being set as the account to use for the `origin` Git remote. --- **Note:** All special characters must be URL encoded/escaped, for example `@` becomes `%40`. --- The `list --show-remotes` command will show the user account specified in the remote URL: ```shell ~/myrepo$ git-credential-manager azure-repos list --show-remotes contoso: (global) -> alice@contoso.com origin: (fetch) -> alice-alt@contoso.com (push) -> alice-alt@contoso.com fabrikam: (global) -> alice@fabrikam.com ``` [azure-devops-pats]: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page [credential-azreposCredentialType]: configuration.md#credentialazreposcredentialtype [gcm-azrepos-credential-type]: environment.md#GCM_AZREPOS_CREDENTIALTYPE [azure-devops-api]: https://docs.microsoft.com/en-gb/rest/api/azure/devops/tokens/pats [rfc3986-s321]: https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1 ================================================ FILE: docs/bitbucket-authentication.md ================================================ # Bitbucket Authentication When GCM is triggered by Git, it will check the `host` parameter passed to it. If this parameter contains `bitbucket.org` it will trigger Bitbucket authentication and prompt you for credentials. In this scenario, you have two options for authentication: `OAuth` or `Password/Token`. ### OAuth The dialog GCM presents for authentication contains two tabs. The first tab (labeled `Browser`) will trigger OAuth Authentication. Clicking the `Sign in with your browser` button opens a browser request to `_https://bitbucket.org/site/oauth2/authorize?response_type=code&client_id={consumerkey}&state=authenticated&scope={scopes}&redirect_uri=http://localhost:34106/_`. This triggers a flow on Bitbucket requiring you to log in (and potentially complete 2FA) to authorize GCM to access Bitbucket with the specified scopes. GCM will then spawn a temporary local webserver, listening on port 34106, to handle the OAuth redirect/callback. Assuming you successfully log into Bitbucket and authorize GCM, this callback will include the appropriate tokens for GCM to handle authencation. These tokens are then stored in your configured [credential store][credstores] and are returned to Git. ### Password/Token **Note:** Bitbucket Data Center, also known as Bitbucket Server or Bitbucket On Premises, only supports Basic Authentication - please follow the below instructions if you are using this product. The dialog GCM presents for authentication contains two tabs. The second tab (labeled `Password/Token`) will trigger Basic Authentication. This tab contains two fields, one for your username and one for your password or token. If the `username` parameter was passed into GCM, that will pre-populate the username field, although it can be overridden. Enter your username (if needed) and your password or token (i.e. Bitbucket App Password) and click `Sign in`. :rotating_light: Requirements for App Passwords :rotating_light: If you are planning to use an [App Password][app-password] for basic authentication, it must at a minimum have _Account Read_ permissions (as shown below). If your App Password does not have these permissions, you will be re-prompted for credentials on every interaction with the server. ![][app-password-example] When your username and password are submitted, GCM will attempt to retrieve a basic authentication token for these credentials via the Bitbucket REST API. If this is successful, the credentials, username, and password/token are stored in your configured [credential store][credstores] and are returned to Git. If the API request fails with a 401 return code, the entered username/password combination is invalid; nothing is stored and nothing is returned to Git. In this scenario, re-attempt authentication, ensuring your credentials are correct. If the API request fails with a 403 (Forbidden) return code, the username and password are valid, but 2FA is enabled on the corresponding Bitbucket Account. In this scenario, you will be prompted to complete the OAuth authentication process. If this is successful, the credentials, username, and password/token are stored in your configured [credential store][credstores] and are returned to Git. [app-password]: https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/ [app-password-example]: img/app-password.png [credstores]: ./credstores.md ================================================ FILE: docs/bitbucket-development.md ================================================ # Bitbucket Authentication, 2FA and OAuth By default for authenticating against private Git repositories Bitbucket supports SSH and username/password Basic Auth over HTTPS. Username/password Basic Auth over HTTPS is also available for REST API access. Additionally Bitbucket supports App-specific passwords which can be used via Basic Auth as username/app-specific-password. To enhance security Bitbucket offers optional Two-Factor Authentication (2FA). When 2FA is enabled username/password Basic Auth access to the REST APIs and to Git repositories is suspended. At that point users are left with the choice of username/apps-specific-password Basic Auth for REST APIs and Git interactions, OAuth for REST APIs and Git/Hg interactions or SSH for Git/HG interactions and one of the previous choices for REST APIs. SSH and REST API access are beyond the scope of this document. Read about [Bitbucket's 2FA implementation][2fa-impl]. App-specific passwords are not particularly user friendly as once created Bitbucket hides their value, even from the owner. They are intended for use within application that talk to Bitbucket where application can remember and use the app-specific-password. [Additional information][additional-info]. OAuth is the intended authentication method for user interactions with HTTPS remote URL for Git repositories when 2FA is active. Essentially once a client application has an OAuth access token it can be used in place of a user's password. Read more about information [Bitbucket's OAuth implementation][oauth-impl]. Bitbucket's OAuth implementation follows the standard specifications for OAuth 2.0, which is out of scope for this document. However it implements a comparatively rare part of OAuth 2.0 Refresh Tokens. Bitbucket's Access Token's expire after 1 hour if not revoked, as opposed to GitHub's that expire after 1 year. When GitHub's Access Tokens expire the user must anticipate in the standard OAuth authentication flow to get a new Access Token. Since this occurs, in theory, once per year this is not too onerous. Since Bitbucket's Access Tokens expire every hour it is too much to expect a user to go through the OAuth authentication flow every hour.Bitbucket implements refresh Tokens. Refresh Tokens are issued to the client application at the same time as Access Tokens. They can only be used to request a new Access Token, and then only if they have not been revoked. As such the support for Bitbucket and the use of its OAuth in the Git Credentials Manager differs significantly from how VSTS and GitHub are implemented. This is explained in more detail below. ## Multiple User Accounts Unlike the GitHub implementation within the Git Credential Manager, the Bitbucket implementation stores 'secrets', passwords, app-specific passwords, or OAuth tokens, with usernames in the [Windows Credential Manager][wincred-manager] vault. Depending on the circumstances this means either saving an explicit username in to the Windows Credential Manager/Vault or including the username in the URL used as the identifying key of entries in the Windows Credential Manager vault, i.e. using a key such as `git:https://mminns@bitbucket.org/` rather than `git:https://bitbucket.org`. This means that the Bitbucket implementation in the GCM can support multiple accounts, and usernames, for a single user against Bitbucket, e.g. a personal account and a work account. ## On-Premise Bitbucket On-premise Bitbucket, more correctly known as Bitbucket Server or Bitbucket DC, has a number of differences compared to the cloud instance of Bitbucket, [bitbucket.org][bitbucket]. It is possible to test with Bitbucket Server by running it locally using the following command from the Atlassian SDK: ❯ atlas-run-standalone --product bitbucket See the developer documentation for [atlas-run-standalone][atlas-run-standalone]. This will download and run a standalone instance of Bitbucket Server which can be accessed using the credentials `admin`/`admin` at https://localhost:7990/bitbucket Atlassian has [documentation][atlassian-sdk] on how to download and install their SDK. ## OAuth2 Configuration Bitbucket DC [7.20](https://confluence.atlassian.com/bitbucketserver/bitbucket-data-center-and-server-7-20-release-notes-1101934428.html) added support for OAuth2 Incoming Application Links and this can be used to support OAuth2 authentication for Git. This is especially useful in environments where Bitbucket uses SSO as it removes the requirement for users to manage [SSH keys](https://confluence.atlassian.com/display/BITBUCKETSERVER0717/Using+SSH+keys+to+secure+Git+operations) or manual [HTTP access tokens](https://confluence.atlassian.com/display/BITBUCKETSERVER0717/Personal+access+tokens). ### Host Configuration For more details see [Bitbucket's documentation on Data Center and Server Application Links to other Applications](https://confluence.atlassian.com/bitbucketserver/link-to-other-applications-1018764620.html) Create Incoming OAuth 2 Application Link: 1. Navigate to Administration/Application Links 1. Create Link 1. Screen 1 - External Application [check] - Incoming Application [check] 1. Screen 2 - Name : GCM - Redirect URL : `http://localhost:34106/` - Application Permissions : Repositories.Read [check], Repositories.Write [check] 1. Save 1. Copy the `ClientId` and `ClientSecret` to configure GCM ### Client Configuration Set the OAuth2 configuration use the `ClientId` and `ClientSecret` copied above, (for details see [credential.bitbucketDataCenterOAuthClientId](configuration.md#credential.bitbucketDataCenterOAuthClientId) and [credential.bitbucketDataCenterOAuthClientSecret](configuration.md#credential.bitbucketDataCenterOAuthClientSecret)) ❯ git config --global credential.bitbucketDataCenterOAuthClientId {`Copied ClientId`} ❯ git config --global credential.bitbucketDataCenterOAuthClientSecret {`Copied ClientSecret`} As described in [Configuration options](configuration.md#Configuration%20options) the settings can be made more specific to apply only to a specific Bitbucket DC host by specifying the host url, e.g. https://bitbucket.example.com/ ❯ git config --global credential.https://bitbucket.example.com.bitbucketDataCenterOAuthClientId {`Copied ClientId`} ❯ git config --global credential.https://bitbucket.example.com.bitbucketDataCenterOAuthClientSecret {`Copied ClientSecret`} Due to the way GCM resolves hosts and determines REST API urls, if the Bitbucket DC instance is hosted under a relative url (e.g. https://example.com/bitbucket) it is necessary to configure Git to send the full path to GCM. This is done using the [credential.useHttpPath](configuration.md#credential.useHttpPath) setting. ❯ git config --global credential.https://example.com/bitbucket.usehttppath true If a port number is used in the url of the Bitbucket DC instance the Git configuration needs to reflect this. However, due to [Issue 608](https://github.com/git-ecosystem/git-credential-manager/issues/608) the port is ignored when resolving [credential.bitbucketDataCenterOAuthClientId](configuration.md#credential.bitbucketDataCenterOAuthClientId) and [credential.bitbucketDataCenterOAuthClientSecret](configuration.md#credential.bitbucketDataCenterOAuthClientSecret). For example, a Bitbucket DC host at https://example.com:7990/bitbucket would require configuration in the form: ❯ git config --global credential.https://example.com/bitbucket.bitbucketDataCenterOAuthClientId {`Copied ClientId`} ❯ git config --global credential.https://example.com/bitbucket.bitbucketDataCenterOAuthClientSecret {`Copied ClientSecret`} ❯ git config --global credential.https://example.com:7990/bitbucket.usehttppath true [additional-info]:https://confluence.atlassian.com/display/BITBUCKET/App+passwords [atlas-run-standalone]: https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-run-standalone/ [bitbucket]: https://bitbucket.org [2fa-impl]: https://confluence.atlassian.com/bitbucket/two-step-verification-777023203.html [oauth-impl]: https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html [atlassian-sdk]: https://developer.atlassian.com/server/framework/atlassian-sdk/ [wincred-manager]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374792(v=vs.85).aspx ================================================ FILE: docs/configuration.md ================================================ # Configuration options [Git Credential Manager][usage] works out of the box for most users. Git Credential Manager (GCM) can be configured using Git's configuration files, and follows all of the same rules Git does when consuming the files. Global configuration settings override system configuration settings, and local configuration settings override global settings; and because the configuration details exist within Git's configuration files you can use Git's `git config` utility to set, unset, and alter the setting values. All of GCM's configuration settings begin with the term `credential`. GCM honors several levels of settings, in addition to the standard local \> global \> system tiering Git uses. URL-specific settings or overrides can be applied to any value in the `credential` namespace with the syntax below. Additionally, GCM respects several GCM-specific [environment variables][envars] **which take precedence over configuration options**. System administrators may also configure [default values][enterprise-config] for many settings used by GCM. GCM will only be used by Git if it is installed and configured. Use `git config --global credential.helper manager` to assign GCM as your credential helper. Use `git config credential.helper` to see the current configuration. **Example:** > `credential.microsoft.visualstudio.com.namespace` is more specific than > `credential.visualstudio.com.namespace`, which is more specific than > `credential.namespace`. In the examples above, the `credential.namespace` setting would affect any remote repository; the `credential.visualstudio.com.namespace` would affect any remote repository in the domain, and/or any subdomain (including `www.`) of, 'visualstudio.com'; where as the `credential.microsoft.visualstudio.com.namespace` setting would only be applied to remote repositories hosted at 'microsoft.visualstudio.com'. For the complete list of settings GCM understands, see the list below. ## Available settings ### credential.interactive Permit or disable GCM from interacting with the user (showing GUI or TTY prompts). If interaction is required but has been disabled, an error is returned. This can be helpful when using GCM in headless and unattended environments, such as build servers, where it would be preferable to fail than to hang indefinitely waiting for a non-existent user. To disable interactivity set this to `false` or `0`. #### Compatibility In previous versions of GCM this setting had a different behavior and accepted other values. The following table summarizes the change in behavior and the mapping of older values such as `never`: Value(s)|Old meaning|New meaning -|-|- `auto`|Prompt if required – use cached credentials if possible|_(unchanged)_ `never`, `false`| Never prompt – fail if interaction is required|_(unchanged)_ `always`, `force`, `true`|Always prompt – don't use cached credentials|Prompt if required (same as the old `auto` value) #### Example ```shell git config --global credential.interactive false ``` Defaults to enabled. **Also see: [GCM_INTERACTIVE][gcm-interactive]** --- ### credential.trace Enables trace logging of all activities. Configuring Git and GCM to trace to the same location is often desirable, and GCM is compatible and cooperative with `GIT_TRACE`. #### Example ```shell git config --global credential.trace /tmp/git.log ``` If the value of `credential.trace` is a full path to a file in an existing directory, logs are appended to the file. If the value of `credential.trace` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [GCM_TRACE][gcm-trace]** --- ### credential.traceSecrets Enables tracing of secret and sensitive information, which is by default masked in trace output. Requires that `credential.trace` is also enabled. #### Example ```shell git config --global credential.traceSecrets true ``` If the value of `credential.traceSecrets` is `true` or `1`, trace logs will include secret information. Defaults to disabled. **Also see: [GCM_TRACE_SECRETS][gcm-trace-secrets]** --- ### credential.traceMsAuth Enables inclusion of Microsoft Authentication library (MSAL) logs in GCM trace output. Requires that `credential.trace` is also enabled. #### Example ```shell git config --global credential.traceMsAuth true ``` If the value of `credential.traceMsAuth` is `true` or `1`, trace logs will include verbose MSAL logs. Defaults to disabled. **Also see: [GCM_TRACE_MSAUTH][gcm-trace-msauth]** --- ### credential.debug Pauses execution of GCM at launch to wait for a debugger to be attached. #### Example ```shell git config --global credential.debug true ``` Defaults to disabled. **Also see: [GCM_DEBUG][gcm-debug]** --- ### credential.provider Define the host provider to use when authenticating. ID|Provider -|- `auto` _(default)_|_\[automatic\]_ ([learn more][autodetect]) `azure-repos`|Azure Repos `github`|GitHub `bitbucket`|Bitbucket `gitlab`|GitLab _(supports OAuth in browser, personal access token and Basic Authentication)_ `generic`|Generic (any other provider not listed above) Automatic provider selection is based on the remote URL. This setting is typically used with a scoped URL to map a particular set of remote URLs to providers, for example to mark a host as a GitHub Enterprise instance. #### Example ```shell git config --global credential.ghe.contoso.com.provider github ``` **Also see: [GCM_PROVIDER][gcm-provider]** --- ### credential.authority _(deprecated)_ > This setting is deprecated and should be replaced by `credential.provider` > with the corresponding provider ID value. > > See the [migration guide][provider-migrate] for more information. Select the host provider to use when authenticating by which authority is supported by the providers. Authority|Provider(s) -|- `auto` _(default)_|_\[automatic\]_ `msa`, `microsoft`, `microsoftaccount`, `aad`, `azure`, `azuredirectory`, `live`, `liveconnect`, `liveid`|Azure Repos _(supports Microsoft Authentication)_ `github`|GitHub _(supports GitHub Authentication)_ `bitbucket`|Bitbucket.org _(supports Basic Authentication and OAuth)_, Bitbucket Server _(supports Basic Authentication)_ `gitlab`|GitLab _(supports OAuth in browser, personal access token and Basic Authentication)_ `basic`, `integrated`, `windows`, `kerberos`, `ntlm`, `tfs`, `sso`|Generic _(supports Basic and Windows Integrated Authentication)_ #### Example ```shell git config --global credential.ghe.contoso.com.authority github ``` **Also see: [GCM_AUTHORITY][gcm-authority]** --- ### credential.guiPrompt Permit or disable GCM from presenting GUI prompts. If an equivalent terminal/ text-based prompt is available, that will be shown instead. To disable all interactivity see [credential.interactive][credential-interactive]. #### Example ```shell git config --global credential.guiPrompt false ``` Defaults to enabled. **Also see: [GCM_GUI_PROMPT][gcm-gui-prompt]** --- ### credential.guiSoftwareRendering Force the use of software rendering for GUI prompts. This is currently only applicable on Windows. #### Example ```shell git config --global credential.guiSoftwareRendering true ``` Defaults to false (use hardware acceleration where available). > [!NOTE] > Windows on ARM devices defaults to using software rendering to work around a > known Avalonia issue: **Also see: [GCM_GUI_SOFTWARE_RENDERING][gcm-gui-software-rendering]** --- ### credential.allowUnsafeRemotes Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP URLs. This setting is not recommended for general use and should only be used when necessary. Defaults false (disallow unsafe remote URLs). #### Example ```shell git config --global credential.allowUnsafeRemotes true ``` **Also see: [GCM_ALLOW_UNSAFE_REMOTES][gcm-allow-unsafe-remotes]** --- ### credential.autoDetectTimeout Set the maximum length of time, in milliseconds, that GCM should wait for a network response during host provider auto-detection probing. See [auto-detection][auto-detection] for more information. **Note:** Use a negative or zero value to disable probing altogether. Defaults to 2000 milliseconds (2 seconds). #### Example ```shell git config --global credential.autoDetectTimeout -1 ``` **Also see: [GCM_AUTODETECT_TIMEOUT][gcm-autodetect-timeout]** --- ### credential.allowWindowsAuth Allow detection of Windows Integrated Authentication (WIA) support for generic host providers. Setting this value to `false` will prevent the use of WIA and force a basic authentication prompt when using the Generic host provider. **Note:** WIA is only supported on Windows. **Note:** WIA is an umbrella term for NTLM and Kerberos (and Negotiate). Value|WIA detection -|- `true` _(default)_|Permitted `false`|Not permitted #### Example ```shell git config --global credential.tfsonprem123.allowWindowsAuth false ``` **Also see: [GCM_ALLOW_WINDOWSAUTH][gcm-allow-windowsauth]** --- ### credential.httpProxy _(deprecated)_ > This setting is deprecated and should be replaced by the > [standard `http.proxy` Git configuration option][git-config-http-proxy]. > > See [HTTP Proxy][http-proxy] for more information. Configure GCM to use the a proxy for network operations. **Note:** Git itself does _not_ respect this setting; this affects GCM _only_. #### Example ```shell git config --global credential.httpsProxy http://john.doe:password@proxy.contoso.com ``` **Also see: [GCM_HTTP_PROXY][gcm-http-proxy]** --- ### credential.bitbucketAuthModes Override the available authentication modes presented during Bitbucket authentication. If this option is not set, then the available authentication modes will be automatically detected. **Note:** This setting only applies to Bitbucket.org, and not Server or DC instances. **Note:** This setting supports multiple values separated by commas. Value|Authentication Mode -|- _(unset)_|Automatically detect modes `oauth`|OAuth-based authentication `basic`|Basic/PAT-based authentication #### Example ```shell git config --global credential.bitbucketAuthModes "oauth,basic" ``` **Also see: [GCM_BITBUCKET_AUTHMODES][gcm-bitbucket-authmodes]** --- ### credential.bitbucketAlwaysRefreshCredentials Forces GCM to ignore any existing stored Basic Auth or OAuth access tokens and always run through the process to refresh the credentials before returning them to Git. This is especially relevant to OAuth credentials. Bitbucket.org access tokens expire after 2 hours, after that the refresh token must be used to get a new access token. Enabling this option will improve performance when using Oauth2 and interacting with Bitbucket.org if, on average, commits are done less frequently than every 2 hours. Enabling this option will decrease performance when using Basic Auth by requiring the user the re-enter credentials every time. Value|Refresh Credentials Before Returning -|- `true`, `1`, `yes`, `on` |Always `false`, `0`, `no`, `off`_(default)_|Only when the credentials are found to be invalid #### Example ```shell git config --global credential.bitbucketAlwaysRefreshCredentials true ``` Defaults to false/disabled. **Also see: [GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS][gcm-bitbucket-always-refresh-credentials]** --- ### credential.bitbucketValidateStoredCredentials Forces GCM to validate any stored credentials before returning them to Git. It does this by calling a REST API resource that requires authentication. Disabling this option reduces the HTTP traffic within GCM when it is retrieving credentials. This may improve user performance, but will increase the number of times Git remote calls fail to authenticate with the host and therefore require the user to re-try the Git remote call. Enabling this option helps ensure Git is always provided with valid credentials. Value|Validate credentials -|- `true`, `1`, `yes`, `on`_(default)_|Always `false`, `0`, `no`, `off`|Never #### Example ```shell git config --global credential.bitbucketValidateStoredCredentials true ``` Defaults to true/enabled. **Also see: [GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS](environment.md#GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS)** --- ### credential.bitbucketDataCenterOAuthClientId To use OAuth with Bitbucket DC it is necessary to create an external, incoming [AppLink](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html). It is then necessary to configure the local GCM installation with both the OAuth [ClientId](configuration.md#credential.bitbucketDataCenterOAuthClientId) and [ClientSecret](configuration.md#credential.bitbucketDataCenterOauthSecret) from the AppLink. #### Example ```shell git config --global credential.bitbucketDataCenterOAuthClientId 1111111111111111111 ``` Defaults to undefined. **Also see: [GCM_BITBUCKET_DATACENTER_CLIENTID](environment.md#GCM_BITBUCKET_DATACENTER_CLIENTID)** --- ### credential.bitbucketDataCenterOAuthClientSecret To use OAuth with Bitbucket DC it is necessary to create an external, incoming [AppLink](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html). It is then necessary to configure the local GCM installation with both the OAuth [ClientId](configuration.md#credential.bitbucketDataCenterOAuthClientId) and [ClientSecret](configuration.md#credential.bitbucketDataCenterOauthSecret) from the AppLink. #### Example ```shell git config --global credential.bitbucketDataCenterOAuthClientSecret 222222222222222222222 ``` Defaults to undefined. **Also see: [GCM_BITBUCKET_DATACENTER_CLIENTSECRET](environment.md#GCM_BITBUCKET_DATACENTER_CLIENTSECRET)** --- ### credential.gitHubAccountFiltering Enable or disable automatic account filtering for GitHub based on server hints when there are multiple available accounts. This setting is only applicable to GitHub.com with [Enterprise Managed Users][github-emu]. Value|Description -|- `true` _(default)_|Filter available accounts based on server hints. `false`|Show all available accounts. #### Example ```shell git config --global credential.gitHubAccountFiltering "false" ``` **Also see: [GCM_GITHUB_ACCOUNTFILTERING][gcm-github-accountfiltering]** --- ### credential.gitHubAuthModes Override the available authentication modes presented during GitHub authentication. If this option is not set, then the available authentication modes will be automatically detected. **Note:** This setting supports multiple values separated by commas. Value|Authentication Mode -|- _(unset)_|Automatically detect modes `oauth`|Expands to: `browser, device` `browser`|OAuth authentication via a web browser _(requires a GUI)_ `device`|OAuth authentication with a device code `basic`|Basic authentication using username and password `pat`|Personal Access Token (pat)-based authentication #### Example ```shell git config --global credential.gitHubAuthModes "oauth,basic" ``` **Also see: [GCM_GITHUB_AUTHMODES][gcm-github-authmodes]** --- ### credential.gitLabAuthModes Override the available authentication modes presented during GitLab authentication. If this option is not set, then the available authentication modes will be automatically detected. **Note:** This setting supports multiple values separated by commas. Value|Authentication Mode -|- _(unset)_|Automatically detect modes `browser`|OAuth authentication via a web browser _(requires a GUI)_ `basic`|Basic authentication using username and password `pat`|Personal Access Token (pat)-based authentication #### Example ```shell git config --global credential.gitLabAuthModes "browser" ``` **Also see: [GCM_GITLAB_AUTHMODES][gcm-gitlab-authmodes]** --- ### credential.namespace Use a custom namespace prefix for credentials read and written in the OS credential store. Credentials will be stored in the format `{namespace}:{service}`. Defaults to the value `git`. #### Example ```shell git config --global credential.namespace "my-namespace" ``` **Also see: [GCM_NAMESPACE][gcm-namespace]** --- ### credential.credentialStore Select the type of credential store to use on supported platforms. Default value on Windows is `wincredman`, on macOS is `keychain`, and is unset on Linux. **Note:** See more information about configuring secret stores in [cred-stores][cred-stores]. Value|Credential Store|Platforms -|-|- _(unset)_|Windows: `wincredman`, macOS: `keychain`, Linux: _(none)_|- `wincredman`|Windows Credential Manager (not available over SSH).|Windows `dpapi`|DPAPI protected files. Customize the DPAPI store location with [credential.dpapiStorePath][credential-dpapistorepath]|Windows `keychain`|macOS Keychain.|macOS `secretservice`|[freedesktop.org Secret Service API][freedesktop-ss] via [libsecret][libsecret] (requires a graphical interface to unlock secret collections).|Linux `gpg`|Use GPG to store encrypted files that are compatible with the [pass][pass] (requires GPG and `pass` to initialize the store).|macOS, Linux `cache`|Git's built-in [credential cache][credential-cache].|macOS, Linux `plaintext`|Store credentials in plaintext files (**UNSECURE**). Customize the plaintext store location with [`credential.plaintextStorePath`][credential-plaintextstorepath].|Windows, macOS, Linux `none`|Do not store credentials via GCM.|Windows, macOS, Linux #### Example ```bash git config --global credential.credentialStore gpg ``` **Also see: [GCM_CREDENTIAL_STORE][gcm-credential-store]** --- ### credential.cacheOptions Pass [options][cache-options] to the Git credential cache when [`credential.credentialStore`][credential-credentialstore] is set to `cache`. This allows you to select a different amount of time to cache credentials (the default is 900 seconds) by passing `"--timeout "`. Use of other options like `--socket` is untested and unsupported, but there's no reason it shouldn't work. Defaults to empty. #### Example ```shell git config --global credential.cacheOptions "--timeout 300" ``` **Also see: [GCM_CREDENTIAL_CACHE_OPTIONS][gcm-credential-cache-options]** --- ### credential.plaintextStorePath Specify a custom directory to store plaintext credential files in when [`credential.credentialStore`][credential-credentialstore] is set to `plaintext`. Defaults to the value `~/.gcm/store` or `%USERPROFILE%\.gcm\store`. #### Example ```shell git config --global credential.plaintextStorePath /mnt/external-drive/credentials ``` **Also see: [GCM_PLAINTEXT_STORE_PATH][gcm-plaintext-store-path]** --- ### credential.dpapiStorePath Specify a custom directory to store DPAPI protected credential files in when [`credential.credentialStore`][credential-credentialstore] is set to `dpapi`. Defaults to the value `%USERPROFILE%\.gcm\dpapi_store`. #### Example ```batch git config --global credential.dpapiStorePath D:\credentials ``` **Also see: [GCM_DPAPI_STORE_PATH][gcm-dpapi-store-path]** --- ### credential.gpgPassStorePath Specify a custom directory to store GPG-encrypted [pass][pass]-compatible credential files in when [`credential.credentialStore`][credential-credentialstore] is set to `gpg`. Defaults to the value `~/.password-store` or `%USERPROFILE%\.password-store`. #### Example ```shell git config --global credential.gpgPassStorePath /mnt/external-drive/.password-store ``` **Note:** Location of the password store used by [pass][pass] can be overridden by the `PASSWORD_STORE_DIR` environment variable, see the [man page][pass-man] for details. --- ### credential.msauthFlow Specify which authentication flow should be used when performing Microsoft authentication and an interactive flow is required. Defaults to `auto`. **Note:** If [`credential.msauthUseBroker`][credential-msauthusebroker] is set to `true` and the operating system authentication broker is available, all flows will be delegated to the broker. If both of those things are true, then the value of `credential.msauthFlow` has no effect. Value|Authentication Flow -|- `auto` _(default)_|Select the best option depending on the current environment and platform. `embedded`|Show a window with embedded web view control. `system`|Open the user's default web browser. `devicecode`|Show a device code. #### Example ```shell git config --global credential.msauthFlow devicecode ``` **Also see: [GCM_MSAUTH_FLOW][gcm-msauth-flow]** --- ### credential.msauthUseBroker _(experimental)_ Use the operating system account manager where available. Defaults to `false`. In certain cloud hosted environments when using a work or school account, such as [Microsoft DevBox][devbox], the default is `true`. These defaults are subject to change in the future. _**Note:** before you enable this option on Windows, please review the [Windows Broker][wam] details for what this means to your local Windows user account._ Value|Description -|- `true`|Use the operating system account manager as an authentication broker. `false` _(default)_|Do not use the broker. #### Example ```shell git config --global credential.msauthUseBroker true ``` **Also see: [GCM_MSAUTH_USEBROKER][gcm-msauth-usebroker]** --- ### credential.msauthUseDefaultAccount _(experimental)_ Use the current operating system account by default when the broker is enabled. Defaults to `false`. In certain cloud hosted environments when using a work or school account, such as [Microsoft DevBox][devbox], the default is `true`. These defaults are subject to change in the future. Value|Description -|- `true`|Use the current operating system account by default. `false` _(default)_|Do not assume any account to use by default. #### Example ```shell git config --global credential.msauthUseDefaultAccount true ``` **Also see: [GCM_MSAUTH_USEDEFAULTACCOUNT][gcm-msauth-usedefaultaccount]** --- ### credential.useHttpPath Tells Git to pass the entire repository URL, rather than just the hostname, when calling out to a credential provider. (This setting [comes from Git itself][use-http-path], not GCM.) Defaults to `false`. **Note:** GCM sets this value to `true` for `dev.azure.com` (Azure Repos) hosts after installation by default. This is because `dev.azure.com` alone is not enough information to determine the correct Azure authentication authority - we require a part of the path. The fallout of this is that for `dev.azure.com` remote URLs we do not support storing credentials against the full-path. We always store against the `dev.azure.com/org-name` stub. In order to use Azure Repos and store credentials against a full-path URL, you must use the `org-name.visualstudio.com` remote URL format instead. Value|Git Behavior -|- `false` _(default)_|Git will use only `user` and `hostname` to look up credentials. `true`|Git will use the full repository URL to look up credentials. #### Example On Windows using GitHub, for a user whose login is `alice`, and with `credential.useHttpPath` set to `false` (or not set), the following remote URLs will use the same credentials: ```text Credential: "git:https://github.com" (user = alice) https://github.com/foo/bar https://github.com/contoso/widgets https://alice@github.com/contoso/widgets ``` ```text Credential: "git:https://bob@github.com" (user = bob) https://bob@github.com/foo/bar https://bob@github.com/example/myrepo ``` Under the same user but with `credential.useHttpPath` set to `true`, these credentials would be used: ```text Credential: "git:https://github.com/foo/bar" (user = alice) https://github.com/foo/bar ``` ```text Credential: "git:https://github.com/contoso/widgets" (user = alice) https://github.com/contoso/widgets https://alice@github.com/contoso/widgets ``` ```text Credential: "git:https://bob@github.com/foo/bar" (user = bob) https://bob@github.com/foo/bar ``` ```text Credential: "git:https://bob@github.com/example/myrepo" (user = bob) https://bob@github.com/example/myrepo ``` --- ### credential.azreposCredentialType Specify the type of credential the Azure Repos host provider should return. Defaults to the value `pat`. In certain cloud hosted environments when using a work or school account, such as [Microsoft DevBox][devbox], the default value is `oauth`. Value|Description -|- `pat`|Azure DevOps personal access tokens `oauth`|Microsoft identity OAuth tokens (AAD or MSA tokens) Here is more information about [Azure Access tokens][azure-tokens]. #### Example ```shell git config --global credential.azreposCredentialType oauth ``` **Also see: [GCM_AZREPOS_CREDENTIALTYPE][gcm-azrepos-credentialtype]** --- ### credential.azreposManagedIdentity Use a [Managed Identity][managed-identity] to authenticate with Azure Repos. The value `system` will tell GCM to use the system-assigned Managed Identity. To specify a user-assigned Managed Identity, use the format `id://{clientId}` where `{clientId}` is the client ID of the Managed Identity. Alternatively any GUID-like value will also be interpreted as a user-assigned Managed Identity client ID. To specify a Managed Identity associated with an Azure resource, you can use the format `resource://{resourceId}` where `{resourceId}` is the ID of the resource. For more information about managed identities, see the Azure DevOps [documentation][azrepos-sp-mid]. Value|Description -|- `system`|System-Assigned Managed Identity `[guid]`|User-Assigned Managed Identity with the specified client ID `id://[guid]`|User-Assigned Managed Identity with the specified client ID `resource://[guid]`|User-Assigned Managed Identity for the associated resource ```shell git config --global credential.azreposManagedIdentity "id://11111111-1111-1111-1111-111111111111" ``` **Also see: [GCM_AZREPOS_MANAGEDIDENTITY][gcm-azrepos-credentialmanagedidentity]** --- ### credential.azreposServicePrincipal Specify the client and tenant IDs of a [service principal][service-principal] to use when performing Microsoft authentication for Azure Repos. The value of this setting should be in the format: `{tenantId}/{clientId}`. You must also set at least one authentication mechanism if you set this value: - [credential.azreposServicePrincipalSecret][credential-azrepos-sp-secret] - [credential.azreposServicePrincipalCertificateThumbprint][credential-azrepos-sp-cert-thumbprint] - [credential.azreposServicePrincipalCertificateSendX5C][credential-azrepos-sp-cert-x5c] For more information about service principals, see the Azure DevOps [documentation][azrepos-sp-mid]. #### Example ```shell git config --global credential.azreposServicePrincipal "11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222" ``` **Also see: [GCM_AZREPOS_SERVICE_PRINCIPAL][gcm-azrepos-service-principal]** --- ### credential.azreposServicePrincipalSecret Specifies the client secret for the [service principal][service-principal] when performing Microsoft authentication for Azure Repos with [credential.azreposServicePrincipalSecret][credential-azrepos-sp] set. #### Example ```shell git config --global credential.azreposServicePrincipalSecret "da39a3ee5e6b4b0d3255bfef95601890afd80709" ``` **Also see: [GCM_AZREPOS_SP_SECRET][gcm-azrepos-sp-secret]** --- ### credential.azreposServicePrincipalCertificateThumbprint Specifies the thumbprint of a certificate to use when authenticating as a [service principal][service-principal] for Azure Repos when [GCM_AZREPOS_SERVICE_PRINCIPAL][credential-azrepos-sp] is set. #### Example ```shell git config --global credential.azreposServicePrincipalCertificateThumbprint "9b6555292e4ea21cbc2ebd23e66e2f91ebbe92dc" ``` **Also see: [GCM_AZREPOS_SP_CERT_THUMBPRINT][gcm-azrepos-sp-cert-thumbprint]** --- ### credential.azreposServicePrincipalCertificateSendX5C When using a certificate for [service principal][service-principal] authentication, this configuration specifies whether the X5C claim should be should be sent to the STS. Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: this method will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. This saves the application admin from the need to explicitly manage the certificate rollover. For details see [https://aka.ms/msal-net-sni](https://aka.ms/msal-net-sni). #### Example ```shell git config --global credential.azreposServicePrincipalCertificateSendX5C true ``` **Also see: [GCM_AZREPOS_SP_CERT_SEND_X5C][gcm-azrepos-sp-cert-x5c]** --- ### trace2.normalTarget Turns on Trace2 Normal Format tracing - see [Git's Trace2 Normal Format documentation][trace2-normal-docs] for more details. #### Example ```shell git config --global trace2.normalTarget true ``` If the value of `trace2.normalTarget` is a full path to a file in an existing directory, logs are appended to the file. If the value of `trace2.normalTarget` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [GIT_TRACE2][trace2-normal-env]** --- ### trace2.eventTarget Turns on Trace2 Event Format tracing - see [Git's Trace2 Event Format documentation][trace2-event-docs] for more details. #### Example ```shell git config --global trace2.eventTarget true ``` If the value of `trace2.eventTarget` is a full path to a file in an existing directory, logs are appended to the file. If the value of `trace2.eventTarget` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [GIT_TRACE2_EVENT][trace2-event-env]** --- ### trace2.perfTarget Turns on Trace2 Performance Format tracing - see [Git's Trace2 Performance Format documentation][trace2-performance-docs] for more details. #### Example ```shell git config --global trace2.perfTarget true ``` If the value of `trace2.perfTarget` is a full path to a file in an existing directory, logs are appended to the file. If the value of `trace2.perfTarget` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [GIT_TRACE2_PERF][trace2-performance-env]** [auto-detection]: autodetect.md [azure-tokens]: azrepos-users-and-tokens.md [use-http-path]: https://git-scm.com/docs/gitcredentials/#Documentation/gitcredentials.txt-useHttpPath [credential-credentialstore]: #credentialcredentialstore [credential-dpapistorepath]: #credentialdpapistorepath [credential-interactive]: #credentialinteractive [credential-msauthusebroker]: #credentialmsauthusebroker-experimental [credential-plaintextstorepath]: #credentialplaintextstorepath [credential-cache]: https://git-scm.com/docs/git-credential-cache [cred-stores]: credstores.md [devbox]: https://azure.microsoft.com/en-us/products/dev-box [enterprise-config]: enterprise-config.md [envars]: environment.md [freedesktop-ss]: https://specifications.freedesktop.org/secret-service-spec/ [gcm-allow-windowsauth]: environment.md#GCM_ALLOW_WINDOWSAUTH [gcm-allow-unsafe-remotes]: environment.md#GCM_ALLOW_UNSAFE_REMOTES [gcm-authority]: environment.md#GCM_AUTHORITY-deprecated [gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT [gcm-azrepos-credentialtype]: environment.md#GCM_AZREPOS_CREDENTIALTYPE [gcm-azrepos-credentialmanagedidentity]: environment.md#GCM_AZREPOS_MANAGEDIDENTITY [gcm-bitbucket-always-refresh-credentials]: environment.md#GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS [gcm-bitbucket-authmodes]: environment.md#GCM_BITBUCKET_AUTHMODES [gcm-credential-cache-options]: environment.md#GCM_CREDENTIAL_CACHE_OPTIONS [gcm-credential-store]: environment.md#GCM_CREDENTIAL_STORE [gcm-debug]: environment.md#GCM_DEBUG [gcm-dpapi-store-path]: environment.md#GCM_DPAPI_STORE_PATH [gcm-github-accountfiltering]: environment.md#GCM_GITHUB_ACCOUNTFILTERING [gcm-github-authmodes]: environment.md#GCM_GITHUB_AUTHMODES [gcm-gitlab-authmodes]:environment.md#GCM_GITLAB_AUTHMODES [gcm-gui-prompt]: environment.md#GCM_GUI_PROMPT [gcm-gui-software-rendering]: environment.md#GCM_GUI_SOFTWARE_RENDERING [gcm-http-proxy]: environment.md#GCM_HTTP_PROXY-deprecated [gcm-interactive]: environment.md#GCM_INTERACTIVE [gcm-msauth-flow]: environment.md#GCM_MSAUTH_FLOW [gcm-msauth-usebroker]: environment.md#GCM_MSAUTH_USEBROKER-experimental [gcm-msauth-usedefaultaccount]: environment.md#GCM_MSAUTH_USEDEFAULTACCOUNT-experimental [gcm-namespace]: environment.md#GCM_NAMESPACE [gcm-plaintext-store-path]: environment.md#GCM_PLAINTEXT_STORE_PATH [gcm-provider]: environment.md#GCM_PROVIDER [gcm-trace]: environment.md#GCM_TRACE [gcm-trace-secrets]: environment.md#GCM_TRACE_SECRETS [gcm-trace-msauth]: environment.md#GCM_TRACE_MSAUTH [github-emu]: https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/using-enterprise-managed-users-for-iam/about-enterprise-managed-users [usage]: usage.md [git-config-http-proxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy [http-proxy]: netconfig.md#http-proxy [autodetect]: autodetect.md [libsecret]: https://wiki.gnome.org/Projects/Libsecret [managed-identity]: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview [provider-migrate]: migration.md#gcm_authority [cache-options]: https://git-scm.com/docs/git-credential-cache#_options [pass]: https://www.passwordstore.org/ [pass-man]: https://git.zx2c4.com/password-store/about/ [trace2-normal-docs]: https://git-scm.com/docs/api-trace2#_the_normal_format_target [trace2-normal-env]: environment.md#GIT_TRACE2 [trace2-event-docs]: https://git-scm.com/docs/api-trace2#_the_event_format_target [trace2-event-env]: environment.md#GIT_TRACE2_EVENT [trace2-performance-docs]: https://git-scm.com/docs/api-trace2#_the_performance_format_target [trace2-performance-env]: environment.md#GIT_TRACE2_PERF [wam]: windows-broker.md [service-principal]: https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals [azrepos-sp-mid]: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity [credential-azrepos-sp]: #credentialazreposserviceprincipal [credential-azrepos-sp-secret]: #credentialazreposserviceprincipalsecret [credential-azrepos-sp-cert-thumbprint]: #credentialazreposserviceprincipalcertificatethumbprint [credential-azrepos-sp-cert-x5c]: #credentialazreposserviceprincipalcertificatesendx5c [gcm-azrepos-service-principal]: environment.md#GCM_AZREPOS_SERVICE_PRINCIPAL [gcm-azrepos-sp-secret]: environment.md#GCM_AZREPOS_SP_SECRET [gcm-azrepos-sp-cert-thumbprint]: environment.md#GCM_AZREPOS_SP_CERT_THUMBPRINT [gcm-azrepos-sp-cert-x5c]: environment.md#GCM_AZREPOS_SP_CERT_SEND_X5C ================================================ FILE: docs/credstores.md ================================================ # Credential stores There are several options for storing credentials that GCM supports: - Windows Credential Manager - DPAPI protected files - macOS Keychain - [freedesktop.org Secret Service API][freedesktop-secret-service] - GPG/[`pass`][passwordstore] compatible files - Git's built-in [credential cache][credential-cache] - Plaintext files - Passthrough/no-op (no credential store) The default credential stores on macOS and Windows are the macOS Keychain and the Windows Credential Manager, respectively. GCM comes without a default store on Linux distributions. You can select which credential store to use by setting the [`GCM_CREDENTIAL_STORE`][gcm-credential-store] environment variable, or the [`credential.credentialStore`][credential-store] Git configuration setting. For example: ```shell git config --global credential.credentialStore gpg ``` Some credential stores have limitations, or further configuration required depending on your particular setup. See more detailed information below for each credential store. ## Windows Credential Manager **Available on:** _Windows_ **This is the default store on Windows.** **:warning: Does not work over a network/SSH session.** ```batch SET GCM_CREDENTIAL_STORE="wincredman" ``` or ```shell git config --global credential.credentialStore wincredman ``` This credential store uses the Windows Credential APIs (`wincred.h`) to store data securely in the Windows Credential Manager (also known as the Windows Credential Vault in earlier versions of Windows). You can [access and manage data in the credential manager][access-windows-credential-manager] from the control panel, or via the [`cmdkey` command-line tool][cmdkey]. When connecting to a Windows machine over a network session (such as SSH), GCM is unable to persist credentials to the Windows Credential Manager due to limitations in Windows. Connecting by Remote Desktop doesn't suffer from this limitation. ## DPAPI protected files **Available on:** _Windows_ ```batch SET GCM_CREDENTIAL_STORE="dpapi" ``` or ```shell git config --global credential.credentialStore dpapi ``` This credential store uses Windows DPAPI to encrypt credentials which are stored as files in your file system. The file structure is the same as the [plaintext files credential store][plaintext-files] except the first line (the secret value) is protected by DPAPI. By default files are stored in `%USERPROFILE%\.gcm\dpapi_store`. This can be configured using the environment variable `GCM_DPAPI_STORE_PATH` environment variable. If the directory doesn't exist it will be created. ## macOS Keychain **Available on:** _macOS_ **This is the default store on macOS.** ```shell export GCM_CREDENTIAL_STORE=keychain # or git config --global credential.credentialStore keychain ``` This credential store uses the default macOS Keychain, which is typically the `login` keychain. You can [manage data stored in the keychain][mac-keychain-management] using the Keychain Access application. ## [freedesktop.org Secret Service API][freedesktop-secret-service] **Available on:** _Linux_ **:warning: Requires a graphical user interface session.** ```shell export GCM_CREDENTIAL_STORE=secretservice # or git config --global credential.credentialStore secretservice ``` This credential store uses the `libsecret` library to interact with the Secret Service. It stores credentials securely in 'collections', which can be viewed by tools such as `secret-tool` and `seahorse`. A graphical user interface is required in order to show a secure prompt to request a secret collection be unlocked. ## GPG/[`pass`][passwordstore] compatible files **Available on:** _macOS, Linux_ **:warning: Requires `gpg`, `pass`, and a GPG key pair.** ```shell export GCM_CREDENTIAL_STORE=gpg # or git config --global credential.credentialStore gpg ``` This credential store uses GPG to encrypt files containing credentials which are stored in your file system. The file structure is compatible with the popular [`pass`][passwordstore] tool. By default files are stored in `~/.password-store` but this can be configured using the `pass` environment variable `PASSWORD_STORE_DIR`. Before you can use this credential store, it must be initialized by the `pass` utility, which in-turn requires a valid GPG key pair. To initalize the store, run: ```shell pass init ``` ..where `` is the user ID of a GPG key pair on your system. To create a new GPG key pair, run: ```shell gpg --gen-key ``` ..and follow the prompts. ### Headless/TTY-only sessions If you are using the `gpg` credential store in a headless/TTY-only environment, you must ensure you have configured the GPG Agent (`gpg-agent`) with a suitable pin-entry program for the terminal such as `pinentry-tty` or `pinentry-curses`. If you are connecting to your system via SSH, then the `SSH_TTY` variable should automatically be set. GCM will pass the value of `SSH_TTY` to GPG/GPG Agent as the TTY device to use for prompting for a passphrase. If you are not connecting via SSH, or otherwise do not have the `SSH_TTY` environment variable set, you must set the `GPG_TTY` environment variable before running GCM. The easiest way to do this is by adding the following to your profile (`~/.bashrc`, `~/.profile` etc): ```shell export GPG_TTY=$(tty) ``` **Note:** Using `/dev/tty` does not appear to work here - you must use the real TTY device path, as returned by the `tty` utility. ## Git's built-in [credential cache][credential-cache] **Available on:** _macOS, Linux_ ```shell export GCM_CREDENTIAL_STORE=cache # or git config --global credential.credentialStore cache ``` This credential store uses Git's built-in ephemeral in-memory [credential cache][credential-cache]. This helps you reduce the number of times you have to authenticate but doesn't require storing credentials on persistent storage. It's good for scenarios like [Azure Cloud Shell][azure-cloudshell] or [AWS CloudShell][aws-cloudshell], where you don't want to leave credentials on disk but also don't want to re-authenticate on every Git operation. By default, `git credential-cache` stores your credentials for 900 seconds. That, and any other [options it accepts][git-credential-cache-options], may be altered by setting them in the environment variable `GCM_CREDENTIAL_CACHE_OPTIONS` or the Git config value `credential.cacheOptions`. (Using the `--socket` option is untested and unsupported, but there's no reason it shouldn't work.) ```shell export GCM_CREDENTIAL_CACHE_OPTIONS="--timeout 300" # or git config --global credential.cacheOptions "--timeout 300" ``` ## Plaintext files **Available on:** _Windows, macOS, Linux_ **:warning: This is not a secure method of credential storage!** ```shell export GCM_CREDENTIAL_STORE=plaintext # or git config --global credential.credentialStore plaintext ``` This credential store saves credentials to plaintext files in your file system. By default files are stored in `~/.gcm/store` or `%USERPROFILE%\.gcm\store`. This can be configured using the environment variable `GCM_PLAINTEXT_STORE_PATH` environment variable. If the directory doesn't exist it will be created. On POSIX platforms the newly created store directory will have permissions set such that only the owner can `r`ead/`w`rite/e`x`ecute (`700` or `drwx---`). Permissions on existing directories will not be modified. NB. GCM's plaintext store is distinct from [git-credential-store][git-credential-store], though the formats are similar. The default paths differ. --- :warning: **WARNING** :warning: **This storage mechanism is NOT secure!** **Secrets and credentials are stored in plaintext files _without any security_!** It is **HIGHLY RECOMMENDED** to always use one of the other credential store options above. This option is only provided for compatibility and use in environments where no other secure option is available. If you chose to use this credential store, it is recommended you set the permissions on this directory such that no other users or applications can access files within. If possible, use a path that exists on an external volume that you take with you and use full-disk encryption. ## Passthrough/no-op (no credential store) **Available on:** _Windows, macOS, Linux_ **:warning: .** ```batch SET GCM_CREDENTIAL_STORE="none" ``` or ```shell git config --global credential.credentialStore none ``` This option disables the internal credential store. All operations to store or retrieve credentials will do nothing, and will return success. This is useful if you want to use a different credential store, chained in sequence via Git configuration, and don't want GCM to store credentials. Note that you'll want to ensure that another credential helper is placed before GCM in the `credential.helper` Git configuration or else you will be prompted to enter your credentials every time you interact with a remote repository. [access-windows-credential-manager]: https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0 [aws-cloudshell]: https://aws.amazon.com/cloudshell/ [azure-cloudshell]: https://docs.microsoft.com/azure/cloud-shell/overview [cmdkey]: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmdkey [credential-store]: configuration.md#credentialcredentialstore [credential-cache]: https://git-scm.com/docs/git-credential-cache [freedesktop-secret-service]: https://specifications.freedesktop.org/secret-service/ [gcm-credential-store]: environment.md#GCM_CREDENTIAL_STORE [git-credential-store]: https://git-scm.com/docs/git-credential-store [mac-keychain-management]: https://support.apple.com/en-gb/guide/mac-help/mchlf375f392/mac [git-credential-cache-options]: https://git-scm.com/docs/git-credential-cache#_options [passwordstore]: https://www.passwordstore.org/ [plaintext-files]: #plaintext-files ================================================ FILE: docs/development.md ================================================ # Development and debugging Start by cloning this repository: ```shell git clone https://github.com/git-ecosystem/git-credential-manager ``` You also need the latest version of the .NET SDK which can be downloaded and installed from the [.NET website][dotnet-web]. ## Building The `Git-Credential-Manager.sln` solution can be opened and built in Visual Studio, Visual Studio for Mac, Visual Studio Code, or JetBrains Rider. ### macOS To build from inside an IDE, make sure to select the `MacDebug` or `MacRelease` solution configurations. To build from the command line, run: ```shell dotnet build -c MacDebug ``` You can find a copy of the installer .pkg file in `out/osx/Installer.Mac/pkg/Debug`. The flat binaries can also be found in `out/osx/Installer.Mac/pkg/Debug/payload`. ### Windows To build from inside an IDE, make sure to select the `WindowsDebug` or `WindowsRelease` solution configurations. To build from the command line, run: ```powershell dotnet build -c WindowsDebug ``` You can find a copy of the installer .exe file in `out\windows\Installer.Windows\bin\Debug\net472`. The flat binaries can also be found in `out\windows\Payload.Windows\bin\Debug\net472\win-x86`. ### Linux The two available solution configurations are `LinuxDebug` and `LinuxRelease`. To build from the command line, run: ```shell dotnet build -c LinuxDebug ``` If you want to build for a specific architecture, you can provide `linux-x64` or `linux-arm64` or `linux-arm` as the runtime: ```shell dotnet build -c LinuxDebug -r linux-arm64 ``` You can find a copy of the Debian package (.deb) file in `out/linux/Packaging.Linux/deb/Debug`. The flat binaries can also be found in `out/linux/Packaging.Linux/payload/Debug`. ## Debugging To debug from inside an IDE you'll want to set `Git-Credential-Manager` as the startup project, and specify one of `get`, `store`, or `erase` as a program argument. To simulate Git interacting with GCM, when you start from your IDE of choice, you'll need to enter the following [information over standard input][ioformat]: ```text protocol=http host= ``` ..where `` is a supported hostname such as `github.com`, and `` is a line feed (or CRLF, we support both!). You may also include the following optional fields, depending on your scenario: ```text username= password= ``` For more information about how Git interacts with credential helpers, please read Git's documentation on [custom helpers][custom-helpers]. ### Attaching to a running process If you want to debug an already running GCM process, set the `GCM_DEBUG` environment variable to `1` or `true`. The process will wait on launch for a debugger to attach before continuing. This is useful when debugging interactions between GCM and Git, and you want Git to be the one launching us. ### Collect trace output GCM has two tracing systems - one that is distinctly GCM's and one that implements certain features of [Git's Trace2 API][trace2]. Below are instructions for how to use each. #### `GCM_TRACE` If you want to debug a release build or installation of GCM, you can set the `GCM_TRACE` environment variable to `1` to print trace information to standard error, or to an absolute file path to write trace information to a file. For example: ```shell $ GCM_TRACE=1 git-credential-manager version > 18:47:56.526712 ...er/Application.cs:69 trace: [RunInternalAsync] Git Credential Manager version 2.0.124-beta+e1ebbe1517 (macOS, .NET 5.0) 'version' > Git Credential Manager version 2.0.124-beta+e1ebbe1517 (macOS, .NET 5.0) ``` #### Git's Trace2 API This API can also be used to print debug, performance, and telemetry information to stderr or a file in various formats. ##### Supported format targets 1. The Normal Format Target: Similar to `GCM_TRACE`, this target writes human-readable output and is best suited for debugging. It can be enabled via environment variable or config, for example: ```shell export GIT_TRACE2=1 ``` or ```shell git config --global trace2.normalTarget ~/log.normal ``` 0. The Performance Format Target: This format is column-based and geared toward analyzing performance during development and testing. It can be enabled via environment variable or config, for example: ```shell export GIT_TRACE2_PERF=1 ``` or ```shell git config --global trace2.perfTarget ~/log.perf ``` 0. The Event Format Target: This format is json-based and is geared toward collection of large quantities of data for advanced analysis. It can be enabled via environment variable or config, for example: ```shell export GIT_TRACE2_EVENT=1 ``` or ```shell git config --global trace2.eventTarget ~/log.event ``` You can read more about each of these format targets in the [corresponding section][trace2-targets] of Git's Trace2 API documentation. ##### Supported events The below describes, at a high level, the Trace2 API events that are currently supported in GCM and the information they provide: 1. `version`: contains the version of the current executable (e.g. GCM or a helper exe) 0. `start`: contains the complete argv received by current executable's `Main()` method 0. `exit`: contains current executable's exit code 0. `child_start`: describes a child process that is about to be spawned 0. `child_exit`: describes a child process at exit 0. `region_enter`: describes a region (e.g. a timer for a section of code that is interesting) on entry 0. `region_leave`: describes a region on leaving You can read more about each of these format targets in the [corresponding section][trace2-events] of Git's Trace2 API documentation. Want to see more events? Consider contributing! We'd :love: to see your awesome work in support of building out this API. ### Code coverage metrics If you want code coverage metrics these can be generated either from the command line: ```shell dotnet test --collect:"XPlat Code Coverage" --settings=./.code-coverage/coverlet.settings.xml ``` Or via the VSCode Terminal/Run Task: ```console test with coverage ``` HTML reports can be generated using ReportGenerator, this should be installed during the build process, from the command line: ```shell dotnet ~/.nuget/packages/reportgenerator/*/*/net8.0/ReportGenerator.dll -reports:./**/TestResults/**/coverage.cobertura.xml -targetdir:./out/code-coverage ``` or ```shell dotnet {$env:USERPROFILE}/.nuget/packages/reportgenerator/*/*/net8.0/ReportGenerator.dll -reports:./**/TestResults/**/coverage.cobertura.xml -targetdir:./out/code-coverage ``` Or via VSCode Terminal/Run Task: ```console report coverage - nix ``` or ```console report coverage - win ``` ## Linting Documentation Documents are linted using [markdownlint][markdownlint] which can be installed as a CLI tool via NPM or as an [extension in VSCode][vscode-markdownlint]. See the [documentation on GitHub][markdownlint]. The configuration used for markdownlint is in [.markdownlint.jsonc][markdownlint-config]. Documents are checked for link validity using [lychee][lychee]. Lychee can be installed in a variety of ways depending on your platform, see the [docs on GitHub][lychee-docs]. Some URLs are ignored by lychee, per the [lycheeignore][lycheeignore]. [dotnet-web]: https://dotnet.microsoft.com/ [custom-helpers]: https://git-scm.com/docs/gitcredentials#_custom_helpers [ioformat]: https://git-scm.com/docs/git-credential#IOFMT [lychee]: https://lychee.cli.rs/ [lychee-docs]: https://github.com/lycheeverse/lychee [lycheeignore]: ../.lycheeignore [markdownlint]: https://github.com/DavidAnson/markdownlint-cli2 [markdownlint-config]: ../.markdownlint.jsonc [trace2]: https://git-scm.com/docs/api-trace2 [trace2-events]: https://git-scm.com/docs/api-trace2#_event_specific_keyvalue_pairs [trace2-targets]: https://git-scm.com/docs/api-trace2#_trace2_targets [vscode-markdownlint]: https://github.com/DavidAnson/vscode-markdownlint ================================================ FILE: docs/enterprise-config.md ================================================ # Enterprise configuration defaults Git Credential Manager (GCM) can be configured using multiple different mechanisms. In order of preference, those mechanisms are: 1. [Environment variables][environment] 1. Standard [Git configuration][config] files 1. Repository/local configuration (`.git/config`) 1. User/global configuration (`$HOME/.gitconfig` or `%HOME%\.gitconfig`) 1. Installation/system configuration (`etc/gitconfig`) 1. Enterprise system administrator defaults 1. Compiled default values This model largely matches what Git itself supports, namely environment variables that take precedence over Git configuration files. The addition of the enterprise system administrator defaults enables those administrators to configure many GCM settings using familiar MDM tooling, rather than having to modify the Git installation configuration files. ## User Freedom We believe the user should _always_ be at liberty to configure Git and GCM exactly as they wish. By preferring environment variables and Git configuration files over system admin values, these only act as _default values_ that can always be overridden by the user in the usual ways. ## Windows Default setting values come from the Windows Registry, specifically the following keys: ```text HKEY_LOCAL_MACHINE\SOFTWARE\GitCredentialManager\Configuration ``` By using the Windows Registry, system administrators can use Group Policy to easily set defaults for GCM's settings. The names and possible values of all settings under this key are the same as those of the [Git configuration][config] settings. The type of each registry key can be either `REG_SZ` (string) or `REG_DWORD` (integer). ### 32-bit / x86 When running the 32-bit (x86) version of GCM on a 64-bit (x64 or ARM64) installation of Windows, the registry access is transparently redirected to the `WOW6432Node` node. ```text HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GitCredentialManager\Configuration ``` ## macOS Default settings values come from macOS's preferences system. Configuration profiles can be deployed to devices using a compatible Mobile Device Management (MDM) solution. Configuration for Git Credential Manager must take the form of a dictionary, set for the domain `git-credential-manager` under the key `configuration`. For example: ```shell defaults write git-credential-manager configuration -dict-add ``` ..where `` is the name of the settings from the [Git configuration][config] reference, and `` is the desired value. All values in the `configuration` dictionary must be strings. For boolean values use `true` or `false`, and for integer values use the number in string form. To read the current configuration: ```console $ defaults read git-credential-manager configuration { = ; ... = ; } ``` ## Linux Default settings values come from the `/etc/git-credential-manager/config.d` directory. Each file in this directory represents a single settings dictionary. All files in this directory are read at runtime and merged into a single collection of settings, in the order they are read from the file system. To provide a stable ordering, it is recommended to prefix each filename with a number, e.g. `42-my-settings`. The format of each file is a simple set of key/value pairs, separated by an `=` sign, and each line separated by a line-feed (`\n`, LF) character. Comments are identified by a `#` character at the beginning of a line. For example: ```text # /etc/git-credential-manager/config.d/00-example1 credential.noguiprompt=0 ``` ```text # /etc/git-credential-manager/config.d/01-example2 credential.trace=true credential.traceMsAuth=true ``` All settings names and values are the same as the [Git configuration][config] reference. > Note: These files are read once at startup. If changes are made to these files they will not be reflected in an already running process. [environment]: environment.md [config]: configuration.md ================================================ FILE: docs/environment.md ================================================ # Environment variables [Git Credential Manager][gcm] works out of the box for most users. Configuration options are available to customize or tweak behavior. Git Credential Manager (GCM) can be configured using environment variables. **Environment variables take precedence over [configuration][configuration] options and enterprise system administrator [default values][default-values]**. For the complete list of environment variables GCM understands, see the list below. ## Available settings ### GCM_TRACE Enables trace logging of all activities. Configuring Git and GCM to trace to the same location is often desirable, and GCM is compatible and cooperative with `GIT_TRACE`. #### Example ##### Windows ```batch SET GIT_TRACE=%UserProfile%\git.log SET GCM_TRACE=%UserProfile%\git.log ``` ##### macOS/Linux ```bash export GIT_TRACE=$HOME/git.log export GCM_TRACE=$HOME/git.log ``` If the value of `GCM_TRACE` is a full path to a file in an existing directory, logs are appended to the file. If the value of `GCM_TRACE` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [credential.trace][credential-trace]** --- ### GCM_TRACE_SECRETS Enables tracing of secret and sensitive information, which is by default masked in trace output. Requires that `GCM_TRACE` is also enabled. #### Example ##### Windows ```batch SET GCM_TRACE=%UserProfile%\gcm.log SET GCM_TRACE_SECRETS=1 ``` ##### macOS/Linux ```bash export GCM_TRACE=$HOME/gcm.log export GCM_TRACE_SECRETS=1 ``` If the value of `GCM_TRACE_SECRETS` is `true` or `1`, trace logs will include secret information. Defaults to disabled. **Also see: [credential.traceSecrets][credential-trace-secrets]** --- ### GCM_TRACE_MSAUTH Enables inclusion of Microsoft Authentication library (MSAL) logs in GCM trace output. Requires that `GCM_TRACE` is also enabled. #### Example ##### Windows ```batch SET GCM_TRACE=%UserProfile%\gcm.log SET GCM_TRACE_MSAUTH=1 ``` ##### macOS/Linux ```bash export GCM_TRACE=$HOME/gcm.log export GCM_TRACE_MSAUTH=1 ``` If the value of `GCM_TRACE_MSAUTH` is `true` or `1`, trace logs will include verbose MSAL logs. Defaults to disabled. **Also see: [credential.traceMsAuth][credential-trace-msauth]** --- ### GCM_DEBUG Pauses execution of GCM at launch to wait for a debugger to be attached. #### Example ##### Windows ```batch SET GCM_DEBUG=1 ``` ##### macOS/Linux ```bash export GCM_DEBUG=1 ``` Defaults to disabled. **Also see: [credential.debug][credential-debug]** --- ### GCM_INTERACTIVE Permit or disable GCM from interacting with the user (showing GUI or TTY prompts). If interaction is required but has been disabled, an error is returned. This can be helpful when using GCM in headless and unattended environments, such as build servers, where it would be preferable to fail than to hang indefinitely waiting for a non-existent user. To disable interactivity set this to `false` or `0`. #### Compatibility In previous versions of GCM this setting had a different behavior and accepted other values. The following table summarizes the change in behavior and the mapping of older values such as `never`: Value(s)|Old meaning|New meaning -|-|- `auto`|Prompt if required – use cached credentials if possible|_(unchanged)_ `never`, `false`| Never prompt – fail if interaction is required|_(unchanged)_ `always`, `force`, `true`|Always prompt – don't use cached credentials|Prompt if required (same as the old `auto` value) #### Example ##### Windows ```batch SET GCM_INTERACTIVE=0 ``` ##### macOS/Linux ```bash export GCM_INTERACTIVE=0 ``` Defaults to enabled. **Also see: [credential.interactive][credential-interactive]** --- ### GCM_PROVIDER Define the host provider to use when authenticating. ID|Provider -|- `auto` _(default)_|_\[automatic\]_ ([learn more][autodetect]) `azure-repos`|Azure Repos `github`|GitHub `gitlab`|GitLab _(supports OAuth in browser, personal access token and Basic Authentication)_ `generic`|Generic (any other provider not listed above) Automatic provider selection is based on the remote URL. This setting is typically used with a scoped URL to map a particular set of remote URLs to providers, for example to mark a host as a GitHub Enterprise instance. #### Example ##### Windows ```batch SET GCM_PROVIDER=github ``` ##### macOS/Linux ```bash export GCM_PROVIDER=github ``` **Also see: [credential.provider][credential-provider]** --- ### GCM_AUTHORITY _(deprecated)_ > This setting is deprecated and should be replaced by `GCM_PROVIDER` with the > corresponding provider ID value. > > See the [migration guide][migration-guide] for more information. Select the host provider to use when authenticating by which authority is supported by the providers. Authority|Provider(s) -|- `auto` _(default)_|_\[automatic\]_ `msa`, `microsoft`, `microsoftaccount`, `aad`, `azure`, `azuredirectory`, `live`, `liveconnect`, `liveid`|Azure Repos _(supports Microsoft Authentication)_ `github`|GitHub _(supports GitHub Authentication)_ `gitlab`|GitLab _(supports OAuth in browser, personal access token and Basic Authentication)_ `basic`, `integrated`, `windows`, `kerberos`, `ntlm`, `tfs`, `sso`|Generic _(supports Basic and Windows Integrated Authentication)_ #### Example ##### Windows ```batch SET GCM_AUTHORITY=github ``` ##### macOS/Linux ```bash export GCM_AUTHORITY=github ``` **Also see: [credential.authority][credential-authority]** --- ### GCM_GUI_PROMPT Permit or disable GCM from presenting GUI prompts. If an equivalent terminal/ text-based prompt is available, that will be shown instead. To disable all interactivity see [GCM_INTERACTIVE][gcm-interactive]. #### Example ##### Windows ```batch SET GCM_GUI_PROMPT=0 ``` ##### macOS/Linux ```bash export GCM_GUI_PROMPT=0 ``` Defaults to enabled. **Also see: [credential.guiPrompt][credential-guiprompt]** --- ### GCM_GUI_SOFTWARE_RENDERING Force the use of software rendering for GUI prompts. This is currently only applicable on Windows. #### Example ##### Windows ```batch SET GCM_GUI_SOFTWARE_RENDERING=1 ``` ##### macOS/Linux ```bash export GCM_GUI_SOFTWARE_RENDERING=1 ``` Defaults to false (use hardware acceleration where available). > [!NOTE] > Windows on ARM devices defaults to using software rendering to work around a > known Avalonia issue: **Also see: [credential.guiSoftwareRendering][credential-guisoftwarerendering]** --- ### GCM_ALLOW_UNSAFE_REMOTES Allow transmitting credentials to unsafe remote URLs such as unencrypted HTTP URLs. This setting is not recommended for general use and should only be used when necessary. Defaults false (disallow unsafe remote URLs). #### Example ##### Windows ```batch SET GCM_ALLOW_UNSAFE_REMOTES=true ``` ##### macOS/Linux ```bash export GCM_ALLOW_UNSAFE_REMOTES=true ``` **Also see: [credential.allowUnsafeRemotes][credential-allowunsaferemotes]** --- ### GCM_AUTODETECT_TIMEOUT Set the maximum length of time, in milliseconds, that GCM should wait for a network response during host provider auto-detection probing. See [autodetection][autodetect] for more information. **Note:** Use a negative or zero value to disable probing altogether. Defaults to 2000 milliseconds (2 seconds). #### Example ##### Windows ```batch SET GCM_AUTODETECT_TIMEOUT=-1 ``` ##### macOS/Linux ```bash export GCM_AUTODETECT_TIMEOUT=-1 ``` **Also see: [credential.autoDetectTimeout][credential-autodetecttimeout]** --- ### GCM_ALLOW_WINDOWSAUTH Allow detection of Windows Integrated Authentication (WIA) support for generic host providers. Setting this value to `false` will prevent the use of WIA and force a basic authentication prompt when using the Generic host provider. **Note:** WIA is only supported on Windows. **Note:** WIA is an umbrella term for NTLM and Kerberos (and Negotiate). Value|WIA detection -|- `true`, `1`, `yes`, `on` _(default)_|Permitted `false`, `0`, `no`, `off`|Not permitted #### Example ##### Windows ```batch SET GCM_ALLOW_WINDOWSAUTH=0 ``` ##### macOS/Linux ```bash export GCM_ALLOW_WINDOWSAUTH=0 ``` **Also see: [credential.allowWindowsAuth][credential-allowwindowsauth]** --- ### GCM_HTTP_PROXY _(deprecated)_ > This setting is deprecated and should be replaced by the [standard `http.proxy` > Git configuration option][git-httpproxy]. > > See the [HTTP proxy configuration][network-http-proxy] for more information. Configure GCM to use the a proxy for network operations. **Note:** Git itself does _not_ respect this setting; this affects GCM _only_. #### Windows ```batch SET GCM_HTTP_PROXY=http://john.doe:password@proxy.contoso.com ``` #### macOS/Linux ```bash export GCM_HTTP_PROXY=http://john.doe:password@proxy.contoso.com ``` **Also see: [credential.httpProxy][credential-httpproxy]** --- ### GCM_BITBUCKET_AUTHMODES Override the available authentication modes presented during Bitbucket authentication. If this option is not set, then the available authentication modes will be automatically detected. **Note:** This setting only applies to Bitbucket.org, and not Server or DC instances. **Note:** This setting supports multiple values separated by commas. Value|Authentication Mode -|- _(unset)_|Automatically detect modes `oauth`|OAuth-based authentication `basic`|Basic/PAT-based authentication #### Windows ```batch SET GCM_BITBUCKET_AUTHMODES="oauth,basic" ``` #### macOS/Linux ```bash export GCM_BITBUCKET_AUTHMODES="oauth,basic" ``` **Also see: [credential.bitbucketAuthModes][credential-bitbucketauthmodes]** --- ### GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS Forces GCM to ignore any existing stored Basic Auth or OAuth access tokens and always run through the process to refresh the credentials before returning them to Git. This is especially relevant to OAuth credentials. Bitbucket.org access tokens expire after 2 hours, after that the refresh token must be used to get a new access token. Enabling this option will improve performance when using Oauth2 and interacting with Bitbucket.org if, on average, commits are done less frequently than every 2 hours. Enabling this option will decrease performance when using Basic Auth by requiring the user the re-enter credentials every time. Value|Refresh Credentials Before Returning -|- `true`, `1`, `yes`, `on` |Always `false`, `0`, `no`, `off`_(default)_|Only when the credentials are found to be invalid #### Windows ```batch SET GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS=1 ``` #### macOS/Linux ```bash export GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS=1 ``` Defaults to false/disabled. **Also see: [credential.bitbucketAlwaysRefreshCredentials](configuration.md#credentialbitbucketAlwaysRefreshCredentials)** --- ### GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS Forces GCM to validate any stored credentials before returning them to Git. It does this by calling a REST API resource that requires authentication. Disabling this option reduces the HTTP traffic within GCM when it is retrieving credentials. This may improve user performance, but will increase the number of times Git remote calls fail to authenticate with the host and therefore require the user to re-try the Git remote call. Enabling this option helps ensure Git is always provided with valid credentials. Value|Validate credentials -|- `true`, `1`, `yes`, `on`_(default)_|Always `false`, `0`, `no`, `off`|Never #### Windows ```batch SET GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS=1 ``` #### macOS/Linux ```bash export GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS=1 ``` Defaults to true/enabled. **Also see: [credential.bitbucketValidateStoredCredentials](configuration.md#credentialbitbucketValidateStoredCredentials)** --- ### GCM_BITBUCKET_DATACENTER_CLIENTID To use OAuth with Bitbucket DC it is necessary to create an external, incoming [AppLink](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html). It is then necessary to configure the local GCM installation with the OAuth [ClientId](environment.md#GCM_BITBUCKET_DATACENTER_CLIENTID) and [ClientSecret](environment.md#GCM_BITBUCKET_DATACENTER_CLIENTSECRET) from the AppLink. #### Windows ```batch SET GCM_BITBUCKET_DATACENTER_CLIENTID=1111111111111111111 ``` #### macOS/Linux ```bash export GCM_BITBUCKET_DATACENTER_CLIENTID=1111111111111111111 ``` Defaults to undefined. **Also see: [credential.bitbucketDataCenterOAuthClientId](configuration.md#credentialbitbucketDataCenterOAuthClientId)** --- ### GCM_BITBUCKET_DATACENTER_CLIENTSECRET To use OAuth with Bitbucket DC it is necessary to create an external, incoming [AppLink](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html). It is then necessary to configure the local GCM installation with the OAuth [ClientId](environment.md#GCM_BITBUCKET_DATACENTER_CLIENTID) and [ClientSecret](environment.md#GCM_BITBUCKET_DATACENTER_CLIENTSECRET) from the AppLink. #### Windows ```batch SET GCM_BITBUCKET_DATACENTER_CLIENTSECRET=222222222222222222222 ``` #### macOS/Linux ```bash export GCM_BITBUCKET_DATACENTER_CLIENTSECRET=222222222222222222222 ``` Defaults to undefined. **Also see: [credential.bitbucketDataCenterOAuthClientSecret](configuration.md#credentialbitbucketDataCenterOAuthClientSecret)** --- ### GCM_GITHUB_ACCOUNTFILTERING Enable or disable automatic account filtering for GitHub based on server hints when there are multiple available accounts. This setting is only applicable to GitHub.com with [Enterprise Managed Users][github-emu]. Value|Description -|- `true` _(default)_|Filter available accounts based on server hints. `false`|Show all available accounts. #### Windows ```batch SET GCM_GITHUB_ACCOUNTFILTERING=false ``` #### macOS/Linux ```bash export GCM_GITHUB_ACCOUNTFILTERING=false ``` **Also see: [credential.gitHubAccountFiltering][credential-githubaccountfiltering]** --- ### GCM_GITHUB_AUTHMODES Override the available authentication modes presented during GitHub authentication. If this option is not set, then the available authentication modes will be automatically detected. **Note:** This setting supports multiple values separated by commas. Value|Authentication Mode -|- _(unset)_|Automatically detect modes `oauth`|Expands to: `browser, device` `browser`|OAuth authentication via a web browser _(requires a GUI)_ `device`|OAuth authentication with a device code `basic`|Basic authentication using username and password `pat`|Personal Access Token (pat)-based authentication #### Windows ```batch SET GCM_GITHUB_AUTHMODES="oauth,basic" ``` #### macOS/Linux ```bash export GCM_GITHUB_AUTHMODES="oauth,basic" ``` **Also see: [credential.gitHubAuthModes][credential-githubauthmodes]** --- ### GCM_GITLAB_AUTHMODES Override the available authentication modes presented during GitLab authentication. If this option is not set, then the available authentication modes will be automatically detected. **Note:** This setting supports multiple values separated by commas. Value|Authentication Mode -|- _(unset)_|Automatically detect modes `browser`|OAuth authentication via a web browser _(requires a GUI)_ `basic`|Basic authentication using username and password `pat`|Personal Access Token (pat)-based authentication #### Windows ```batch SET GCM_GITLAB_AUTHMODES="browser" ``` #### macOS/Linux ```bash export GCM_GITLAB_AUTHMODES="browser" ``` **Also see: [credential.gitLabAuthModes][credential-gitlabauthmodes]** --- ### GCM_NAMESPACE Use a custom namespace prefix for credentials read and written in the OS credential store. Credentials will be stored in the format `{namespace}:{service}`. Defaults to the value `git`. #### Windows ```batch SET GCM_NAMESPACE="my-namespace" ``` #### macOS/Linux ```bash export GCM_NAMESPACE="my-namespace" ``` **Also see: [credential.namespace][credential-namespace]** --- ### GCM_CREDENTIAL_STORE Select the type of credential store to use on supported platforms. Default value on Windows is `wincredman`, on macOS is `keychain`, and is unset on Linux. **Note:** For more information about configuring secret stores see the [credential stores documentation][credential-stores]. Value|Credential Store|Platforms -|-|- _(unset)_|Windows: `wincredman`, macOS: `keychain`, Linux: _(none)_|- `wincredman`|Windows Credential Manager (not available over SSH).|Windows `dpapi`|DPAPI protected files. Customize the DPAPI store location with [`GCM_DPAPI_STORE_PATH`][gcm-dpapi-store-path]|Windows `keychain`|macOS Keychain.|macOS `secretservice`|[freedesktop.org Secret Service API][freedesktop-ss] via [libsecret][libsecret] (requires a graphical interface to unlock secret collections).|Linux `gpg`|Use GPG to store encrypted files that are compatible with the [`pass` utility][passwordstore] (requires GPG and `pass` to initialize the store).|macOS, Linux `cache`|Git's built-in [credential cache][git-credential-cache].|Windows, macOS, Linux `plaintext`|Store credentials in plaintext files (**UNSECURE**). Customize the plaintext store location with [`GCM_PLAINTEXT_STORE_PATH`][gcm-plaintext-store-path].|Windows, macOS, Linux `none`|Do not store credentials via GCM.|Windows, macOS, Linux #### Windows ```batch SET GCM_CREDENTIAL_STORE="gpg" ``` #### macOS/Linux ```bash export GCM_CREDENTIAL_STORE="gpg" ``` **Also see: [credential.credentialStore][credential-credentialstore]** --- ### GCM_CREDENTIAL_CACHE_OPTIONS Pass [options][git-cache-options] to the Git credential cache when [`GCM_CREDENTIAL_STORE`][gcm-credential-store] is set to `cache`. This allows you to select a different amount of time to cache credentials (the default is 900 seconds) by passing `"--timeout "`. Use of other options like `--socket` is untested and unsupported, but there's no reason it shouldn't work. Defaults to empty. #### Windows ```batch SET GCM_CREDENTIAL_CACHE_OPTIONS="--timeout 300" ``` #### macOS/Linux ```shell export GCM_CREDENTIAL_CACHE_OPTIONS="--timeout 300" ``` **Also see: [credential.cacheOptions][credential-cacheoptions]** --- ### GCM_PLAINTEXT_STORE_PATH Specify a custom directory to store plaintext credential files in when [`GCM_CREDENTIAL_STORE`][gcm-credential-store] is set to `plaintext`. Defaults to the value `~/.gcm/store` or `%USERPROFILE%\.gcm\store`. #### Windows ```batch SETX GCM_PLAINTEXT_STORE_PATH=D:\credentials ``` #### macOS/Linux ```shell export GCM_PLAINTEXT_STORE_PATH=/mnt/external-drive/credentials ``` **Also see: [credential.plaintextStorePath][credential-plain-text-store]** --- ### GCM_DPAPI_STORE_PATH Specify a custom directory to store DPAPI protected credential files in when [`GCM_CREDENTIAL_STORE`][gcm-credential-store] is set to `dpapi`. Defaults to the value `%USERPROFILE%\.gcm\dpapi_store`. #### Windows ```batch SETX GCM_DPAPI_STORE_PATH=D:\credentials ``` **Also see: [credential.dpapiStorePath][credential-dpapi-store-path]** --- ### GCM_GPG_PATH Specify the path (_including_ the executable name) to the version of `gpg` used by `pass` (`gpg2` if present, otherwise `gpg`). This is primarily meant to allow manual resolution of the conflict that occurs on legacy Linux systems with parallel installs of `gpg` and `gpg2`. If not specified, GCM defaults to using the version of `gpg2` on the `$PATH`, falling back on `gpg` if `gpg2` is not found. #### macOS/Linux ```bash export GCM_GPG_PATH="/usr/local/bin/gpg2" ``` _No configuration equivalent._ --- ### GCM_MSAUTH_FLOW Specify which authentication flow should be used when performing Microsoft authentication and an interactive flow is required. Defaults to `auto`. **Note:** If [`GCM_MSAUTH_USEBROKER`][gcm-msauth-usebroker] is set to `true` and the operating system authentication broker is available, all flows will be delegated to the broker. If both of those things are true, then the value of `GCM_MSAUTH_FLOW` has no effect. Value|Authentication Flow -|- `auto` _(default)_|Select the best option depending on the current environment and platform. `embedded`|Show a window with embedded web view control. `system`|Open the user's default web browser. `devicecode`|Show a device code. #### Windows ```batch SET GCM_MSAUTH_FLOW="devicecode" ``` #### macOS/Linux ```bash export GCM_MSAUTH_FLOW="devicecode" ``` **Also see: [credential.msauthFlow][credential-msauth-flow]** --- ### GCM_MSAUTH_USEBROKER _(experimental)_ Use the operating system account manager where available. Defaults to `false`. In certain cloud hosted environments when using a work or school account, such as [Microsoft DevBox][devbox], the default is `true`. These defaults are subject to change in the future. _**Note:** before you enable this option on Windows, please [review the details][windows-broker] about what this means to your local Windows user account._ Value|Description -|- `true`|Use the operating system account manager as an authentication broker. `false` _(default)_|Do not use the broker. #### Windows ```batch SET GCM_MSAUTH_USEBROKER="true" ``` #### macOS/Linux ```bash export GCM_MSAUTH_USEBROKER="false" ``` **Also see: [credential.msauthUseBroker][credential-msauth-usebroker]** --- ### GCM_MSAUTH_USEDEFAULTACCOUNT _(experimental)_ Use the current operating system account by default when the broker is enabled. Defaults to `false`. In certain cloud hosted environments when using a work or school account, such as [Microsoft DevBox][devbox], the default is `true`. These defaults are subject to change in the future. Value|Description -|- `true`|Use the current operating system account by default. `false` _(default)_|Do not assume any account to use by default. #### Windows ```batch SET GCM_MSAUTH_USEDEFAULTACCOUNT="true" ``` #### macOS/Linux ```bash export GCM_MSAUTH_USEDEFAULTACCOUNT="false" ``` **Also see: [credential.msauthUseDefaultAccount][credential-msauth-usedefaultaccount]** --- ### GCM_AZREPOS_CREDENTIALTYPE Specify the type of credential the Azure Repos host provider should return. Defaults to the value `pat`. In certain cloud hosted environments when using a work or school account, such as [Microsoft DevBox][devbox], the default value is `oauth`. Value|Description -|- `pat`|Azure DevOps personal access tokens `oauth`|Microsoft identity OAuth tokens (AAD or MSA tokens) More information about Azure Access tokens can be found [here][azure-access-tokens]. #### Windows ```batch SET GCM_AZREPOS_CREDENTIALTYPE="oauth" ``` #### macOS/Linux ```bash export GCM_AZREPOS_CREDENTIALTYPE="oauth" ``` **Also see: [credential.azreposCredentialType][credential-azrepos-credential-type]** --- ### GCM_AZREPOS_MANAGEDIDENTITY Use a [Managed Identity][managed-identity] to authenticate with Azure Repos. The value `system` will tell GCM to use the system-assigned Managed Identity. To specify a user-assigned Managed Identity, use the format `id://{clientId}` where `{clientId}` is the client ID of the Managed Identity. Alternatively any GUID-like value will also be interpreted as a user-assigned Managed Identity client ID. To specify a Managed Identity associated with an Azure resource, you can use the format `resource://{resourceId}` where `{resourceId}` is the ID of the resource. For more information about managed identities, see the Azure DevOps [documentation][azrepos-sp-mid]. Value|Description -|- `system`|System-Assigned Managed Identity `[guid]`|User-Assigned Managed Identity with the specified client ID `id://[guid]`|User-Assigned Managed Identity with the specified client ID `resource://[guid]`|User-Assigned Managed Identity for the associated resource #### Windows ```batch SET GCM_AZREPOS_MANAGEDIDENTITY="id://11111111-1111-1111-1111-111111111111" ``` #### macOS/Linux ```bash export GCM_AZREPOS_MANAGEDIDENTITY="id://11111111-1111-1111-1111-111111111111" ``` **Also see: [credential.azreposManagedIdentity][credential-azrepos-managedidentity]** --- ### GCM_AZREPOS_SERVICE_PRINCIPAL Specify the client and tenant IDs of a [service principal][service-principal] to use when performing Microsoft authentication for Azure Repos. The value of this setting should be in the format: `{tenantId}/{clientId}`. You must also set at least one authentication mechanism if you set this value: - [GCM_AZREPOS_SP_SECRET][gcm-azrepos-sp-secret] - [GCM_AZREPOS_SP_CERT_THUMBPRINT][gcm-azrepos-sp-cert-thumbprint] For more information about service principals, see the Azure DevOps [documentation][azrepos-sp-mid]. #### Windows ```batch SET GCM_AZREPOS_SERVICE_PRINCIPAL="11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222" ``` #### macOS/Linux ```bash export GCM_AZREPOS_SERVICE_PRINCIPAL="11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222" ``` **Also see: [credential.azreposServicePrincipal][credential-azrepos-sp]** --- ### GCM_AZREPOS_SP_SECRET Specifies the client secret for the [service principal][service-principal] when performing Microsoft authentication for Azure Repos with [GCM_AZREPOS_SERVICE_PRINCIPAL][gcm-azrepos-sp] set. #### Windows ```batch SET GCM_AZREPOS_SP_SECRET="da39a3ee5e6b4b0d3255bfef95601890afd80709" ``` #### macOS/Linux ```bash export GCM_AZREPOS_SP_SECRET="da39a3ee5e6b4b0d3255bfef95601890afd80709" ``` **Also see: [credential.azreposServicePrincipalSecret][credential-azrepos-sp-secret]** --- ### GCM_AZREPOS_SP_CERT_THUMBPRINT Specifies the thumbprint of a certificate to use when authenticating as a [service principal][service-principal] for Azure Repos when [GCM_AZREPOS_SERVICE_PRINCIPAL][gcm-azrepos-sp] is set. #### Windows ```batch SET GCM_AZREPOS_SP_CERT_THUMBPRINT="9b6555292e4ea21cbc2ebd23e66e2f91ebbe92dc" ``` #### macOS/Linux ```bash export GCM_AZREPOS_SP_CERT_THUMBPRINT="9b6555292e4ea21cbc2ebd23e66e2f91ebbe92dc" ``` **Also see: [credential.azreposServicePrincipalCertificateThumbprint][credential-azrepos-sp-cert-thumbprint]** --- ### GCM_AZREPOS_SP_CERT_SEND_X5C When using a certificate for service principal authentication, this configuration specifies whether the X5C claim should be should be sent to the STS. Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: this method will send the public certificate to Azure AD along with the token request, so that Azure AD can use it to validate the subject name based on a trusted issuer policy. This saves the application admin from the need to explicitly manage the certificate rollover. For details see [https://aka.ms/msal-net-sni](https://aka.ms/msal-net-sni). #### Windows ```batch SET GCM_AZREPOS_SP_CERT_SEND_X5C="true" ``` #### macOS/Linux ```bash export GCM_AZREPOS_SP_CERT_SEND_X5C="true" ``` **Also see: [credential.azreposServicePrincipalCertificateSendX5C][credential-azrepos-sp-cert-x5c]** --- ### GIT_TRACE2 Turns on Trace2 Normal Format tracing - see [Git's Trace2 Normal Format documentation][trace2-normal-docs] for more details. #### Windows ```batch SET GIT_TRACE2=%UserProfile%\log.normal ``` #### macOS/Linux ```bash export GIT_TRACE2=~/log.normal ``` If the value of `GIT_TRACE2` is a full path to a file in an existing directory, logs are appended to the file. If the value of `GIT_TRACE2` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [trace2.normalFormat][trace2-normal-config]** --- ### GIT_TRACE2_EVENT Turns on Trace2 Event Format tracing - see [Git's Trace2 Event Format documentation][trace2-event-docs] for more details. #### Windows ```batch SET GIT_TRACE2_EVENT=%UserProfile%\log.event ``` #### macOS/Linux ```bash export GIT_TRACE2_EVENT=~/log.event ``` If the value of `GIT_TRACE2_EVENT` is a full path to a file in an existing directory, logs are appended to the file. If the value of `GIT_TRACE2_EVENT` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [trace2.eventFormat][trace2-event-config]** --- ### GIT_TRACE2_PERF Turns on Trace2 Performance Format tracing - see [Git's Trace2 Performance Format documentation][trace2-performance-docs] for more details. #### Windows ```batch SET GIT_TRACE2_PERF=%UserProfile%\log.perf ``` #### macOS/Linux ```bash export GIT_TRACE2_PERF=~/log.perf ``` If the value of `GIT_TRACE2_PERF` is a full path to a file in an existing directory, logs are appended to the file. If the value of `GIT_TRACE2_PERF` is `true` or `1`, logs are written to standard error. Defaults to disabled. **Also see: [trace2.perfFormat][trace2-performance-config]** [autodetect]: autodetect.md [azure-access-tokens]: azrepos-users-and-tokens.md [configuration]: configuration.md [credential-allowwindowsauth]: configuration.md#credentialallowwindowsauth [credential-allowunsaferemotes]: configuration.md#credentialallowunsaferemotes [credential-authority]: configuration.md#credentialauthority-deprecated [credential-autodetecttimeout]: configuration.md#credentialautodetecttimeout [credential-azrepos-credential-type]: configuration.md#credentialazreposcredentialtype [credential-azrepos-managedidentity]: configuration.md#credentialazreposmanagedidentity [credential-bitbucketauthmodes]: configuration.md#credentialbitbucketAuthModes [credential-cacheoptions]: configuration.md#credentialcacheoptions [credential-credentialstore]: configuration.md#credentialcredentialstore [credential-debug]: configuration.md#credentialdebug [credential-dpapi-store-path]: configuration.md#credentialdpapistorepath [credential-githubaccountfiltering]: configuration.md#credentialgitHubAccountFiltering [credential-githubauthmodes]: configuration.md#credentialgitHubAuthModes [credential-gitlabauthmodes]: configuration.md#credentialgitLabAuthModes [credential-guiprompt]: configuration.md#credentialguiprompt [credential-guisoftwarerendering]: configuration.md#credentialguisoftwarerendering [credential-httpproxy]: configuration.md#credentialhttpProxy-deprecated [credential-interactive]: configuration.md#credentialinteractive [credential-namespace]: configuration.md#credentialnamespace [credential-msauth-flow]: configuration.md#credentialmsauthflow [credential-msauth-usebroker]: configuration.md#credentialmsauthusebroker-experimental [credential-msauth-usedefaultaccount]: configuration.md#credentialmsauthusedefaultaccount-experimental [credential-plain-text-store]: configuration.md#credentialplaintextstorepath [credential-provider]: configuration.md#credentialprovider [credential-stores]: credstores.md [credential-trace]: configuration.md#credentialtrace [credential-trace-secrets]: configuration.md#credentialtracesecrets [credential-trace-msauth]: configuration.md#credentialtracemsauth [default-values]: enterprise-config.md [devbox]: https://azure.microsoft.com/en-us/products/dev-box [freedesktop-ss]: https://specifications.freedesktop.org/secret-service-spec/ [gcm]: usage.md [gcm-interactive]: #gcm_interactive [gcm-credential-store]: #gcm_credential_store [gcm-dpapi-store-path]: #gcm_dpapi_store_path [gcm-plaintext-store-path]: #gcm_plaintext_store_path [gcm-msauth-usebroker]: #gcm_msauth_usebroker-experimental [git-cache-options]: https://git-scm.com/docs/git-credential-cache#_options [git-credential-cache]: https://git-scm.com/docs/git-credential-cache [git-httpproxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy [github-emu]: https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/using-enterprise-managed-users-for-iam/about-enterprise-managed-users [network-http-proxy]: netconfig.md#http-proxy [libsecret]: https://wiki.gnome.org/Projects/Libsecret [managed-identity]: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview [migration-guide]: migration.md#gcm_authority [passwordstore]: https://www.passwordstore.org/ [trace2-normal-docs]: https://git-scm.com/docs/api-trace2#_the_normal_format_target [trace2-normal-config]: configuration.md#trace2normalTarget [trace2-event-docs]: https://git-scm.com/docs/api-trace2#_the_event_format_target [trace2-event-config]: configuration.md#trace2eventTarget [trace2-performance-docs]: https://git-scm.com/docs/api-trace2#_the_performance_format_target [trace2-performance-config]: configuration.md#trace2perfTarget [windows-broker]: windows-broker.md [service-principal]: https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals [azrepos-sp-mid]: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity [gcm-azrepos-sp]: #gcm_azrepos_service_principal [gcm-azrepos-sp-secret]: #gcm_azrepos_sp_secret [gcm-azrepos-sp-cert-thumbprint]: #gcm_azrepos_sp_cert_thumbprint [gcm-azrepos-sp-cert-x5c]: #gcm_azrepos_sp_cert_send_x5c [credential-azrepos-sp]: configuration.md#credentialazreposserviceprincipal [credential-azrepos-sp-secret]: configuration.md#credentialazreposserviceprincipalsecret [credential-azrepos-sp-cert-thumbprint]: configuration.md#credentialazreposserviceprincipalcertificatethumbprint [credential-azrepos-sp-cert-x5c]: configuration.md#credentialazreposserviceprincipalcertificatesendx5c ================================================ FILE: docs/faq.md ================================================ # Frequently asked questions ## Authentication problems ### Q: I got an error trying to push/pull/clone. What do I do now? Please follow these steps to diagnose or resolve the problem: 1. Check if you can access the remote repository in a web browser. If you cannot, this is probably a permission problem and you should follow up with the repository administrator for access. Execute `git remote -v` from a terminal to show the remote URL. 1. If you are experiencing a Git authentication problem using an editor, IDE or other tool, try performing the same operation from the terminal. Does this still fail? If the operation succeeds from the terminal please include details of the specific tool and version in any issue reports. 1. Set the environment variable `GCM_TRACE` and run the Git operation again. Find instructions in the [environment doc][env-trace]. 1. If all else fails, create an issue [here][create-issue], making sure to include the trace log. ### Q: I got an error saying unsecure HTTP is not supported To keep your data secure, Git Credential Manager will not send credentials for Azure Repos, Azure DevOps Server (TFS), GitHub, and Bitbucket, over HTTP connections that are not secured using TLS (HTTPS). Please make sure your remote URLs use "https://" rather than "http://". ### Q: I got an authentication error and I am behind a network proxy You probably need to configure Git and GCM to use a proxy. Please see detailed information in the [network config doc][netconfig-http-proxy]. ### Q: I'm getting errors about picking a credential store on Linux On Linux you must [select and configure a credential store][credstores], as due to the varied nature of distributions and installations, we cannot guarantee a suitable storage solution is available. ## About the project ### Q: How does this project relate to [Git Credential Manager for Windows][gcm-windows] and [Git Credential Manager for Mac and Linux][gcm-linux]? Git Credential Manager for Windows (GCM Windows) is a .NET Framework-based Git credential helper which runs on Windows. Likewise the Git Credential Manager for Mac and Linux (Java GCM) is a Java-based Git credential helper that runs only on macOS and Linux. Although both of these projects aim to solve the same problem (providing seamless multi-factor HTTPS authentication with Git), they are based on different codebases and languages which is becoming hard to manage to ensure feature parity. Git Credential Manager (GCM; this project) aims to replace both GCM Windows and Java GCM with a unified codebase which should be easier to maintain and enhance in the future. ### Q: Does this mean GCM for Windows (.NET Framework-based) is deprecated? Yes. Git Credential Manager for Windows (GCM Windows) is no longer receiving updates and fixes. All development effort has now been directed to GCM. GCM is available as an credential helper option in Git for Windows 2.28, and will be made the default helper in 2.29. ### Q: Does this mean the Java-based GCM for Mac/Linux is deprecated? Yes. Usage of Git Credential Manager for Mac and Linux (Java GCM) should be replaced with GCM or SSH keys. If you wish to install GCM on macOS or Linux, please follow the [download and installation instructions][download-and-install]. ### Q: I want to use SSH GCM is only useful for HTTP(S)-based remotes. Git supports SSH out-of-the box so you shouldn't need to install anything else. To use SSH please follow the below links: - [Azure DevOps][azure-ssh] - [GitHub][github-ssh] - [Bitbucket][bitbucket-ssh] ### Q: Are HTTP(S) remotes preferred over SSH? No, neither are "preferred". SSH isn't going away, and is supported "natively" in Git. ### Q: Why did you not just port the existing GCM Windows codebase from .NET Framework to .NET Core? GCM Windows was not designed with a cross-platform architecture. ### What level of support does GCM have? Support will be best-effort. We would really appreciate your feedback to make this a great experience across each platform we support. ### Q: Why does GCM not support operating system/distribution 'X', or Git hosting provider 'Y'? The likely answer is we haven't gotten around to that yet! 🙂 We are working on ensuring support for the Windows, macOS, and Ubuntu operating system, as well as the following Git hosting providers: Azure Repos, Azure DevOps Server (TFS), GitHub, and Bitbucket. We are happy to accept proposals and/or contributions to enable GCM to run on other platforms and Git host providers. Thank you! ## Technical ### Why is the `credential.useHttpPath` setting required for `dev.azure.com`? Due to the design of Git and credential helpers such as GCM, we need this setting to make Git use the full remote URL (including the path component) when communicating with GCM. The new `dev.azure.com` format of Azure DevOps URLs means the account name is now part of the path component (for example: `https://dev.azure.com/contoso/...`). The Azure DevOps account name is required in order to resolve the correct authority for authentication (which Azure AD tenant backs this account, or if it is backed by Microsoft personal accounts). In the older GCM for Windows product, the solution to the same problem was a "hack". GCM for Windows would walk the process tree looking for the `git-remote-https.exe` process, and attempt to read/parse the process environment block looking for the command line arguments (that contained the full remote URL). This is fragile and not a cross-platform solution, hence the need for the `credential.useHttpPath` setting with GCM. ### Why does GCM take so long at startup the first time? GCM will [autodetect][autodetect] what kind of Git host it's talking to. GitHub, Bitbucket, and Azure DevOps each have their own form(s) of authentication, plus there's a "generic" username and password option. For the hosted versions of these services, GCM can guess from the URL which service to use. But for on-premises versions which would have unique URLs, GCM will probe with a network call. GCM caches the results of the probe, so it should be faster on the second and later invocations. If you know which provider you're talking to and want to avoid the probe, that's possible. You can explicitly tell GCM which provider to use for a URL "example.com" like this: Provider|Command -|- GitHub|`git config --global credential.https://example.com.provider github` Bitbucket|`git config --global credential.https://example.com.provider bitbucket` Azure DevOps|`git config --global credential.https://example.com.provider azure-repos` Generic|`git config --global credential.https://example.com.provider generic` ### How do I fix "Could not create SSL/TLS secure channel" errors on Windows 7? This likely indicates that you don't have newer TLS versions available. Please [follow Microsoft's guide][enable-windows-ssh] for enabling TLS 1.1 and 1.2 on your machine, specifically the **SChannel** instructions. You'll need to be on at least Windows 7 SP1, and in the end you should have a `TLS 1.2` key with `DisabledByDefault` set to `0`. You can also read [more from Microsoft][windows-server-tls] on this change. ### How do I use GCM with Windows Subsystem for Linux (WSL)? Follow the instructions in [our WSL guide][wsl] carefully. Especially note the need to run `git config --global credential.https://dev.azure.com.useHttpPath true` _within_ WSL if you're using Azure DevOps. ### Does GCM work with multiple users? If so, how? That's a fairly complicated question to answer, but in short, yes. See [our document on multiple users][multiple-users] for details. ### How can I disable GUI dialogs and prompts? There are various environment variables and configuration options available to customize how GCM will prompt you (or not) for input. Please see the following: - [`GCM_INTERACTIVE`][env-interactive] / [`credential.interactive`][config-interactive] - [`GCM_GUI_PROMPT`][env-gui-prompt] / [`credential.guiPrompt`][config-gui-prompt] - [`GIT_TERMINAL_PROMPT`][git-term-prompt] (note this is a _Git setting_ that will affect Git as well as GCM) ### How can I extend GUI prompts/integrate prompts with my application? Application developers who use Git - think Visual Studio, GitKraken, etc. - may want to replace the GCM default UI with prompts styled to look like their application. This isn't complicated (though it is a bit of work). You can replace the GUI prompts of the Bitbucket and GitHub host providers specifically by using the `credential.gitHubHelper`/`credential.bitbucketHelper` settings or `GCM_GITHUB_HELPER`/`GCM_BITBUCKET_HELPER` environment variables. Set these variables to the path of an external helper executable that responds to the requests as the bundled UI helpers do. See the current `--help` documents for the bundled UI helpers (`GitHub.UI`/`Atlassian.Bitbucket.UI`) for more information. You may also set these variables to the empty string `""` to force terminal/ text-based prompts instead. ### How do I revoke consent for GCM for GitHub.com? In your GitHub user settings, navigate to [Integrations > Applications > Authorized OAuth Apps > Git Credential Manager][github-connected-apps] and pick "Revoke access". ![Revoke GCM OAuth app access][github-oauthapp-revoke] After revoking access, any tokens created by GCM will be invalidated and can no longer be used to access your repositories. The next time GCM attempts to access GitHub.com you will be prompted to consent again. ### I used the install from source script to install GCM on my Linux distribution. Now how can I uninstall GCM and its dependencies? Please see full instructions [here][linux-uninstall-from-src]. ### How do I revoke access for a GitLab OAuth application? There are some scenarios (e.g. updated scopes) for which you will need to manually revoke and re-authorize access for a GitLab OAuth application. You can do so by: 1. Navigating to [the **Applications** page within your **User Settings**][gitlab-apps]. 2. Scrolling to **Authorized applications**. 3. Clicking the **Revoke** button next to the name of the application for which you would like to revoke access (Git Credential Manager is used here for demonstration purposes). ![Button to revoke GitLab OAuth Application access][gitlab-oauthapp-revoke] 4. Waiting for a notification stating **The application was revoked access**. ![Notifaction of successful revocation][gitlab-oauthapp-revoked] 5. Re-authorizing the application with the new scope (GCM should automatically initiate this flow for you next time access is requested). ### Q: What do the `configure` and `unconfigure` commands do? #### `configure` The `configure` command will set up Git to use GCM exclusively as the credential helper. The `configure` command is automatically called by the installers for Windows and macOS, but you can also run it manually. It will also set Git to provide the full remote URL (including path) to credential helpers for Azure Repos remotes using the `dev.azure.com` URL format. This is required in order to be to able to correctly identify the correct authority for that Azure DevOps organization. Specifically, the `configure` command will modify your user Git configuration to include the following lines: ```ini [credential] helper = helper = [credential "https://dev.azure.com"] useHttpPath = true ``` ..where `` is the absolute path to the GCM executable. The empty `helper =` line makes sure that existing credential helpers that may be set in the system Git configuration are not used. For more details see the [credential.helper][helper-config-docs]. If you pass the `--system` option, the `configure` command will instead modify the system Git configuration. This is useful if you want to set up GCM for all users on a machine. #### `unconfigure` This command essentially undoes what the `configure` command does. It will check your Git configuration for the lines added by the `configure` command and remove them. The `unconfigure` command is run by the uninstaller for Windows and the uninstall script on macOS. On Windows, if run with the `--system` option, the `unconfigure` command will also ensure that the `credential.helper` setting in the system Git configuration is not removed and is left as `manager`, the default set by Git for Windows. [autodetect]: autodetect.md [azure-ssh]: https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops [bitbucket-ssh]: https://confluence.atlassian.com/bitbucket/ssh-keys-935365775.html [config-gui-prompt]: configuration.md#credentialguiprompt [config-interactive]: configuration.md#credentialinteractive [create-issue]: https://github.com/git-ecosystem/git-credential-manager/issues/create [credstores]: credstores.md [download-and-install]: ../README.md#download-and-install [enable-windows-ssh]: https://support.microsoft.com/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392 [env-gui-prompt]: environment.md#GCM_GUI_PROMPT [env-interactive]: environment.md#GCM_INTERACTIVE [env-trace]: environment.md#GCM_TRACE [gcm-linux]: https://github.com/Microsoft/Git-Credential-Manager-for-Mac-and-Linux [gcm-windows]: https://github.com/Microsoft/Git-Credential-Manager-for-Windows [git-term-prompt]: https://git-scm.com/docs/git#Documentation/git.txt-codeGITTERMINALPROMPTcode [github-connected-apps]: https://github.com/settings/connections/applications/0120e057bd645470c1ed [github-oauthapp-revoke]: img/github-oauthapp-revoke.png [github-ssh]: https://help.github.com/en/articles/connecting-to-github-with-ssh [gitlab-apps]: https://gitlab.com/-/profile/applications [gitlab-oauthapp-revoke]: ./img/gitlab-oauthapp-revoke.png [gitlab-oauthapp-revoked]: ./img/gitlab-oauthapp-revoked.png [helper-config-docs]: https://git-scm.com/docs/gitcredentials#Documentation/gitcredentials.txt-helper [multiple-users]: multiple-users.md [netconfig-http-proxy]: netconfig.md#http-proxy [linux-uninstall-from-src]: ./linux-fromsrc-uninstall.md [windows-server-tls]: https://docs.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/dn786418(v=ws.11)#tls-12 [wsl]: wsl.md ================================================ FILE: docs/generic-oauth.md ================================================ # Generic Host Provider OAuth Many Git hosts use the popular standard OAuth2 or OpenID Connect (OIDC) authentication mechanisms to secure repositories they host. Git Credential Manager supports any generic OAuth2-based Git host by simply setting some configuration. ## Registering an OAuth application In order to use GCM with a Git host that supports OAuth you must first have registered an OAuth application with your host. The instructions on how to do this can be found with your Git host provider's documentation. When registering a new application, you should make sure to set an HTTP-based redirect URL that points to `localhost`; for example: ```text http://localhost http://localhost: http://127.0.0.1 http://127.0.0.1: ``` Note that you cannot use an HTTPS redirect URL. GCM does not require a specific port number be used; if your Git host requires you to specify a port number in the redirect URL then GCM will use that. Otherwise an available port will be selected at the point authentication starts. You must ensure that all scopes required to read and write to Git repositories have been granted for the application or else credentials that are generated will cause errors when pushing or fetching using Git. As part of the registration process you should also be given a Client ID and, optionally, a Client Secret. You will need both of these to configure GCM. ## Configure GCM In order to configure GCM to use OAuth with your Git host you need to set the following values in your Git configuration: - Client ID - Client Secret (optional) - Redirect URL (optional, defaults to `http://127.0.0.1`) - Scopes (optional) - OAuth Endpoints - Authorization Endpoint - Token Endpoint - Device Code Authorization Endpoint (optional) OAuth endpoints can be found by consulting your Git host's OAuth app development documentation. The URLs can be either absolute or relative to the host name; for example: `https://example.com/oauth/authorize` or `/oauth/authorize`. In order to set these values, you can run the following commands, where `` is the hostname of your Git host: ```shell git config --global credential..oauthClientId git config --global credential..oauthClientSecret git config --global credential..oauthRedirectUri git config --global credential..oauthAuthorizeEndpoint git config --global credential..oauthTokenEndpoint git config --global credential..oauthScopes git config --global credential..oauthDeviceEndpoint ``` **Example commands:** - `git config --global credential.https://example.com.oauthClientId C33F2751FB76` - `git config --global credential.https://example.com.oauthScopes "code:write profile:read"` **Example Git configuration** ```ini [credential "https://example.com"] oauthClientId = 9d886e36-5771-4f2b-8c8b-420c68ad5baa oauthClientSecret = 4BC5BD4704EAE28FD832 oauthRedirectUri = "http://127.0.0.1" oauthAuthorizeEndpoint = "/login/oauth/authorize" oauthTokenEndpoint = "/login/oauth/token" oauthDeviceEndpoint = "/login/oauth/device" oauthScopes = "code:write profile:read" oauthDefaultUserName = "OAUTH" oauthUseClientAuthHeader = false ``` ### Additional configuration Depending on the specific implementation of OAuth with your Git host you may also need to specify additional behavior. #### Token user name If your Git host requires that you specify a username to use with OAuth tokens you can either include the username in the Git remote URL, or specify a default option via Git configuration. Example Git remote with username: `https://username@example.com/repo.git`. In order to use special characters you need to URL encode the values; for example `@` becomes `%40`. By default GCM uses the value `OAUTH-USER` unless specified in the remote URL, or overridden using the `credential..oauthDefaultUserName` configuration. #### Include client authentication in headers If your Git host's OAuth implementation has specific requirements about whether the client ID and secret should or should not be included in an `Authorization` header during OAuth requests, you can control this using the following setting: ```shell git config --global credential..oauthUseClientAuthHeader ``` The default behavior is to include these values; i.e., `true`. ================================================ FILE: docs/github-apideprecation.md ================================================ # GitHub Authentication Deprecation ## What's going on? GitHub now [requires token-based authentication][token-auth] to call their APIs, and in the future, use Git itself. This means Git credential helpers such as [Git Credential Manager (GCM) for Windows][gcm-windows], and old versions of [GCM][gcm] that offer username/password flows **will not be able to create new access tokens** for accessing Git repositories. If you already have tokens generated by Git credential helpers like GCM for Windows, they will continue to work until they expire or are revoked/deleted. ## What should I do now? ### Windows command-line users The best thing to do right now is upgrade to the latest Git for Windows (at least version 2.29), which includes a version of Git Credential Manager that uses supported OAuth token-based authentication. [Download the latest Git for Windows ⬇️][git-windows] ### Visual Studio users Please update to the latest supported release of Visual Studio, that includes GCM and support for OAuth token-based authentication. - [Visual Studio 2019 ⬇️][vs-2019] - [Visual Studio 2017 ⬇️][vs-2017] ### SSH, macOS, and Linux users If you are using SSH this change does **not** affect you. If you are using an older version of Git Credential Manager (before 2.0.124-beta) please upgrade to the latest version following [these instructions][gcm-install]. ## What if I cannot upgrade Git for Windows? If you are unable to upgrade Git for Windows, you can manually install Git Credential Manager as a standalone install. This will override the older, GCM for Windows bundled with the Git for Windows installation. [Download Git Credential Manager standalone ⬇️][gcm-latest] ## What if I cannot use Git Credential Manager? If you are unable to use Git Credential Manager due to a bug or compatibility issue we'd [like to know why][gcm-new-issue]! ## Help! I cannot make any changes to my Windows machine without an Administrator If you do not have permission to change your installation (for example in a corporate environment) you can use the per-user installer. Check out the [latest release][gcm-latest] and download the `gcmcoreuser-win-*.exe` executable. ### Help! I still cannot or don't want to install anything There is a workaround which should work and doesn't require installing anything. 1. Tell your system administrator they should start planning to upgrade the installed version of Git for Windows to at least 2.29! 😁 1. [Create a new personal access token][github-pat] (see official [documentation][github-pat-docs]) 1. Enter a name ("note") for the token and ensure the `repo`, `gist`, and `workflow` scopes are selected: ![image][github-pat-note-image] ... ![image][github-pat-repo-scope-image] ... ![image][github-pat-gist-scope-image] ... ![image][github-pat-workflow-scope-image] 1. Click "Generate Token" ![image][github-generate-pat-image] 1. **[IMPORTANT]** Keep the resulting page open as this contains your new token (this will only be displayed once!) ![image][github-display-pat-image] 1. Save the generated PAT in the Windows Credential Manager: 1. If you prefer to use the command-line, open a command prompt (cmd.exe) and type the following: ```bash cmdkey /generic:git:https://github.com /user:PersonalAccessToken /pass ``` You will be prompted to enter a password – copy the newly generated PAT in step 4 and paste it here, and press the `Enter` key ![image][windows-cli-save-pat-image] 1. If you do not wish to use the command-line, [open the Credential Manager via Control Panel][windows-credential-manager] and select the "Windows Credentials" tab. ![image][windows-gui-credentials-image] Click "Add a generic credential", and enter the following details: - Internet or network address: `git:https://github.com` - Username: `PersonalAccessToken` - Password: _(copy and paste the PAT generated in step 4 here)_ ![image][windows-gui-add-pat-image] ## What about GitHub Enterprise Server (GHES)? As mentioned in [the blog post][github-token-authentication-requirements], the new token-based authentication requirements **DO NOT** apply to GHES: > We have not announced any changes to GitHub Enterprise Server, which remains > unaffected at this time. [token-auth]: https://github.blog/2020-07-30-token-authentication-requirements-for-api-and-git-operations/ [gcm]: https://aka.ms/gcm [gcm-install]: ../README.md#download-and-install [gcm-latest]: https://aka.ms/gcm/latest [gcm-new-issue]: https://github.com/git-ecosystem/git-credential-manager/issues/new/choose [gcm-windows]: https://github.com/microsoft/Git-Credential-Manager-for-Windows [git-windows]: https://git-scm.com/download/win [github-display-pat-image]: img/github-display-pat.png [github-generate-pat-image]: img/github-generate-pat.png [github-pat]: https://github.com/settings/tokens/new?scopes=repo,gist,workflow [github-pat-docs]: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token [github-pat-gist-scope-image]: img/github-pat-gist-scope.png [github-pat-note-image]: img/github-pat-note.png [github-pat-repo-scope-image]: img/github-pat-repo-scope.png [github-pat-workflow-scope-image]: img/github-pat-workflow-scope.png [github-token-authentication-requirements]: https://github.blog/2020-07-30-token-authentication-requirements-for-api-and-git-operations/ [windows-cli-save-pat-image]: img/windows-cli-save-pat.png [vs-2019]: https://docs.microsoft.com/en-us/visualstudio/install/update-visual-studio?view=vs-2019 [vs-2017]: https://docs.microsoft.com/en-us/visualstudio/install/update-visual-studio?view=vs-2017 [windows-credential-manager]: https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0 [windows-gui-add-pat-image]: img/windows-gui-add-pat.png [windows-gui-credentials-image]: img/windows-gui-credentials.png ================================================ FILE: docs/gitlab.md ================================================ # GitLab support Git Credential Manager supports [gitlab.com][gitlab] out the box. ## Using on another instance To use on another instance, eg. `https://gitlab.example.com` requires setup and configuration: 1. [Create an OAuth application][gitlab-oauth]. This can be at the user, group or instance level. Specify a name and use a redirect URI of `http://127.0.0.1/`. _Unselect_ the 'Confidential' option. Set the 'read_repository' and 'write_repository' scopes. 1. Copy the application ID and configure `git config --global credential.https://gitlab.example.com.gitLabDevClientId ` 1. Copy the application secret and configure `git config --global credential.https://gitlab.example.com.gitLabDevClientSecret ` 1. Optional if you want to force browser auth: `git config --global credential.https://gitlab.example.com.gitLabAuthModes browser` 1. For good measure, configure `git config --global credential.https://gitlab.example.com.provider gitlab`. This may be necessary to recognise the domain as a GitLab instance. 1. Verify the config is as expected `git config --global --get-urlmatch credential https://gitlab.example.com` ### Clearing config ```console git config --global --unset-all credential.https://gitlab.example.com.gitLabDevClientId git config --global --unset-all credential.https://gitlab.example.com.gitLabDevClientSecret git config --global --unset-all credential.https://gitlab.example.com.provider ``` ### Config for popular instances For convenience, here are the config commands for several popular GitLab instances, provided by community member [hickford](https://github.com/hickford/): ```console # https://gitlab.freedesktop.org/ git config --global credential.https://gitlab.freedesktop.org.gitLabDevClientId 6503d8c5a27187628440d44e0352833a2b49bce540c546c22a3378c8f5b74d45 git config --global credential.https://gitlab.freedesktop.org.gitLabDevClientSecret 2ae9343a034ff1baadaef1e7ce3197776b00746a02ddf0323bb34aca8bff6dc1 # https://gitlab.gnome.org/ git config --global credential.https://gitlab.gnome.org.gitLabDevClientId adf21361d32eddc87bf6baf8366f242dfe07a7d4335b46e8e101303364ccc470 git config --global credential.https://gitlab.gnome.org.gitLabDevClientSecret cdca4678f64e5b0be9febc0d5e7aab0d81d27696d7adb1cf8022ccefd0a58fc0 # https://invent.kde.org/ git config --global credential.https://invent.kde.org.gitLabDevClientId cd7cb4342c7cd83d8c2fcc22c87320f88d0bde14984432ffca07ee24d0bf0699 git config --global credential.https://invent.kde.org.gitLabDevClientSecret 9cc8440b280c792ac429b3615ae1c8e0702e6b2479056f899d314f05afd94211 # https://salsa.debian.org/ git config --global credential.https://salsa.debian.org.gitLabDevClientId 213f5fd32c6a14a0328048c0a77cc12c19138cc165ab957fb83d0add74656f89 git config --global credential.https://salsa.debian.org.gitLabDevClientSecret 3616b974b59451ecf553f951cb7b8e6e3c91c6d84dd3247dcb0183dac93c2a26 # https://gitlab.haskell.org/ git config --global credential.https://gitlab.haskell.org.gitLabDevClientId 57de5eaab72b3dc447fca8c19cea39527a08e82da5377c2d10a8ebb30b08fa5f git config --global credential.https://gitlab.haskell.org.gitLabDevClientSecret 5170a480da8fb7341e0daac94223d4fff549c702efb2f8873d950bb2b88e434f # https://code.videolan.org/ git config --global credential.https://code.videolan.org.gitLabDevClientId f35c379241cc20bf9dffecb47990491b62757db4fb96080cddf2461eacb40375 git config --global credential.https://code.videolan.org.gitLabDevClientSecret 631558ec973c5ef65b78db9f41103f8247dc68d979c86f051c0fe4389e1995e8 ``` See also [issue #677](https://github.com/git-ecosystem/git-credential-manager/issues/677). ## Preferences ```console Select an authentication method for 'https://gitlab.com/': 1. Web browser (default) 2. Personal access token 3. Username/password option (enter for default): ``` If you have a preferred authentication mode, you can specify [credential.gitLabAuthModes][config-gitlab-auth-modes]: ```console git config --global credential.gitLabAuthModes browser ``` ## Caveats Improved support requires changes in GitLab. Please vote for these issues if they affect you: 1. No support for OAuth device authorization (necessary for machines without web browser): [GitLab issue 332682][gitlab-issue-332682] 1. Preconfigure Git Credential Manager as instance-wide OAuth application: [GitLab issue 374172](gitlab-issue-374172) 1. Username/password authentication is suggested even if disabled on server: [GitLab issue 349463][gitlab-issue-349463] [config-gitlab-auth-modes]: configuration.md#credential.gitLabAuthModes [gitlab]: https://gitlab.com [gitlab-issue-332682]: https://gitlab.com/gitlab-org/gitlab/-/issues/332682 [gitlab-issue-374172]: https://gitlab.com/gitlab-org/gitlab/-/issues/374172 [gitlab-issue-349463]: https://gitlab.com/gitlab-org/gitlab/-/issues/349463 [gitlab-oauth]: https://docs.gitlab.com/ee/integration/oauth_provider.html ================================================ FILE: docs/hostprovider.md ================================================ # Git Credential Manager Host Provider ## Abstract Git Credential Manger, the cross-platform and cross-host Git credential helper, can be extended to support any Git hosting service allowing seamless authentication to secured Git repositories by implementing and registering a "host provider". ## 1. Introduction Git Credential Manager (GCM) is a host and platform agnostic Git credential helper application. Support for authenticating to any Git hosting service can be added to GCM by creating a custom "host provider" and registering it within the product. Host providers can be submitted via a pull request on [the Git Credential Manager repository on GitHub][gcm]. This document outlines the required and expected behaviour of a host provider, and what is required to implement and register one. ### 1.1. Notational Conventions The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this specification are to be interpreted as described in [[RFC2119][rfc-2119]]. ### 1.2. Abbreviations Throughout this document you may see multiple abbreviations of product names and security or credential objects. "Git Credential Manager" is abbreviated to "GCM". "Git Credential Manager for Windows" is abbreviated to "GCM for Windows" or "GCM Windows". "Git Credential Manager for Mac & Linux" is abbreviated to "GCM for Mac/Linux" or "GCM Mac/Linux". OAuth2 [[RFC6749][rfc-6749]] "access tokens" are abbreviated to "ATs" and "refresh tokens" to "RTs". "Personal Access Tokens" are abbreviated to "PATs". ## 2. Implementation Writing and adding a host provider to GCM requires two main actions: implementing the `IHostProvider` interface, and registering an instance of the provider with the application via the host provider registry. Host providers MUST implement the `IHostProvider` interface. They can choose to directly implement the interface they MAY derive from the `HostProvider` abstract class (which itself implements the `IHostProvider` interface) - see [2.6][hostprovider-base-class]. Implementors MUST implement all interface properties and abstract methods. The `Id` and `Name` properties MUST be implemented and MUST NOT return default or empty values. The `Id` field MUST be unique over the set of all providers, or else an error will be thrown at registration time. The `Id` field MAY be a unique random string of characters and digits such as a UUID, but it is RECOMMENDED to use a human-readable value consisting of letter characters in the range \[a-z\] only. The `Name` property MUST be a human readable string and MUST identify the Git hosting service this provider supports. The `SupportedAuthorityIds` property MUST return an instance of an object and NOT a `null` reference. Populating this collection with values is OPTIONAL but highly RECOMMENDED. You should return a set of stable identifiers of all authorities that the provider supports authentication against. ### 2.1. Registration Host providers must provide an instance of their `IHostProvider` type to the GCM application host provider registry to be considered for handling requests. The main GCM `Application` object has one principal registry which you can register providers with by calling the `RegisterProvider` method. #### 2.1.2. Ordering The default host provider registry in GCM has multiple priority levels that host providers can be registered at: High, Normal, and Low. For each priority level (starting with High, then Normal, then Low), the registry will call each host provider in the order they were registered in, unless the user has overridden the provider selection process. There are no rules or restrictions on the ordering of host providers, except that the `GenericHostProvider` MUST be registered last and at the Low priority. The generic provider is a catch-all provider implementation that will handle any request in a standard way. ### 2.2. Handling Requests The `IsSupported(InputArguments)` method will be called on all registered host providers in-turn on the invocation of a `get`, `store`, or `erase` request. The first host provider to return `true` will be called upon to handle the specific request. If the user has overridden the host provider selection process, a specific host provider may be selected instead, and the `IsSupported(InputArguments)` method will NOT be called. This method MUST return `true` if and only if the provider understands the request and can serve or handle the request. If the provider does not know how to handle the request it MUST return `false` instead. If no host provider returns `true` to a call to the `IsSupported(InputArguments)` method for a each host provider priority level, then a HTTP HEAD request will be made to the remote URL and each host provider will be be called via the `IsSupported(HttpResponseMessage)` method. A host provider SHOULD use this call to check for recognised on-premises instances (for example, by inspecting response headers) and return `true` if it wishes to be called upon to handle the credential request, otherwise it MUST return `false`. Host providers SHOULD NOT make further network calls if possible during any of the `IsSupported` method overloads to avoid degrading the performance of the overall application. #### 2.2.1. Rejecting Requests The `IsSupported` methods MUST return `true` if the host provider would like to cancel the authentication operation based on the current context or input. For example, if provider requires a secure protocol but the requested protocol for a supported hostname is `http` and not `https`. Host providers MUST instead cancel the request from the `GetCredentialAsync` method by throwing an `Exception`. Implementors MUST provide detailed information regarding the reason why the authentication cannot continue, for example "HTTP is not secure, please use HTTPS". ### 2.3. Retrieving Credentials The `GetCredentialAsync` method will be called when a `get` request is made. The method MUST return an instance of an `ICredential` capable of fulfilling the specific access request. The argument passed to `GetCredentialAsync` contains properties indicating the required `protocol` and `host` for this request. The `username` and `path` properties are OPTIONAL, however if they are present, they MUST be considered and used to direct the authentication. The host provider MAY attempt to locate any existing credential, stored by the `StoreCredentialAsync` method, before resorting to the creation a new one. The host provider MAY choose to check if a stored credential is still valid by inspecting any stored metadata associated with the value. A host provider MAY also choose to further validate a retrieved stored credential by making a web request. However, it is NOT RECOMMENDED to make any request that is known to be slow or that typically produces inconclusive validation results. If a provider chooses to make a validation web request and that request fails or is inconclusive, it SHOULD assume the credential is still valid and return it anyway, letting Git (the caller) attempt to use it and validate it itself. The returned `ICredential` MAY leave both the username and password values as the empty string or `null`. This signals to Git (or rather cURL) that it should negotiate the authentication mechanism with the remote itself. This is typically used for Windows Integrated Authentication. #### 2.3.1 Authentication Prompts When it is not possible to locate an existing credential suitable for the current request, a host provider SHOULD prompt the user to complete an authentication flow. The method, modes, and interactions for performing authentication will vary widely between Git hosting services and their supported authentication authorities. A host provider SHOULD attempt to detect the best authentication experience given the current environment or context, and select that one to attempt first. Host providers are RECOMMENDED to attempt authentication mechanisms that do not require user interaction if possible. If there are multiple authentication mechanisms that could be equally considered "best" they MAY prompt the user to make a selection. Host providers MAY wish to remember such a selection for future use, however they MUST make it clear how to clear this stored selection to the user. If interaction is required to complete authentication a host provider MUST first check if interaction has been disabled (`ISettings.IsInteractionAllowed`), and an exception MUST be thrown if interaction has been disallowed. Authentication prompts that display a graphical user interface such as a window are MUST be preferred when an interactive "desktop" session is available. If an authentication prompt is required when an interactive session is not available and a terminal/TTY is attached then a provider MUST first check if terminal prompts are enabled (`ISettings.IsTerminalPromptsEnabled`), and an exception MUST be thrown if interaction has been disallowed. ### 2.4. Storing Credentials Host providers MAY store credentials at various stages of a typical authentication flow, or when explicitly requested to do so in a call to `StoreCredentialAsync`. Providers SHOULD use the credential store (exposed as `ICredentialStore`) to persist secret values and credential entities such as passwords, PATs and OAuth tokens. The typical Git credential helper call pattern is one call to `get`, followed by either a `store` request in case of a HTTP 200 (OK) response, or `erase` in case of HTTP 401 (Unauthorized) response. In some cases there is additional context that is present as part of the `get` request or during the generation of a new credential that is not present during the subsequent call to `store` (or `erase`). In these cases providers MAY store the credential during the `get` rather than, or as well as during the `store`. Host providers MAY store multiple credentials or tokens in the same request if it is required. One example where multiple credential storage is needed is with OAuth2 access tokens (AT) and refresh tokens (RT). Both the AT and RT SHOULD be stored in the same location using the credential store with complementary credential service names. ### 2.5. Erasing Credentials If host providers have stored credentials in the credential store, they MUST respond to requests to erase them in calls to `EraseCredentialAsync`. If a host provider cannot locate a credential to erase it MUST NOT raise an error and MUST exit successfully. A warning message MAY be emitted to the tracing system. Host providers MUST NOT perform their own repeated validation of credentials for the purposes of ignoring the request to erase them. The ultimate authority on the validity of a credential is the caller (Git). Providers MAY validate any additional or ancillary credentials (such as OAuth RTs) are still valid when a request to erase the primary credential (such as an OAuth AT) is made, and choose not to delete those additional credentials. The primary credential MUST still always be erased in all cases. ### 2.6 `HostProvider` base class The `HostProvider` abstract base class is provided for the convenience of host provider implementors. This base class implements most required methods of the `IHostProvider` interface with common credential recall and storage behaviour. The `GetCredentialAsync`, `StoreCredentialAsync`, and `EraseCredentialAsync` methods are implemented as `virtual` meaning they MAY be overridden by derived classes to customise the behaviour of those operations. It is NOT RECOMMENDED to derive from the `HostProvider` base class if the implementor must override most of the methods as implemented - implementors SHOULD implement the `IHostProvider` interface directly instead. Implementors that choose to derive from this base class MUST implement all abstract methods and properties. The primary abstract method to implement is `GenerateCredentialAsync`. There is also an additional `virtual` method named `GetServiceName` that is used by the default implementations of the `Get|Store|EraseCredentialAsync` methods to locate and store credentials. #### 2.6.1 `GetServiceName` The `GetServiceName` virtual method, if overriden, MUST return a string that identifies the service/provider for this request, and is used for storing credentials. The value returned MUST be stable - i.e, it MUST return the same value given the same or equivalent input arguments. By default this method returns the full remote URI, without a trailing slash, including protocol/scheme, hostname, and path if present in the input arguments. Any username in the input arguments is never included in the URI. #### 2.6.2 `GenerateCredentialAsync` The `GenerateCredentialAsync` method will be called if an existing credential with a matching service (from `GetServiceName`) and account is not found in the credential store. This method MUST return a freshly created/generated credential and not any existing or stored one. It MAY use existing or stored ancillary data or tokens, such as OAuth refresh tokens, to generate the new token (such as an OAuth AT). ### 2.7. External Metadata Host providers MAY wish to store extra data about authentications or users collected or produced during authentication operations. These SHOULD be stored in a per-user, local location such as the user's home or profile directory. Secrets, credentials or other sensitive data SHOULD be stored in the credential store, or otherwise protected by some form of per-user, local encryption. In the case of stored data caches, providers SHOULD invalidate relevant parts of, or the entire cache, when a call to `EraseCredentialAsync` is made. ## 3. Helpers Host providers MAY wish to make use of platform or operating system specific features such as native APIs and native graphical user interfaces, in order to offer a better authentication experience. Host providers MUST function without the presence of a helper, even if that function is to fail gracefully with a user-friendly error message, including a remedy to correct their installation. Host providers SHOULD always offer a terminal/TTY or text-based authentication mechanism alongside any graphical interface provided by a helper. In order to achieve this host providers MUST introduce an out-of-process "helper" executable that can be invoked from the main GCM process. This allows the "helper" executable full implementation freedom of runtime, language, etc. Communications between the main and helper processes MAY use any IPC mechanism available. It is RECOMMENDED implementors use standard input/output streams or file descriptors to send and receive data as this is consistent with how Git and GCM communicate. UNIX sockets or Windows Named Pipes MAY also be used when an ongoing back-and-forth communication is required. ### 3.1. Discovery It is RECOMMENDED that helper discovery is achieved by simply checking for the presence of the expected executable file. The name and path of the helper executable SHOULD be configurable by the user via Git's configuration files. ## 4. Error Handling If an unrecoverable error occurs a host provider MUST throw an exception and MUST include detailed failure information in the error message. If the reason for failure can be fixed by the user the error message MUST include instructions to fix the problem, or a link to online documentation. In the case of a recoverable error, host providers SHOULD print a warning message to the standard error stream, and MUST include the error information and the recovery steps take in the trace log. In the case of an authentication error, providers SHOULD attempt to prompt the user again with a message indicating the incorrect authentication details have been entered. ## 5. Custom Commands If a host provider wishes to surface custom commands the SHOULD implement the `ICommandProvider` interface. Each provider is given the opportunity to create a single `ProviderCommand` instance to which further sub-commands can be parented to. Commanding is provided by the `System.CommandLine` API library [[1][references]]. There are no limitations on what format sub-commands, arguments, or options must take, but implementors SHOULD attempt to follow existing practices and styles. ## References 1. [`System.CommandLine` API][github-dotnet-cli] [gcm]: https://github.com/git-ecosystem/git-credential-manager [github-dotnet-cli]: https://github.com/dotnet/command-line-api [hostprovider-base-class]: #26-hostprovider-base-class [references]: #references [rfc-2119]: https://www.rfc-editor.org/rfc/rfc2119 [rfc-6749]: https://www.rfc-editor.org/rfc/rfc6749 ================================================ FILE: docs/install.md ================================================ # Install instructions There are multiple ways to install GCM on macOS, Windows, and Linux. Preferred installation methods for each OS are designated with a :star:. ## macOS ### Homebrew :star: **Note:** If you have an existing installation of the 'Java GCM' on macOS and you have installed this using Homebrew, this installation will be unlinked (`brew unlink git-credential-manager`) when GCM is installed. #### Install ```shell brew install --cask git-credential-manager ``` After installing you can stay up-to-date with new releases by running: ```shell brew upgrade --cask git-credential-manager ``` #### Uninstall To uninstall, run the following: ```shell brew uninstall --cask git-credential-manager ``` --- ### macOS Package #### Install Download and double-click the [installation package][latest-release] and follow the instructions presented. #### Uninstall To uninstall, run the following: ```shell sudo /usr/local/share/gcm-core/uninstall.sh ``` --- ## Linux **Note:** all Linux distributions [require additional configuration][gcm-credstores] to use GCM. --- ### .NET tool :star: See the [.NET tool](#net-tool) section below for instructions on this installation method. --- ### Debian package #### Install Download the latest [.deb package][latest-release]*, and run the following: ```shell sudo dpkg -i git-credential-manager configure ``` #### Uninstall ```shell git-credential-manager unconfigure sudo dpkg -r gcm ``` *If you'd like to validate the package's signature after downloading, check out the instructions [here][linux-validate-gpg-debian]. --- ### Tarball #### Install Download the latest [tarball][latest-release]*, and run the following: ```shell tar -xvf -C /usr/local/bin git-credential-manager configure ``` #### Uninstall ```shell git-credential-manager unconfigure rm $(command -v git-credential-manager) ``` *If you would like to validate the tarball's signature after downloading, check out the instructions [here][linux-validate-gpg-tarball]. --- ### Install from source helper script #### Install Ensure `curl` is installed: ```shell curl --version ``` If `curl` is not installed, please use your distribution's package manager to install it. Download and run the script: ```shell curl -L https://aka.ms/gcm/linux-install-source.sh | sh git-credential-manager configure ``` **Note:** You will be prompted to enter your credentials so that the script can download GCM's dependencies using your distribution's package manager. #### Uninstall [Follow these instructions][linux-uninstall] for your distribution. --- ## Windows ### Git for Windows :star: GCM is included with [Git for Windows][git-for-windows]. During installation you will be asked to select a credential helper, with GCM listed as the default. ![image][git-for-windows-gcm-screenshot] --- ### Standalone installation You can also download the [latest installer][latest-release] for Windows to install GCM standalone. **:warning: Important :warning:** Installing GCM as a standalone package on Windows will forcibly override the version of GCM that is bundled with Git for Windows, **even if the version bundled with Git for Windows is a later version**. There are two flavors of standalone installation on Windows: - User (`gcmuser-win*`): Does not require administrator rights. Will install only for the current user and updates only the current user's Git configuration. - System (`gcm-win*`): Requires administrator rights. Will install for all users on the system and update the system-wide Git configuration. To install, double-click the desired installation package and follow the instructions presented. ### Uninstall (Windows 10) To uninstall, open the Settings app and navigate to the Apps section. Select "Git Credential Manager" and click "Uninstall". ### Uninstall (Windows 7-8.1) To uninstall, open Control Panel and navigate to the Programs and Features screen. Select "Git Credential Manager" and click "Remove". ### Windows Subsystem for Linux (WSL) Git Credential Manager can be used with the [Windows Subsystem for Linux (WSL)][ms-wsl] to enable secure authentication of your remote Git repositories from inside of WSL. [Please see the GCM on WSL docs][gcm-wsl] for more information. --- ## .NET tool GCM is available to install as a cross-platform [.NET tool][dotnet-tool]. This is the preferred install method for Linux because you can use it to install on any [.NET-supported distribution][dotnet-supported-distributions]. You can also use this method on macOS if you so choose. **Note:** Make sure you have installed [version 8.0 of the .NET SDK][dotnet-install] before attempting to run the following `dotnet tool` commands. After installing, you will also need to follow the output instructions to add the tools directory to your `PATH`. #### Install ```shell dotnet tool install -g git-credential-manager git-credential-manager configure ``` #### Update ```shell dotnet tool update -g git-credential-manager ``` #### Uninstall ```shell git-credential-manager unconfigure dotnet tool uninstall -g git-credential-manager ``` [dotnet-install]: https://learn.microsoft.com/en-us/dotnet/core/install/linux#packages [dotnet-supported-distributions]: https://learn.microsoft.com/en-us/dotnet/core/install/linux [dotnet-tool]: https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools [gcm-credstores]: credstores.md [gcm-wsl]: wsl.md [git-for-windows]: https://gitforwindows.org/ [git-for-windows-gcm-screenshot]: img/git-for-windows-gcm-screenshot.png [latest-release]: https://github.com/git-ecosystem/git-credential-manager/releases/latest [linux-uninstall]: linux-fromsrc-uninstall.md [linux-validate-gpg-debian]: ./linux-validate-gpg.md#debian-package [linux-validate-gpg-tarball]: ./linux-validate-gpg.md#tarball [ms-wsl]: https://aka.ms/wsl# ================================================ FILE: docs/linux-fromsrc-uninstall.md ================================================ # Uninstalling after installing from source These instructions will guide you in removing GCM after running the [install from source script][install-from-source] on your Linux distribution. :rotating_light: PROCEED WITH CAUTION :rotating_light: For completeness, we provide uninstall instructions for _the GCM application, the GCM repo, and the maximum number of dependencies*_ for all distributions. This repo and these dependencies may or may not have already been present on your system when you ran the install from source script, and uninstalling them could impact other programs and/or your normal workflows. Please keep this in mind when following the instructions below. *Certain distributions require some dependencies of the script to function as expected, so we only include instructions to remove the non-required dependencies. ## All distributions **Note:** If you ran the install from source script from a pre-existing clone of the `git-credential-manager` repo or outside of your `$HOME` directory, you will need to modify the final two commands below to point to the location of your pre-existing clone or the directory from which you ran the install from source script. ```console git-credential-manager unconfigure && sudo rm $(command -v git-credential-manager) && sudo rm -rf /usr/local/share/gcm-core && sudo rm -rf ~/git-credential-manager && sudo rm ~/install-from-source.sh ``` ## Debian/Ubuntu **Note:** If you had a pre-existing installation of dotnet that was not installed via `apt` or `apt-get` when you ran the install from source script, you will need to remove it using [these instructions][uninstall-dotnet] and remove `dotnet-*` from the below command. ```console sudo apt remove dotnet-* dpkg-dev apt-transport-https git curl wget ``` ## Linux Mint **Note:** If you had a pre-existing installation of dotnet when you ran the install from source script that was not located at `~/.dotnet`, you will need to modify the first command below to point to the custom install location. If you would like to remove the specific version of dotnet that the script installed and keep other versions, you can do so with [these instructions][uninstall-dotnet]. ```console sudo rm -rf ~/.dotnet && sudo apt remove git curl ``` ## Fedora/CentOS/RHEL **Note:** If you had a pre-existing installation of dotnet when you ran the install from source script that was not located at `~/.dotnet`, you will need to modify the first command below to point to the custom install location. If you would like to remove the specific version of dotnet that the script installed and keep other versions, you can do so with [these instructions][uninstall-dotnet]. ```console sudo rm -rf ~/.dotnet ``` ## Alpine **Note:** If you had a pre-existing installation of dotnet when you ran the install from source script that was not located at `~/.dotnet`, you will need to modify the first command below to point to the custom install location. If you would like to remove the specific version of dotnet that the script installed and keep other versions, you can do so with [these instructions][uninstall-dotnet]. ```console sudo rm -rf ~/.dotnet && sudo apk del icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib which bash coreutils gcompat git curl ``` [install-from-source]: ../src/linux/Packaging.Linux/install-from-source.sh [uninstall-dotnet]: https://docs.microsoft.com/en-us/dotnet/core/install/remove-runtime-sdk-versions?pivots=os-linux#uninstall-net ================================================ FILE: docs/linux-validate-gpg.md ================================================ # Validating GCM's GPG signature Follow the below instructions to import GCM's public key and use it to validate the latest Debian package and/or tarball signature. ## Debian package ```shell # Install needed packages apt-get install -y curl debsig-verify # Download public key signature file curl -s https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest \ | grep -E 'browser_download_url.*gcm-public.asc' \ | cut -d : -f 2,3 \ | tr -d \" \ | xargs -I 'url' curl -L -o gcm-public.asc 'url' # De-armor public key signature file gpg --output gcm-public.gpg --dearmor gcm-public.asc # Note that the fingerprint of this key is "3C853823978B07FA", which you can # determine by running: gpg --show-keys gcm-public.asc | head -n 2 | tail -n 1 | tail -c 17 # Copy de-armored public key to debsig keyring folder mkdir /usr/share/debsig/keyrings/3C853823978B07FA mv gcm-public.gpg /usr/share/debsig/keyrings/3C853823978B07FA/ # Create an appropriate policy file mkdir /etc/debsig/policies/3C853823978B07FA cat > /etc/debsig/policies/3C853823978B07FA/generic.pol << EOL EOL # Download Debian package curl -s https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest \ | grep "browser_download_url.*deb" \ | cut -d : -f 2,3 \ | tr -d \" \ | xargs -I 'url' curl -L -o gcm.deb 'url' # Verify debsig-verify gcm.deb ``` ## Tarball ```shell # Download the public key signature file curl -s https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest \ | grep -E 'browser_download_url.*gcm-public.asc' \ | cut -d : -f 2,3 \ | tr -d \" \ | xargs -I 'url' curl -L -o gcm-public.asc 'url' # Import the public key gpg --import gcm-public.asc # Download the tarball and its signature file curl -s https://api.github.com/repos/ldennington/git-credential-manager/releases/latest \ | grep -E 'browser_download_url.*gcm-linux.*[0-9].[0-9].[0-9].tar.gz' \ | cut -d : -f 2,3 \ | tr -d \" \ | xargs -I 'url' curl -LO 'url' # Trust the public key echo -e "5\ny\n" | gpg --command-fd 0 --expert --edit-key 3C853823978B07FA trust # Verify the signature gpg --verify gcm-linux_amd64*.tar.gz.asc gcm-linux*.tar.gz ``` ================================================ FILE: docs/migration.md ================================================ # Migration Guide ## Migrating from Git Credential Manager for Windows ### GCM_AUTHORITY This setting (and the corresponding `credential.authority` configuration) is deprecated and should be replaced with the `GCM_PROVIDER` (or corresponding `credential.authority` configuration) setting. Because both Basic HTTP authentication and Windows Integrated Authentication (WIA) are now handled by one provider, if you specified `basic` as your authority you also need to disable WIA using `GCM_ALLOW_WINDOWSAUTH` / `credential.allowWindowsAuth`. The following table shows the correct replacement for all legacy authorities values: GCM_AUTHORITY (credential.authority)|→|GCM_PROVIDER (credential.provider)|GCM_ALLOW_WINDOWSAUTH (credential.allowWindowsAuth) -|-|-|- `msa`, `microsoft`, `microsoftaccount`, `aad`, `azure`, `azuredirectory`, `live`, `liveconnect`, `liveid`|→|`azure-repos`|_N/A_ `github`|→|`github`|_N/A_ `basic`|→|`generic`|`false` `integrated`, `windows`, `kerberos`, `ntlm`, `tfs`, `sso`|→|`generic`|`true` _(default)_ For example if you had previous set the authority for the `example.com` host to `basic`.. ```shell git config --global credential.example.com.authority basic ``` ..then you can replace this with the following.. ```shell git config --global --unset credential.example.com.authority git config --global credential.example.com.provider generic git config --global credential.example.com.allowWindowsAuth false ``` ================================================ FILE: docs/multiple-users.md ================================================ # Multiple users If you work with multiple different identities on a single Git hosting service, you may be wondering if Git Credential Manager (GCM) supports this workflow. The answer is yes, with a bit of complexity due to how it interoperates with Git. --- **Prompted to select an account?** Read the [**TL;DR** section][tldr] below for a quick summary of how to make GCM remember which account to use for which repository. --- ## Foundations: Git and Git hosts Git itself doesn't have a single, strong concept of "user". There's the `user.name` and `user.email` which get embedded into commit headers/trailers, but these are arbitrary strings. GCM doesn't interact with this notion of a user at all. You can put whatever you want into your `user.*` config, and nothing in GCM will change at all. Separate from the user strings in commits, Git recognizes the "user" part of a remote URL or a credential. These are not often used, at least by default, in the web UI of major Git hosts. Git hosting providers (like GitHub or Bitbucket) _do_ have a concept of "user". Typically it's an identity like a username or email address, plus a password or other credential to perform actions as that user. You may have guessed by now that GCM (the Git **Credential** Manager) does work with this notion of a user. ## People, identities, credentials, oh my You (a physical person) may have one or more user accounts (identities) with one or more Git hosting providers. Since most Git hosts don't put a "user" part in their URLs, by default, Git will treat the user part for a remote as the empty string. If you have multiple identities on one domain, you'll need to insert a unique user part per-identity yourself. There are good reasons for having multiple identities on one domain. You might use one GitHub identity for your personal work, another for your open source work, and a third for your employer's work. You can ask Git to assign a different credential to different repositories hosted on the same provider. HTTPS URLs include an optional "name" part before an `@` sign in the domain name, and you can use this to force Git to distinguish multiple users. This should likely be your username on the Git hosting service, since there are cases where GCM will use it like a username. ## Setting it up As an example, let's say you're working on multiple repositories hosted at the same domain name. | Repo URL | Identity | |----------|----------| | `https://example.com/open-source/library.git` | `contrib123` | | `https://example.com/more-open-source/app.git` | `contrib123` | | `https://example.com/big-company/secret-repo.git` | `employee9999` | When you clone these repos, include the identity and an `@` before the domain name in order to force Git and GCM to use different identities. If you've already cloned the repos, you can update the remote URL to include the identity. ### Example: fresh clones ```shell # instead of `git clone https://example.com/open-source/library.git`, run: git clone https://contrib123@example.com/open-source/library.git # instead of `git clone https://example.com/big-company/secret-repo.git`, run: git clone https://employee9999@example.com/big-company/secret-repo.git ``` ### Example: existing clones ```shell # in the `library` repo, run: git remote set-url origin https://contrib123@example.com/open-source/library.git # in the `secret-repo` repo, run: git remote set-url origin https://employee9999@example.com/big-company/secret-repo.git ``` ## Azure DevOps [Azure DevOps has some additional, optional complexity][azure-access-tokens] which you should also be aware of if you're using it. [azure-access-tokens]: azrepos-users-and-tokens.md ## GitHub You can use the `github [list | login | logout]` commands to manage your GitHub accounts. These commands are documented in the [command-line usage][cli-usage] or by running `git credential-manager github --help`. ## TL;DR: Tell GCM to remember which account to use To set a default account for a particular remote you can simply set the following Git configuration: ```shell git config --global credential..username ``` ..where `` is the remote URL and `` is the account you wish to have as the default. For example, for `github.com` and the user `alice`: ```shell git config --global credential.https://github.com.username alice ``` If you wish to set a user for a specific repository or remote URL, you can include the account name in the remote URL. If you're using HTTPS remotes, you can include the account name in the URL by inserting it before the `@` sign in the domain name. For example, if you want to always use the `alice` account for the `mona/test` GitHub repository, you can clone it using the `alice` account by running: ```shell git clone https://alice@github.com/mona/test ``` To update an existing clone, you can run `git remote set-url` to update the URL: ```shell git remote set-url origin https://alice@github.com/mona/test ``` If your account name includes an `@` then remember to escape this character using `%40`: `https://alice%40contoso.com@example.com/test`. [tldr]: #tldr-tell-gcm-to-remember-which-account-to-use [cli-usage]: usage.md ================================================ FILE: docs/netconfig.md ================================================ # Network and HTTP configuration Git Credential Manager's network and HTTP(S) behavior can be configured in a few different ways via [environment variables][environment] and [configuration options][configuration]. ## HTTP Proxy If your computer sits behind a network firewall that requires the use of a proxy server to reach repository remotes or the wider Internet, there are various methods for configuring GCM to use a proxy. The simplest way to configure a proxy for _all_ HTTP(S) remotes is to [use the standard Git HTTP(S) proxy setting `http.proxy`][git-http-proxy]. For example to configure a proxy for all remotes for the current user: ```shell git config --global http.proxy http://proxy.example.com ``` To specify a proxy for a particular remote you can [use the `remote..proxy` repository-level setting][git-remote-name-proxy], for example: ```shell git config --local remote.origin.proxy http://proxy.example.com ``` The advantage to using these standard configuration options is that in addition to GCM being configured to use the proxy, Git itself will be configured at the same time. This is probably the most commonly desired case in environments behind an Internet-blocking firewall. ### Authenticated proxies Some proxy servers do not accept anonymous connections and require authentication. In order to specify the credentials to be used with a proxy, you can specify the username and password as part of the proxy URL setting. The format follows [RFC 3986 section 3.2.1][rfc-3986-321] by including the credentials in the 'user information' part of the URI. The password is optional. ```text protocol://username[:password]@hostname ``` For example, to specify the username `john.doe` and the password `letmein123` for the proxy server `proxy.example.com`: ```text https://john.doe:letmein123@proxy.example.com ``` If you have special characters (as defined by [RFC 3986 section 2.2][rfc-3986-22]) in your username or password such as `:`, `@`, or any other non-URL friendly character you can URL-encode them ([section 2.1][rfc-3986-21]). For example, a space character would be encoded with `%20`. ### Other proxy options GCM supports other ways of configuring a proxy for convenience and compatibility. 1. GCM-specific configuration options (_**only** respected by GCM; **deprecated**_): - `credential.httpProxy` - `credential.httpsProxy` 1. cURL environment variables (_also respected by Git_): - `http_proxy` - `https_proxy`/`HTTPS_PROXY` - `all_proxy`/`ALL_PROXY` 1. `GCM_HTTP_PROXY` environment variable (_**only** respected by GCM; **deprecated**_) Note that with the cURL environment variables there are both lowercase and uppercase variants. **_Lowercase variants take precedence over the uppercase form._** This is consistent with how libcurl (and therefore Git) operates. The `http_proxy` variable exists only in the lowercase variant and libcurl does _not_ consider any uppercase form. _GCM also reflects this behavior._ See [the curl docs][curl-proxy-env-vars] for more information. ### Bypassing addresses In some circumstances you may wish to bypass a configured proxy for specific addresses. GCM supports the cURL environment variable `no_proxy` (and `NO_PROXY`) for this scenario, as does Git itself. Like with the [other cURL proxy environment variables][other-proxy-options], the lowercase variant will take precedence over the uppercase form. This environment variable should contain a comma-separated or space-separated list of host names that should not be proxied (should connect directly). GCM attempts to match [libcurl's behaviour][curlopt-noproxy], which is briefly summarized here: - a value of `*` disables proxying for all hosts; - other wildcard use is **not** supported; - each name in the list is matched as a domain which contains the hostname, or the hostname itself - a leading period/dot `.` matches against the provided hostname For example, setting `NO_PROXY` to `example.com` results in the following: Hostname|Matches? -|- `example.com`|:white_check_mark: `example.com:80`|:white_check_mark: `www.example.com`|:white_check_mark: `notanexample.com`|:x: `www.notanexample.com`|:x: `example.com.othertld`|:x: **Example:** ```text no_proxy="contoso.com,www.fabrikam.com" ``` ## TLS Verification If you are using self-signed TLS (SSL) certificates with a self-hosted host provider such as GitHub Enterprise Server or Azure DevOps Server (previously TFS), you may see the following error message when attempting to connect using Git and/or GCM: ```shell $ git clone https://ghe.example.com/john.doe/myrepo fatal: The remote certificate is invalid according to the validation procedure. ``` The **recommended and safest option** is to acquire a TLS certificate signed by a public trusted certificate authority (CA). There are multiple public CAs; here is a non-exhaustive list to consider: [Let's Encrypt][lets-encrypt], [Comodo][comodo], [Digicert][digicert], [GoDaddy][godaddy], [GlobalSign][globalsign]. If it is not possible to **obtain a TLS certificate from a trusted 3rd party** then you should try to add the _specific_ self-signed certificate or one of the CA certificates in the verification chain to your operating system's trusted certificate store ([macOS][mac-keychain-access], [Windows][install-cert-vista]). If you are _unable_ to either **obtain a trusted certificate**, or trust the self-signed certificate you can disable certificate verification in Git and GCM. --- **Security Warning** :warning: Disabling verification of TLS (SSL) certificates removes protection against a [man-in-the-middle (MITM) attack][mitm-attack]. Only disable certificate verification if you are sure you need to, are aware of all the risks, and are unable to trust specific self-signed certificates (as described above). --- The [environment variable `GIT_SSL_NO_VERIFY`][git-ssl-no-verify] and [Git configuration option `http.sslVerify`][git-http-ssl-verify] can be used to control TLS (SSL) certificate verification. To disable verification for a specific remote (for example `https://example.com`): ```shell git config --global http.https://example.com.sslVerify false ``` To disable verification for the current user for **_all remotes_** (**not recommended**): ```shell # Environment variable (Windows) SET GIT_SSL_NO_VERIFY=1 # Environment variable (macOS/Linux) export GIT_SSL_NO_VERIFY=1 # Git configuration (Windows/macOS/Linux) git config --global http.sslVerify false ``` --- **Note:** You may also experience similar verification errors if you are using a network traffic inspection tool such as [Telerik Fiddler][telerik-fiddler]. If you are using such tools please consult their documentation for trusting the proxy root certificates. --- ## Unsafe Remote URLs If you are using a remote URL that is not considered safe, such as unencrypted HTTP (remote URLs that start with `http://`), host providers may prevent you from authenticating with your credentials. In this case, you should consider using a HTTPS (starting with `https://`) remote URL to ensure your credentials are transmitted securely. If you accept the risks associated with using an unsafe remote URL, you can configure GCM to allow the use of unsafe remote URLS by setting the environment variable [`GCM_ALLOW_UNSAFE_REMOTES`][unsafe-envar], or by using the Git configuration option [`credential.allowUnsafeRemotes`][unsafe-config] to `true`. [environment]: environment.md [configuration]: configuration.md [git-http-proxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy [git-remote-name-proxy]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-remoteltnamegtproxy [rfc-3986-321]: https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1 [rfc-3986-22]: https://www.rfc-editor.org/rfc/rfc3986#section-2.2 [rfc-3986-21]: https://www.rfc-editor.org/rfc/rfc3986#section-2.1 [curl-proxy-env-vars]: https://everything.curl.dev/usingcurl/proxies#proxy-environment-variables [other-proxy-options]: #other-proxy-options [curlopt-noproxy]: https://curl.se/libcurl/c/CURLOPT_NOPROXY.html [lets-encrypt]: https://letsencrypt.org/ [comodo]: https://www.comodoca.com/ [digicert]: https://www.digicert.com/ [godaddy]: https://www.godaddy.com/ [globalsign]: https://www.globalsign.com [mac-keychain-access]: https://support.apple.com/en-gb/guide/keychain-access/kyca2431/mac [install-cert-vista]: https://blogs.technet.microsoft.com/sbs/2008/05/08/installing-a-self-signed-certificate-as-a-trusted-root-ca-in-windows-vista/ [mitm-attack]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack [git-ssl-no-verify]: https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_networking [git-http-ssl-verify]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslVerify [telerik-fiddler]: https://www.telerik.com/fiddler [unsafe-envar]: environment.md#gcm_allow_unsafe_remotes [unsafe-config]: configuration.md#credentialallowunsaferemotes ================================================ FILE: docs/ntlm-kerberos.md ================================================ # NTLM and Kerberos Authentication ## Background NTLM and Kerberos are two authentication protocols that are commonly used in Windows environments. In Git Credential Manager (GCM), we refer to these protocols under the umbrella term "Windows Integrated Authentication". ### NTLM [NTLM (NT LAN Manager)][ntlm-wiki] is a challenge-response authentication protocol used in various Microsoft network protocols, such as [SMB file sharing][smb-docs]. > [!CAUTION] > NTLM is now considered _**insecure**_ due to weak cryptographic algorithms and > vulnerabilities to various attacks, such as pass-the-hash and relay attacks. > As such, it is not recommended for use in modern applications. > > There are several versions of NTLM, with NTLMv2 being the latest, however > **all versions** are considered weak by modern security standards. > > Microsoft lists [NTLM as a deprecated protocol][ntlm-deprecated] and has > removed NTLMv1 from Windows as of Windows 11 build 24H2 / Server 2025. NTLM is advertised by HTTP servers using the `WWW-Authenticate: NTLM` header. When a client receives this header, it can respond with an NTLM authentication message to prove its identity. ### Kerberos [Kerberos][kerberos-wiki], on the other hand, is a more secure and robust authentication protocol that uses tickets to authenticate users and services. It is the recommended authentication protocol for Windows domains and is widely used in enterprise environments. Unlike NTLM, Kerberos is typically not directly advertised by HTTP servers, but is instead advertised using "SPNEGO" and the `WWW-Authenticate: Negotiate` header. #### GSS-API Negotiate and SPNEGO Kerberos (or NTLM) authentication is typically initially established using the [GSS-API][gssapi-wiki] ([RFC 2743][gssapi-rfc]) negotiation mechanism ["SPNEGO"][spnego-wiki] ([RFC 4178][spnego-rfc]). SPNEGO allows the client and server to agree on which authentication protocol to use (Kerberos or NTLM) based on their capabilities. Typically Kerberos is preferred if both the client and server support it, with NTLM acting as a fallback. ## Built-in Support in Git Git provides built-in support for NTLM and Kerberos authentication through the use of [libcurl][libcurl], which is the underlying library used by Git for HTTP and HTTPS communications. When Git is compiled with libcurl support, it can leverage the authentication mechanisms provided by libcurl, including NTLM and Kerberos. On Windows, Git can use the native Windows [SSPI][sspi-wiki] (Security Support Provider Interface) to perform NTLM and Kerberos authentication. This allows Git to integrate seamlessly with the Windows authentication infrastructure. > [!NOTE] > As of Git for Windows version 2.XX.X, **NTLM support is disabled by default**. > Kerberos support _remains enabled_. ### Re-enabling NTLM Support You can re-enable NTLM support in Git for Windows for a particular remote by setting Git config option [`http..allowNTLMAuth`][ntlm-config] to `true`. For example, to enable NTLM authentication for `https://example.com`, you would run the following command: ```shell git config --global http.https://example.com.allowNTLMAuth true ``` > [!WARNING] > Enabling NTLM authentication may expose you to security risks, as NTLM is > considered insecure. It is recommended to use Kerberos authentication where > possible, and to only use NTLM with trusted servers in secure environments. > [!WARNING] > Only ever use NTLM authentication over secure connections (i.e., HTTPS) to > protect against eavesdropping and man-in-the-middle attacks. When using GCM with a remote that supports NTLM authentication, GCM will warn you if NTLM authentication is not enabled in Git but the remote server advertises NTLM support. ![GCM warning prompt that NTLM is disabled inside of Git][ntlm-warning-image] * Selecting "Just this time" will continue with NTLM authentication, but only for the current operation. The next time you interact with that remote, you will be prompted again. * Selecting "Always for this remote" will set the `http..allowNTLMAuth` configuration option to `true` for that remote, and continue with NTLM authentication. * Selecting "No" will prompt for a basic username/password credential, and Git's NTLM authentication support will remain disabled. If the remote server only supports NTLM then authentication will fail. ### Seamless Authentication When using NTLM or Kerberos authentication with Git on Windows, it is possible to achieve seamless authentication without prompting for credentials. This is because Git can leverage the existing Windows user credentials to authenticate with the server. This means that if you are logged into your Windows account, Git can use those credentials to authenticate with the remote server automatically, without prompting you for a username or password. This feature is enabled by default in Git. To disable this behavior, you can set the [`http..emptyAuth`][emptyauth] configuration option to `false`. For example, to disable seamless authentication for `https://example.com`, you would run the following command: ```shell git config --global http.https://example.com.emptyAuth false ``` If you disable seamless authentication, Git will prompt you for credentials when accessing a remote that advertises NTLM or Kerberos support rather than using the current Windows user's credentials. [ntlm-wiki]: https://en.wikipedia.org/wiki/NTLM [kerberos-wiki]: https://en.wikipedia.org/wiki/Kerberos_(protocol) [smb-docs]: https://learn.microsoft.com/en-gb/windows/win32/fileio/microsoft-smb-protocol-and-cifs-protocol-overview [ntlm-deprecated]: https://learn.microsoft.com/en-us/windows/whats-new/deprecated-features [ntlm-config]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpallowNTLMAuth [gssapi-rfc]: https://datatracker.ietf.org/doc/html/rfc2743 [gssapi-wiki]: https://en.wikipedia.org/wiki/GSSAPI [spnego-rfc]: https://datatracker.ietf.org/doc/html/rfc4178 [spnego-wiki]: https://en.wikipedia.org/wiki/SPNEGO [libcurl]: https://curl.se/libcurl/ [sspi-wiki]: https://en.wikipedia.org/wiki/Security_Support_Provider_Interface [emptyauth]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpemptyAuth [ntlm-warning-image]: img/ntlm-warning.png ================================================ FILE: docs/rename.md ================================================ # Git Credential Manager Rename In November 2021, _"Git Credential Manager Core"_ was [renamed][rename-pr] to simply _"Git Credential Manager"_, dropping the "Core" moniker. We announced the new name in a [GitHub blog post][rename-blog], along with the new home for the project in its own [organization][gcm-org]. ![Git Credential Manager Core renamed](img/gcmcore-rename.png) At the time, the actual exectuable name was not updated and continued to be `git-credential-manager-core`. As of [2.0.877][rename-ver], the executable has been renamed to `git-credential-manager`, matching the new project name. --- :warning: **Update:** :warning: As of [2.3.0][no-symlink-ver] the `git-credential-manager-core` symlinks have been removed. If you have not updated your configuration you will see error messages similar to: ```console git: 'credential-manager-core' is not a git command. See 'git --help'. ``` To fix your configuration, please follow the [instructions][instructions] below. --- ## Rename transition If you continue to use the `git-credential-manager-core` executable name you may see warning messages like below: ```console warning: git-credential-manager-core was renamed to git-credential-manager warning: see https://aka.ms/gcm/rename for more information ``` Since the executable was renamed in 2.0.877, GCM has also included symlinks using the old name in order to ensure no one's setups would immediately break. These links will remain until _two_ major Git versions are released after GCM 2.0.877, _**at which point the symlinks will no longer be included**_. It is recommended to update your Git configuration to use the new executable name as soon as possible to prevent any issues in the future. ## How to update ### Git for Windows If you are using GCM bundled with Git for Windows (recommended), you should make sure you have updated to the latest version. [Download the latest Git for Windows ⬇️][git-windows] ### Windows standalone installer If you are using GCM installed either by the user (`gcmuser-*.exe`) or system (`gcm-*.exe`) installers on Windows, you should uninstall the current version first and then download and install the [latest version][gcm-latest]. Uninstall instructions for your Windows version can be found [here][win-standalone-instr]. ### macOS Homebrew > **Note:** As of October 2022 the old `git-credential-manager-core` cask name > is still used. In the future we plan to rename the package to drop the `-core` > suffix. If you use Homebrew to install GCM on macOS you should use `brew upgrade` to install the latest version. ```sh brew upgrade git-credential-manager-core ``` ### macOS package If you use the .pkg file to install GCM on macOS, you should first uninstall the current version, and then install the [latest package][gcm-latest]. ```sh sudo /usr/local/share/gcm-core/uninstall.sh installer -pkg -target / ``` ### Linux Debian package If you use the .deb Debian package to install GCM on Linux, you should first `unconfigure` the current version, uninstall the package, and then install and `configure` the [latest version][gcm-latest]. ```sh git-credential-manager-core unconfigure sudo dpkg -r gcmcore sudo dpkg -i git-credential-manager configure ``` ### Linux tarball If you are using the pre-built GCM binaries on Linux from our tarball, you should first `unconfigure` the current version before extracting the [latest binaries][gcm-latest]. ```sh git-credential-manager-core unconfigure rm $(command -v git-credential-manager-core) tar -xvf -C /usr/local/bin git-credential-manager configure ``` ### Troubleshooting If after updating your GCM installations if you are still seeing the [warning][warnings] messages you can try manually editing your Git configuration to point to the correct GCM executable name. Start by listing all Git configuration for `credential.helper`, including which files the particular config entries are located in, using the following command: ```sh git config --show-origin --get-all credential.helper ``` On Mac or Linux you should see something like this: ```shell-session $ git config --show-origin --get-all credential.helper file:/opt/homebrew/etc/gitconfig credential.helper=osxkeychain file:/Users/jdoe/.gitconfig credential.helper= file:/Users/jdoe/.gitconfig credential.helper=/usr/local/share/gcm-core/git-credential-manager-core ``` On Windows you should see something like this: ```shell-session > git config --show-origin --get-all credential.helper file:C:/Program Files/Git/etc/gitconfig credential.helper=manager-core ``` Look out for entries that include `git-credential-manager-core` or `manager-core`; these should be replaced and updated to `git-credential-manager` or `manager` respectively. > **Note:** When updating the Git configuration file in your home directory > (`$HOME/.gitconfig` or `%USERPROFILE%\.gitconfig`) you should ensure there are > is an additional blank entry for `credential.helper` before the GCM entry. > > **Mac/Linux** > > ```ini > [credential] > helper = > helper = /usr/local/share/gcm-core/git-credential-manager > ``` > > **Windows** > > ```ini > [credential] > helper = > helper = C:/Program\\ Files\\ \\(x86\\)/Git\\ Credential\\ Manager/git-credential-manager.exe > ``` > > The blank entry is important as it makes sure GCM is the only credential > helper that is configured, and overrides any helpers configured at the system/ > machine-wide level. [rename-pr]: https://github.com/git-ecosystem/git-credential-manager/pull/541 [rename-blog]: https://github.blog/2022-04-07-git-credential-manager-authentication-for-everyone/#universal-git-authentication [gcm-org]: https://github.com/git-ecosystem [rename-ver]: https://github.com/git-ecosystem/git-credential-manager/releases/tag/v2.0.877 [git-windows]: https://git-scm.com/download/win [gcm-latest]: https://aka.ms/gcm/latest [warnings]: #rename-transition [win-standalone-instr]: ../README.md#standalone-installation [instructions]: #how-to-update [no-symlink-ver]: https://github.com/git-ecosystem/git-credential-manager/releases/tag/v2.3.0 ================================================ FILE: docs/usage.md ================================================ # Command-line usage After installation, Git will use Git Credential Manager and you will only need to interact with any authentication dialogs asking for credentials. GCM stays invisible as much as possible, so ideally you’ll forget that you’re depending on GCM at all. Assuming GCM has been installed, use your favorite terminal to execute the following commands to interact directly with GCM. ```shell git credential-manager [ []] ``` ## Commands ### --help / -h / -? Displays a list of available commands. ### --version Displays the current version. ### get / store / erase Commands for interaction with Git. You shouldn't need to run these manually. Read the [Git manual][git-credentials-custom-helpers] about custom helpers for more information. ### configure/unconfigure Set your user-level Git configuration (`~/.gitconfig`) to use GCM. If you pass `--system` to these commands, they act on the system-level Git configuration (`/etc/gitconfig`) instead. ### azure-repos Interact with the Azure Repos host provider to bind/unbind user accounts to Azure DevOps organizations or specific remote URLs, and manage the authentication authority cache. For more information about managing user account bindings see [here][azure-access-tokens-ua]. [azure-access-tokens-ua]: azrepos-users-and-tokens.md#useraccounts [git-credentials-custom-helpers]: https://git-scm.com/docs/gitcredentials#_custom_helpers ### github Interact with the GitHub host provider to manage your accounts on GitHub.com and GitHub Enterprise Server instances. ================================================ FILE: docs/windows-broker.md ================================================ # Web Account Manager integration Git Credential Manager (GCM) knows how to integrate with the [Web Account Manager (WAM)][azure-refresh-token-terms] feature of Windows. GCM uses WAM to store credentials for Azure DevOps. Authentication requests are said to be "brokered" to the operating system. Currently, GCM will share authentication state with a few other Microsoft developer tools like Visual Studio and the Azure CLI, meaning fewer authentication prompts. Enabling WAM integration may also be required with certain [Conditional Access policies][azure-conditional-access], which enterprises use to help protect their assets, including source code. Integration with the WAM broker offers convenience and other benefits, but may also make unexpected other changes on your device. On a device owned and managed by your institution or employer, WAM is probably the right choice. On a personal device or a device owned by a different institution (e.g. if you're a contractor working for Company A with access to resources at Company B), there are surprising behaviors that you should be aware of before enabling WAM integration. Note that this only affects [Azure DevOps][azure-devops]. It doesn't impact authentication with GitHub, Bitbucket, or any other Git host. ## How to enable You can opt-in to WAM support by setting the environment variable [`GCM_MSAUTH_USEBROKER`][GCM_MSAUTH_USEBROKER] or setting the Git configuration value [`credential.msauthUseBroker`][credential.msauthUseBroker]. ## Features When you turn on WAM support, GCM can cooperate with Windows and with other WAM-enabled software on your machine. This means a more seamless experience, fewer multi-factor authentication prompts, and the ability to use additional authentication technologies like smart cards and Windows Hello. These convenience and security features make a good case for enabling WAM. ## Using the current OS account by default Enabling WAM does not currently automatically use the current Windows account for authentication. In order to opt-in to this behavior you can set the [`GCM_MSAUTH_USEDEFAULTACCOUNT`][GCM_MSAUTH_USEDEFAULTACCOUNT] environment variable or set the [`credential.msauthUseDefaultAccount`][credential.msauthUseDefaultAccount] Git configuration value to `true`. In certain cloud hosted environments when using a work or school account, such as [Microsoft Dev Box][devbox], this setting is **_automatically enabled_**. To disable this behavior, set the environment variable [`GCM_MSAUTH_USEDEFAULTACCOUNT`][GCM_MSAUTH_USEDEFAULTACCOUNT] or the [`credential.msauthUseDefaultAccount`][credential.msauthUseDefaultAccount] Git configuration value explicitly to `false`. ## Surprising behaviors The WAM and Windows identity systems are complex, addressing a very broad range of customer use cases. What works for a solo home user may not be adequate for a corporate-managed fleet of 100,000 devices and vice versa. The GCM team isn't responsible for the user experience or choices made by WAM, but by integrating with WAM, we inherit some of those choices. Therefore, we want you to be aware of some defaults and experiences if you choose to use WAM integration. ### For work or school accounts (Azure AD-backed identities) When you sign into an Azure DevOps organization backed by Azure AD (often your company or school email), if your machine is already joined to Azure AD matching that Azure DevOps organization, you'll get a seamless and easy-to-use experience. If your machine isn't Azure AD-joined, or is Azure AD-joined to a different tenant, WAM will present you with a dialog box suggesting you stay signed in and allow the organization to manage your device. The dialog box has changed a bit in various versions of Windows; here are two examples from 2021: ![Consent dialog pre-21H1][aad-questions] ![Consent dialog post-21H1][aad-questions-21h1] Depending on what you click, one of three things can happen: - If you leave "allow my organization to manage my device" checked and click "OK", your computer will be registered with the Azure AD tenant backing the organization. It may also be MDM-enrolled ("Mobile Device Management" -- think Intune, AirWatch, MobileIron, etc.), meaning an administrator can deploy policies to your machine: requiring certain kinds of sign-in, turning on antivirus and firewall software, and enabling BitLocker. Your identity will also be available to other apps on the computer for signing in, some of which may do so automatically. ![Example of policies pushed to an Intune-enrolled device][aad-bitlocker] - If you uncheck "allow my organization to manage my device" and click "OK", your computer will be registered with Azure AD but will not be MDM-enrolled. Your identity will be available to other apps on the computer for signing in. Other apps may log you in automatically or prompt you again to allow your organization to manage your device. Despite joining Azure AD, your organization's Conditional Access policies may still prevent you from accessing Azure DevOps. If so, you'll be prompted with instructions on how to enroll in MDM. - If you instead click "No, sign in to this app only", your machine will not be joined to Azure AD or MDM-enrolled, so no policies can be enforced, and your identity won't be made available to other apps on the computer. Similar to the above, your organization's Conditional Access policies may prevent you from proceeding. If Conditional Access is required to access your organization's Git repositories, you can [enable WAM integration][GCM_MSAUTH_USEBROKER] (or follow other instructions your organization provides). #### Removing device management If you've allowed your computer to be managed and want to undo it, you can go into **Settings**, **Accounts**, **Access work or school**. In the section where you see your email address and organization name, click **Disconnect**. ![Finding your work or school account][aad-work-school] ![Disconnecting from Azure AD][aad-disconnect] ### For Microsoft accounts When you sign into an Azure DevOps organization backed by Microsoft account (MSA) identities (email addresses like `@outlook.com` or `@gmail.com` fall into this category), you may be prompted to select an existing "work or school account" or use a different one. In order to sign in with an MSA you should continue and select "Use a different [work or school] account", but enter your MSA credentials when prompted. This is due to a configuration outside of our control. We expect this experience to improve over time and a "personal account" option to be presented in the future. ![Initial dialog to choose an existing or different account][ms-sign-in] If you've connected your MSA to Windows or signed-in to other Microsoft applications such as Office, then you may see this account listed in the authentication prompts when using GCM. --- ⚠️ **Important** ⚠️ When adding a new MSA to Windows, you'll be asked to select whether to use this account across all of your device (**option 1**), or only permit Microsoft-apps to access your identity (**option 2**). If you opt to use the account everywhere, then your local Windows user account will be connected to that MSA. This means you'll need to use your MSA credentials to sign in to Windows going forward. Selecting "just this app" or "Microsoft apps only" will still allow you to use this MSA across apps in Windows, but will not require you to use your MSA credentials to sign in to Windows. ![Confirmation to connect your MSA to Windows][msa-confirm] To disconnect an MSA added using option 1, you can go into **Settings**, **Accounts**, **Your info** and click **Stop signing in to all Microsoft apps automatically**. ![Remove your Microsoft account from Windows][msa-remove] For MSAs added for "Microsoft apps only", you can modify whether or not these accounts are available to other applications, and also remove the accounts from **Settings**, **Accounts**, **Emails & accounts**: ![Allow all Microsoft apps to access your identity][all-ms-apps] ![Microsoft apps must ask to access your identity][apps-must-ask] ## Running as administrator ### GCM 2.1 and later From version 2.1 onwards, GCM uses a version of the [Microsoft Authentication Library (MSAL)][msal-dotnet] that supports use of the Windows broker from an elevated process. ### Previous versions The Windows broker ("WAM") makes heavy use of [COM][ms-com], a remote procedure call (RPC) technology built into Windows. In order to integrate with WAM, Git Credential Manager and the underlying [Microsoft Authentication Library (MSAL)][msal-dotnet] must use COM interfaces and RPCs. When you run Git Credential Manager as an elevated process, some of the calls made between GCM and WAM may fail due to differing process security levels. This can happen when you run `git` from an Administrator command-prompt or perform Git operations from Visual Studio running as Administrator. If you've enabled using the broker, GCM will check whether it's running in an elevated process. If it is, GCM will automatically attempt to modify the COM security settings for the running process so that GCM and WAM can work together. However, this automatic process security change is not guaranteed to succeed. Various external factors like registry or system-wide COM settings may cause it to fail. If GCM can't modify the process's COM security settings, GCM prints a warning message and won't be able to use the broker. ```text warning: broker initialization failed Failed to set COM process security to allow Windows broker from an elevated process (0x80010119). See https://aka.ms/gcm/wamadmin for more information. ``` ### Possible solutions In order to fix the problem, there are a few options: 1. Update to the [latest Git for Windows][git-for-windows-latest] **(recommended)**. 2. Run Git or Git Credential Manager from non-elevated processes. 3. Disable the broker by setting the [`GCM_MSAUTH_USEBROKER`][GCM_MSAUTH_USEBROKER] environment variable or the [`credential.msauthUseBroker`][credential.msauthUseBroker] Git configuration setting to `false`. [azure-refresh-token-terms]: https://docs.microsoft.com/azure/active-directory/devices/concept-primary-refresh-token#key-terminology-and-components [azure-conditional-access]: https://docs.microsoft.com/azure/active-directory/conditional-access/overview [azure-devops]: https://azure.microsoft.com/en-us/products/devops [GCM_MSAUTH_USEBROKER]: environment.md#GCM_MSAUTH_USEBROKER-experimental [GCM_MSAUTH_USEDEFAULTACCOUNT]: environment.md#GCM_MSAUTH_USEDEFAULTACCOUNT-experimental [credential.msauthUseBroker]: configuration.md#credentialmsauthusebroker-experimental [credential.msauthUseDefaultAccount]: configuration.md#credentialmsauthusedefaultaccount-experimental [aad-questions]: img/aad-questions.png [aad-questions-21h1]: img/aad-questions-21H1.png [aad-bitlocker]: img/aad-bitlocker.png [aad-work-school]: img/aad-work-school.png [aad-disconnect]: img/aad-disconnect.png [ms-sign-in]: img/get-signed-in.png [all-ms-apps]: img/all-microsoft.png [apps-must-ask]: img/apps-must-ask.png [ms-com]: https://docs.microsoft.com/en-us/windows/win32/com/the-component-object-model [msa-confirm]: img/msa-confirm.png [msa-remove]: img/msa-remove.png [msal-dotnet]: https://aka.ms/msal-net [devbox]: https://azure.microsoft.com/en-us/products/dev-box [git-for-windows-latest]: https://git-scm.com/download/win ================================================ FILE: docs/wsl.md ================================================ # Windows Subsystem for Linux (WSL) GCM can be used with the [Windows Subsystem for Linux (WSL)][wsl], both WSL1 and WSL2, by following these instructions. In order to use GCM with WSL you must be on Windows 10 Version 1903 or later. This is the first version of Windows that includes the required `wsl.exe` tool that GCM uses to interoperate with Git in your WSL distributions. It is highly recommended that you install Git for Windows to both install GCM and enable the best experience sharing credentials & settings between WSL and the Windows host. Alternatively, you must be using GCM version 2.0.XXX or later and configure the `WSLENV` environment variable as [described below][configuring-wsl-without-git-for-windows]. ## Configuring WSL with Git for Windows (recommended) Start by installing the [latest Git for Windows ⬇️][latest-git-for-windows] _Inside your WSL installation_, run the following command to set GCM as the Git credential helper: ```shell git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe" ``` > **Note:** the location of git-credential-manager.exe may be different in your installation of Git for Windows. If you intend to use Azure DevOps you must _also_ set the following Git configuration _inside of your WSL installation_. ```shell git config --global credential.https://dev.azure.com.useHttpPath true ``` ## Configuring WSL without Git for Windows If you wish to use GCM inside of WSL _without installing Git for Windows_ you must complete additional configuration so that GCM can callback to Git inside of your WSL installation. Start by installing the [latest GCM for Windows⬇️][latest-gcm] _Inside your WSL installation_, run the following command to set GCM as the Git credential helper: ```shell git config --global credential.helper "/mnt/c/Program\ Files\ \(x86\)/Git\ Credential\ Manager/git-credential-manager.exe" # For Azure DevOps support only git config --global credential.https://dev.azure.com.useHttpPath true ``` In **_Windows_** you need to update the `WSLENV` environment variable to include the value `GIT_EXEC_PATH/wp`. From an _Administrator_ Command Prompt run the following: ```batch SETX WSLENV %WSLENV%:GIT_EXEC_PATH/wp ``` After updating the `WSLENV` environment variable, restart your WSL installation. ### Using the user-only GCM installer? If you have installed GCM using the user-only installer (i.e, the `gcmuser-*.exe` installer and not the system-wide/admin required installer), you need to modify the above instructions to point to `/mnt/c/Users//AppData/Local/Programs/Git\ Credential\ Manager/git-credential-manager.exe` instead. ## How it works GCM leverages the built-in interoperability between Windows and WSL, provided by Microsoft. You can read more about Windows/WSL interop [here][wsl-interop]. Git inside of a WSL installation can launch the GCM _Windows_ application transparently to acquire credentials. Running GCM as a Windows application allows it to take full advantage of the host operating system for storing credentials securely, and presenting GUI prompts for authentication. Using the host operating system (Windows) to store credentials also means that your Windows applications and WSL distributions can all share those credentials, removing the need to sign-in multiple times. ## Shared configuration Using GCM as a credential helper for a WSL Git installation means that any configuration set in WSL Git is NOT respected by GCM (by default). This is because GCM is running as a Windows application, and therefore will use the Git for Windows installation to query configuration. This means things like proxy settings for GCM need to be set in Git for Windows as well as WSL Git as they are stored in different files (`%USERPROFILE%\.gitconfig` vs `\\wsl$\distro\home\$USER\.gitconfig`). You can configure WSL such that GCM will use the WSL Git configuration following the [instructions above][configuring-wsl-without-git-for-windows]. However, this then means that things like proxy settings are unique to the specific WSL installation, and not shared with others or the Windows host. ## Can I install Git Credential Manager directly inside of WSL? Yes. Rather than install GCM as a Windows application (and have WSL Git invoke the Windows GCM), can you install GCM as a Linux application instead. To do this, simply follow the [GCM installation instructions for Linux][linux-installation]. **Note:** In this scenario, because GCM is running as a Linux application it cannot utilize authentication or credential storage features of the host Windows operating system. [wsl]: https://aka.ms/wsl [configuring-wsl-without-git-for-windows]: #configuring-wsl-without-git-for-windows [latest-git-for-windows]: https://github.com/git-for-windows/git/releases/latest [latest-gcm]: https://aka.ms/gcm/latest [wsl-interop]: https://docs.microsoft.com/en-us/windows/wsl/interop [linux-installation]: ../README.md#linux ================================================ FILE: global.json ================================================ { "sdk": { "rollForward": "latestMajor", "version": "8.0" } } ================================================ FILE: nuget.config ================================================ ================================================ FILE: src/linux/Directory.Build.props ================================================  $(RepoOutPath)linux\ $(PlatformOutPath)$(MSBuildProjectName)\ $(ProjectOutPath)bin\ $(ProjectOutPath)obj\ ================================================ FILE: src/linux/Packaging.Linux/Packaging.Linux.csproj ================================================ net8.0 false false /usr/local ================================================ FILE: src/linux/Packaging.Linux/build.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } echo "Building Packaging.Linux..." # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" INSTALLER_SRC="$SRC/linux/Packaging.Linux" INSTALLER_OUT="$OUT/linux/Packaging.Linux" # Parse script arguments for i in "$@" do case "$i" in --configuration=*) CONFIGURATION="${i#*=}" shift # past argument=value ;; --version=*) VERSION="${i#*=}" shift # past argument=value ;; --install-from-source=*) INSTALL_FROM_SOURCE="${i#*=}" shift # past argument=value ;; --runtime=*) RUNTIME="${i#*=}" shift # past argument=value ;; --install-prefix=*) INSTALL_PREFIX="${i#*=}" shift # past argument=value ;; *) # unknown option ;; esac done # Ensure install prefix exists if [ ! -d "$INSTALL_PREFIX" ]; then mkdir -p "$INSTALL_PREFIX" fi if [ ! -z "$RUNTIME" ]; then echo "Building for runtime ${RUNTIME}" fi # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$VERSION" ]; then die "--version was not set" fi OUTDIR="$INSTALLER_OUT/$CONFIGURATION" PAYLOAD="$OUTDIR/payload" SYMBOLS="$OUTDIR/payload.sym" # Lay out payload "$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" --output="$PAYLOAD" --symbol-output="$SYMBOLS" || exit 1 if [ $INSTALL_FROM_SOURCE = true ]; then echo "Installing to $INSTALL_PREFIX" # Install directories INSTALL_TO="$INSTALL_PREFIX/share/gcm-core/" LINK_TO="$INSTALL_PREFIX/bin/" mkdir -p "$INSTALL_TO" "$LINK_TO" # Copy all binaries and shared libraries to target installation location cp -R "$PAYLOAD"/* "$INSTALL_TO" || exit 1 # Create symlink if [ ! -f "$LINK_TO/git-credential-manager" ]; then ln -s -r "$INSTALL_TO/git-credential-manager" \ "$LINK_TO/git-credential-manager" || exit 1 fi echo "Install complete." else # Pack "$INSTALLER_SRC/pack.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" --payload="$PAYLOAD" --symbols="$SYMBOLS" --version="$VERSION" || exit 1 fi echo "Build of Packaging.Linux complete." ================================================ FILE: src/linux/Packaging.Linux/install-from-source.sh ================================================ #!/bin/sh # Halt execution immediately on failure. # Note there are some scenarios in which this will not exit; see # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html # for additional details. set -e is_ci= for i in "$@"; do case "$i" in -y) is_ci=true shift # Past argument=value ;; --install-prefix=*) installPrefix="${i#*=}" shift # past argument=value ;; esac done # If install-prefix is not passed, use default value if [ -z "$installPrefix" ]; then installPrefix=/usr/local fi # Ensure install directory exists if [ ! -d "$installPrefix" ]; then echo "The folder $installPrefix does not exist" exit fi # In non-ci scenarios, advertise what we will be doing and # give user the option to exit. if [ -z $is_ci ]; then echo "This script will download, compile, and install Git Credential Manager to: $installPrefix/bin Git Credential Manager is licensed under the MIT License: https://aka.ms/gcm/license" while true; do # Display prompt once before reading input printf "Do you want to continue? [Y/n] " # Prefer reading from the controlling terminal (TTY) when available, # so that input works even if the script is piped (e.g. curl URL | sh) if [ -r /dev/tty ]; then read yn < /dev/tty # If no TTY is available, attempt to read from standard input (stdin) elif ! read yn; then # If input is not possible via TTY or stdin, assume a non-interactive environment # and abort with guidance for automated usage echo "Interactive prompt unavailable in this environment. Use 'sh -s -- -y' for automated install." exit 1 fi case "$yn" in [Yy]*|"") break ;; [Nn]*) exit ;; *) echo "Please answer yes or no." ;; esac done fi install_packages() { pkg_manager=$1 install_verb=$2 packages=$3 for package in $packages; do # Ensure we don't stomp on existing installations. if type $package >/dev/null 2>&1; then continue fi if [ $pkg_manager = apk ]; then $sudo_cmd $pkg_manager $install_verb $package elif [ $pkg_manager = zypper ]; then $sudo_cmd $pkg_manager -n $install_verb $package elif [ $pkg_manager = pacman ]; then $sudo_cmd $pkg_manager --noconfirm $install_verb $package else $sudo_cmd $pkg_manager $install_verb $package -y fi done } ensure_dotnet_installed() { if [ -z "$(verify_existing_dotnet_installation)" ]; then curl -LO https://dot.net/v1/dotnet-install.sh chmod +x ./dotnet-install.sh bash -c "./dotnet-install.sh --channel 8.0" # Since we have to run the dotnet install script with bash, dotnet isn't # added to the process PATH, so we manually add it here. cd ~ export DOTNET_ROOT=$(pwd)/.dotnet add_to_PATH $DOTNET_ROOT fi } verify_existing_dotnet_installation() { # Get initial pieces of installed sdk version(s). sdks=$(dotnet --list-sdks | cut -c 1-3) # If we have a supported version installed, return. supported_dotnet_versions="8.0" for v in $supported_dotnet_versions; do if [ $(echo $sdks | grep "$v") ]; then echo $sdks fi done } add_to_PATH () { for directory; do if [ ! -d "$directory" ]; then continue; # Skip nonexistent directory. fi case ":$PATH:" in *":$directory:"*) break ;; *) export PATH=$PATH:$directory ;; esac done } apt_install() { pkg_name=$1 $sudo_cmd apt update $sudo_cmd apt install $pkg_name -y 2>/dev/null } print_unsupported_distro() { prefix=$1 distro=$2 echo "$prefix: $distro is not officially supported by the GCM project." echo "See https://gh.io/gcm/linux for details." } version_at_least() { [ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ] } sudo_cmd= # If the user isn't root, we need to use `sudo` for certain commands # (e.g. installing packages). if [ -z "$sudo_cmd" ]; then if [ `id -u` != 0 ]; then sudo_cmd=sudo fi fi eval "$(sed -n 's/^ID=/distribution=/p' /etc/os-release)" eval "$(sed -n 's/^VERSION_ID=/version=/p' /etc/os-release | tr -d '"')" case "$distribution" in debian | ubuntu) $sudo_cmd apt update install_packages apt install "curl git" # Install dotnet packages and dependencies if needed. if [ -z "$(verify_existing_dotnet_installation)" ]; then # First try to use native feeds (Ubuntu 22.04 and later). if ! apt_install dotnet8; then # If the native feeds fail, we fall back to # packages.microsoft.com. We begin by adding the dotnet package # repository/signing key. $sudo_cmd apt update && $sudo_cmd apt install wget -y curl -LO https://packages.microsoft.com/config/"$distribution"/"$version"/packages-microsoft-prod.deb $sudo_cmd dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb # Proactively install tzdata to prevent prompts. export DEBIAN_FRONTEND=noninteractive $sudo_cmd apt install -y --no-install-recommends tzdata $sudo_cmd apt update $sudo_cmd apt install apt-transport-https -y $sudo_cmd apt update $sudo_cmd apt install dotnet-sdk-8.0 dpkg-dev -y fi fi ;; fedora | centos | rhel | ol) $sudo_cmd dnf upgrade -y # Install dotnet/GCM dependencies. install_packages dnf install "curl git krb5-libs libicu openssl-libs zlib findutils which bash" ensure_dotnet_installed ;; alpine) $sudo_cmd apk update # Install dotnet/GCM dependencies. # Alpine 3.14 and earlier need libssl1.1, while later versions need libssl3. if ( version_at_least "3.15" $version ) then libssl_pkg="libssl3" else libssl_pkg="libssl1.1" fi install_packages apk add "curl git icu-libs krb5-libs libgcc libintl $libssl_pkg libstdc++ zlib which bash coreutils gcompat" ensure_dotnet_installed ;; sles | opensuse*) $sudo_cmd zypper -n update # Install dotnet/GCM dependencies. install_packages zypper install "curl git find krb5 libicu" ensure_dotnet_installed ;; arch) print_unsupported_distro "WARNING" "$distribution" # --noconfirm required when running from container $sudo_cmd pacman -Syu --noconfirm # Install dotnet/GCM dependencies. install_packages pacman -Sy "curl git glibc gcc krb5 icu openssl libc++ zlib" ensure_dotnet_installed ;; mariner | azurelinux*) print_unsupported_distro "WARNING" "$distribution" $sudo_cmd tdnf update -y # Install dotnet/GCM dependencies. install_packages tdnf install "curl ca-certificates git krb5-libs libicu openssl-libs zlib findutils which bash awk" ensure_dotnet_installed ;; *) print_unsupported_distro "ERROR" "$distribution" exit ;; esac # Detect if the script is part of a full source checkout or standalone instead. script_path="$(cd "$(dirname "$0")" && pwd)" toplevel_path="${script_path%/src/linux/Packaging.Linux}" if [ "z$script_path" = "z$toplevel_path" ] || [ ! -f "$toplevel_path/Git-Credential-Manager.sln" ]; then toplevel_path="$PWD/git-credential-manager" test -d "$toplevel_path" || git clone https://github.com/git-ecosystem/git-credential-manager fi if [ -z "$DOTNET_ROOT" ]; then DOTNET_ROOT="$(dirname $(which dotnet))" fi cd "$toplevel_path" $sudo_cmd env "PATH=$PATH" $DOTNET_ROOT/dotnet build ./src/linux/Packaging.Linux/Packaging.Linux.csproj -c Release -p:InstallFromSource=true -p:installPrefix=$installPrefix add_to_PATH "$installPrefix/bin" ================================================ FILE: src/linux/Packaging.Linux/layout.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } make_absolute () { case "$1" in /*) echo "$1" ;; *) echo "$PWD/$1" ;; esac } # Parse script arguments for i in "$@" do case "$i" in --configuration=*) CONFIGURATION="${i#*=}" shift # past argument=value ;; --output=*) PAYLOAD="${i#*=}" shift # past argument=value ;; --runtime=*) RUNTIME="${i#*=}" shift # past argument=value ;; --symbol-output=*) SYMBOLOUT="${i#*=}" ;; *) # unknown option ;; esac done # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" GCM_SRC="$SRC/shared/Git-Credential-Manager" PROJ_OUT="$OUT/linux/Packaging.Linux" # Build parameters FRAMEWORK=net8.0 # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$PAYLOAD" ]; then die "--output was not set" fi if [ -z "$SYMBOLOUT" ]; then SYMBOLOUT="$PAYLOAD.sym" fi # Cleanup payload directory if [ -d "$PAYLOAD" ]; then echo "Cleaning existing payload directory '$PAYLOAD'..." rm -rf "$PAYLOAD" fi # Cleanup symbol directory if [ -d "$SYMBOLOUT" ]; then echo "Cleaning existing symbols directory '$SYMBOLOUT'..." rm -rf "$SYMBOLOUT" fi # Ensure directories exists mkdir -p "$PAYLOAD" "$SYMBOLOUT" if [ -z "$DOTNET_ROOT" ]; then DOTNET_ROOT="$(dirname $(which dotnet))" fi # Publish core application executables echo "Publishing core application..." if [ -z "$RUNTIME" ]; then $DOTNET_ROOT/dotnet publish "$GCM_SRC" \ --configuration="$CONFIGURATION" \ --framework="$FRAMEWORK" \ --self-contained \ -p:PublishSingleFile=true \ --output="$(make_absolute "$PAYLOAD")" || exit 1 else $DOTNET_ROOT/dotnet publish "$GCM_SRC" \ --configuration="$CONFIGURATION" \ --framework="$FRAMEWORK" \ --runtime="$RUNTIME" \ --self-contained \ -p:PublishSingleFile=true \ --output="$(make_absolute "$PAYLOAD")" || exit 1 fi # Collect symbols echo "Collecting managed symbols..." mv "$PAYLOAD"/*.pdb "$SYMBOLOUT" || exit 1 echo "Build complete." ================================================ FILE: src/linux/Packaging.Linux/pack.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" PROJ_OUT="$OUT/linux/Packaging.Linux" INSTALLER_SRC="$SRC/osx/Installer.Mac" # Parse script arguments for i in "$@" do case "$i" in --version=*) VERSION="${i#*=}" shift # past argument=value ;; --payload=*) PAYLOAD="${i#*=}" shift # past argument=value ;; --symbols=*) SYMBOLS="${i#*=}" shift # past argument=value ;; --runtime=*) RUNTIME="${i#*=}" shift # past argument=value ;; --configuration=*) CONFIGURATION="${i#*=}" shift # past argument=value ;; --output=*) OUTPUT_ROOT="${i#*=}" shift # past argument=value ;; *) # unknown option ;; esac done # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$VERSION" ]; then die "--version was not set" fi if [ -z "$PAYLOAD" ]; then die "--payload was not set" elif [ ! -d "$PAYLOAD" ]; then die "Could not find '$PAYLOAD'. Did you run layout.sh first?" fi if [ -z "$SYMBOLS" ]; then die "--symbols was not set" fi if [ -z "$OUTPUT_ROOT" ]; then OUTPUT_ROOT="$PROJ_OUT/$CONFIGURATION" fi # Fall back to host architecture if no explicit runtime is given. if test -z "$RUNTIME"; then HOST_ARCH="`uname -m`" case $HOST_ARCH in x86_64|amd64) RUNTIME="linux-x64" ;; aarch64|arm64) RUNTIME="linux-arm64" ;; armhf) RUNTIME="linux-arm" ;; *) die "Could not determine host architecture! ($HOST_ARCH)" ;; esac fi TAROUT="$OUTPUT_ROOT/tar" TARBALL="$TAROUT/gcm-$RUNTIME-$VERSION.tar.gz" SYMTARBALL="$TAROUT/gcm-$RUNTIME-$VERSION-symbols.tar.gz" DEBOUT="$OUTPUT_ROOT/deb" DEBROOT="$DEBOUT/root" DEBPKG="$DEBOUT/gcm-$RUNTIME-$VERSION.deb" mkdir -p "$DEBROOT" # Set full read, write, execute permissions for owner and just read and execute permissions for group and other echo "Setting file permissions..." /bin/chmod -R 755 "$PAYLOAD" || exit 1 echo "Packing Packaging.Linux..." # Cleanup any old archive files if [ -e "$TAROUT" ]; then echo "Deleting old archive '$TAROUT'..." rm "$TAROUT" fi # Ensure the parent directory for the archive exists mkdir -p "$TAROUT" || exit 1 # Build binaries tarball echo "Building binaries tarball..." pushd "$PAYLOAD" tar -czvf "$TARBALL" * || exit 1 popd # Build symbols tarball echo "Building symbols tarball..." pushd "$SYMBOLS" tar -czvf "$SYMTARBALL" * || exit 1 popd # Build .deb INSTALL_TO="$DEBROOT/usr/local/share/gcm-core/" LINK_TO="$DEBROOT/usr/local/bin/" mkdir -p "$DEBROOT/DEBIAN" "$INSTALL_TO" "$LINK_TO" || exit 1 # Determine architecture for debian control file from the runtime architecture case $RUNTIME in linux-x64) ARCH="amd64" ;; linux-arm64) ARCH="arm64" ;; linux-arm) ARCH="armhf" ;; *) die "Incompatible runtime architecture given for pack.sh" ;; esac # make the debian control file # this is purposefully not indented, see # https://stackoverflow.com/questions/9349616/bash-eof-in-if-statement # for details cat >"$DEBROOT/DEBIAN/control" < Description: Cross Platform Git Credential Manager command line utility. GCM supports authentication with a number of Git hosting providers including GitHub, BitBucket, and Azure DevOps. For more information see https://aka.ms/gcm EOF # Copy all binaries and shared libraries to target installation location cp -R "$PAYLOAD"/* "$INSTALL_TO" || exit 1 # Create symlink if [ ! -f "$LINK_TO/git-credential-manager" ]; then ln -s -r "$INSTALL_TO/git-credential-manager" \ "$LINK_TO/git-credential-manager" || exit 1 fi dpkg-deb -Zxz --root-owner-group --build "$DEBROOT" "$DEBPKG" || exit 1 echo $MESSAGE ================================================ FILE: src/osx/.gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ================================================ FILE: src/osx/Directory.Build.props ================================================  $(RepoOutPath)osx\ $(PlatformOutPath)$(MSBuildProjectName)\ $(ProjectOutPath)bin\ $(ProjectOutPath)obj\ ================================================ FILE: src/osx/Installer.Mac/Installer.Mac.csproj ================================================  net8.0 false ================================================ FILE: src/osx/Installer.Mac/build.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } echo "Building Installer.Mac..." # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" INSTALLER_SRC="$SRC/osx/Installer.Mac" INSTALLER_OUT="$OUT/osx/Installer.Mac" # Parse script arguments for i in "$@" do case "$i" in --configuration=*) CONFIGURATION="${i#*=}" shift # past argument=value ;; --runtime=*) RUNTIME="${i#*=}" shift ;; --version=*) VERSION="${i#*=}" shift # past argument=value ;; *) # unknown option ;; esac done # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$VERSION" ]; then die "--version was not set" fi if [ -z "$RUNTIME" ]; then TEST_RUNTIME=`uname -m` case $TEST_RUNTIME in "x86_64") RUNTIME="osx-x64" ;; "arm64") RUNTIME="osx-arm64" ;; *) die "Unknown runtime '$TEST_RUNTIME'" ;; esac fi OUTDIR="$INSTALLER_OUT/pkg/$CONFIGURATION" PAYLOAD="$OUTDIR/payload" COMPONENTDIR="$OUTDIR/components" COMPONENTOUT="$COMPONENTDIR/com.microsoft.gitcredentialmanager.component.pkg" DISTOUT="$OUTDIR/gcm-$RUNTIME-$VERSION.pkg" # Layout and pack "$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --output="$PAYLOAD" --runtime="$RUNTIME" || exit 1 "$INSTALLER_SRC/pack.sh" --payload="$PAYLOAD" --version="$VERSION" --output="$COMPONENTOUT" || exit 1 "$INSTALLER_SRC/dist.sh" --package-path="$COMPONENTDIR" --version="$VERSION" --output="$DISTOUT" --runtime="$RUNTIME" || exit 1 echo "Build of Installer.Mac complete." ================================================ FILE: src/osx/Installer.Mac/codesign.sh ================================================ #!/bin/bash SIGN_DIR=$1 DEVELOPER_ID=$2 ENTITLEMENTS_FILE=$3 if [ -z "$SIGN_DIR" ]; then echo "error: missing directory argument" exit 1 elif [ -z "$DEVELOPER_ID" ]; then echo "error: missing developer id argument" exit 1 elif [ -z "$ENTITLEMENTS_FILE" ]; then echo "error: missing entitlements file argument" exit 1 fi # The codesign command needs the entitlements file to be given as an absolute # file path; relative paths can cause issues. if [[ "${ENTITLEMENTS_FILE}" != /* ]]; then echo "error: entitlements file argument must be an absolute path" exit 1 fi echo "======== INPUTS ========" echo "Directory: $SIGN_DIR" echo "Developer ID: $DEVELOPER_ID" echo "Entitlements: $ENTITLEMENTS_FILE" echo "======== END INPUTS ========" echo echo "======== ENTITLEMENTS ========" cat "$ENTITLEMENTS_FILE" echo "======== END ENTITLEMENTS ========" echo cd "$SIGN_DIR" || exit 1 for f in * do macho=$(file --mime "$f" | grep mach) # Runtime sign dylibs and Mach-O binaries if [[ $f == *.dylib ]] || [ -n "$macho" ]; then echo "Signing with entitlements and hardening: $f" codesign -s "$DEVELOPER_ID" "$f" --timestamp --force --options=runtime --entitlements "$ENTITLEMENTS_FILE" elif [ -d "$f" ]; then echo "Signing files in subdirectory: $f" ( cd "$f" || exit 1 for i in * do codesign -s "$DEVELOPER_ID" "$i" --timestamp --force done ) else echo "Signing: $f" codesign -s "$DEVELOPER_ID" "$f" --timestamp --force fi done ================================================ FILE: src/osx/Installer.Mac/dist.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" INSTALLER_SRC="$SRC/osx/Installer.Mac" RESXPATH="$INSTALLER_SRC/resources" # Product information IDENTIFIER="com.microsoft.gitcredentialmanager.dist" # Parse script arguments for i in "$@" do case "$i" in --version=*) VERSION="${i#*=}" shift # past argument=value ;; --package-path=*) PACKAGEPATH="${i#*=}" shift # past argument=value ;; --output=*) DISTOUT="${i#*=}" shift # past argument=value ;; --runtime=*) RUNTIME="${i#*=}" shift ;; --identity=*) IDENTITY="${i#*=}" shift ;; *) # unknown option ;; esac done # Perform pre-execution checks if [ -z "$VERSION" ]; then die "--version was not set" fi if [ -z "$PACKAGEPATH" ]; then die "--package-path was not set" elif [ ! -d "$PACKAGEPATH" ]; then die "Could not find '$PACKAGEPATH'. Did you run pack.sh first?" fi if [ -z "$DISTOUT" ]; then die "--output was not set" fi if [ -z "$RUNTIME" ]; then TEST_RUNTIME=`uname -m` case $TEST_RUNTIME in "x86_64") RUNTIME="osx-x64" ;; "arm64") RUNTIME="osx-arm64" ;; *) die "Unknown runtime '$TEST_RUNTIME'" ;; esac fi echo "Building for runtime '$RUNTIME'" if [ "$RUNTIME" == "osx-x64" ]; then DISTPATH="$INSTALLER_SRC/distribution.x64.xml" else DISTPATH="$INSTALLER_SRC/distribution.arm64.xml" fi # Cleanup any old package if [ -e "$DISTOUT" ]; then echo "Deleting old product package '$DISTOUT'..." rm "$DISTOUT" fi # Ensure the parent directory for the package exists mkdir -p "$(dirname "$DISTOUT")" # Build product installer echo "Building product package..." /usr/bin/productbuild \ --package-path "$PACKAGEPATH" \ --resources "$RESXPATH" \ --distribution "$DISTPATH" \ --identifier "$IDENTIFIER" \ --version "$VERSION" \ ${IDENTITY:+"--sign"} ${IDENTITY:+"$IDENTITY"} \ "$DISTOUT" || exit 1 echo "Product build complete." ================================================ FILE: src/osx/Installer.Mac/distribution.arm64.xml ================================================ Git Credential Manager com.microsoft.gitcredentialmanager.component.pkg ================================================ FILE: src/osx/Installer.Mac/distribution.x64.xml ================================================ Git Credential Manager com.microsoft.gitcredentialmanager.component.pkg ================================================ FILE: src/osx/Installer.Mac/entitlements.xml ================================================ com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-library-validation ================================================ FILE: src/osx/Installer.Mac/layout.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } make_absolute () { case "$1" in /*) echo "$1" ;; *) echo "$PWD/$1" ;; esac } # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" INSTALLER_SRC="$SRC/osx/Installer.Mac" GCM_SRC="$SRC/shared/Git-Credential-Manager" GCM_UI_SRC="$SRC/shared/Git-Credential-Manager.UI.Avalonia" # Build parameters FRAMEWORK=net8.0 # Parse script arguments for i in "$@" do case "$i" in --configuration=*) CONFIGURATION="${i#*=}" shift # past argument=value ;; --output=*) PAYLOAD="${i#*=}" shift # past argument=value ;; --runtime=*) RUNTIME="${i#*=}" shift # past argument=value ;; --symbol-output=*) SYMBOLOUT="${i#*=}" ;; *) # unknown option ;; esac done # Determine a runtime if one was not provided if [ -z "$RUNTIME" ]; then TEST_RUNTIME=`uname -m` case $TEST_RUNTIME in "x86_64") RUNTIME="osx-x64" ;; "arm64") RUNTIME="osx-arm64" ;; *) die "Unknown runtime '$TEST_RUNTIME'" ;; esac fi echo "Building for runtime '$RUNTIME'" # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" if [ -z "$PAYLOAD" ]; then die "--output was not set" fi if [ -z "$SYMBOLOUT" ]; then SYMBOLOUT="$PAYLOAD.sym" fi # Cleanup any old payload directory if [ -d "$PAYLOAD" ]; then echo "Cleaning old payload directory '$PAYLOAD'..." rm -rf "$PAYLOAD" fi # Ensure payload and symbol directories exists mkdir -p "$PAYLOAD" "$SYMBOLOUT" # Copy uninstaller script echo "Copying uninstall script..." cp "$INSTALLER_SRC/uninstall.sh" "$PAYLOAD" || exit 1 # Publish core application executables echo "Publishing core application..." dotnet publish "$GCM_SRC" \ --configuration="$CONFIGURATION" \ --framework="$FRAMEWORK" \ --runtime="$RUNTIME" \ --self-contained \ --output="$(make_absolute "$PAYLOAD")" || exit 1 # Collect symbols echo "Collecting managed symbols..." mv "$PAYLOAD"/*.pdb "$SYMBOLOUT" || exit 1 # Remove any unwanted .DS_Store files echo "Removing unnecessary files..." find "$PAYLOAD" -name '*.DS_Store' -type f -delete || exit 1 echo "Layout complete." ================================================ FILE: src/osx/Installer.Mac/notarize.sh ================================================ #!/bin/bash for i in "$@" do case "$i" in --package=*) PACKAGE="${i#*=}" shift # past argument=value ;; --keychain-profile=*) KEYCHAIN_PROFILE="${i#*=}" shift # past argument=value ;; *) die "unknown option '$i'" ;; esac done if [ -z "$PACKAGE" ]; then echo "error: missing package argument" exit 1 elif [ -z "$KEYCHAIN_PROFILE" ]; then echo "error: missing keychain profile argument" exit 1 fi # Exit as soon as any line fails set -e # Send the notarization request xcrun notarytool submit -v "$PACKAGE" -p "$KEYCHAIN_PROFILE" --wait # Staple the notarization ticket (to allow offline installation) xcrun stapler staple -v "$PACKAGE" ================================================ FILE: src/osx/Installer.Mac/pack.sh ================================================ #!/bin/bash die () { echo "$*" >&2 exit 1 } # Directories THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" SRC="$ROOT/src" OUT="$ROOT/out" INSTALLER_SRC="$SRC/osx/Installer.Mac" # Product information IDENTIFIER="com.microsoft.gitcredentialmanager" INSTALL_LOCATION="/usr/local/share/gcm-core" # Parse script arguments for i in "$@" do case "$i" in --version=*) VERSION="${i#*=}" shift # past argument=value ;; --payload=*) PAYLOAD="${i#*=}" shift # past argument=value ;; --output=*) PKGOUT="${i#*=}" shift # past argument=value ;; *) # unknown option ;; esac done # Perform pre-execution checks if [ -z "$VERSION" ]; then die "--version was not set" fi if [ -z "$PAYLOAD" ]; then die "--payload was not set" elif [ ! -d "$PAYLOAD" ]; then die "Could not find '$PAYLOAD'. Did you run layout.sh first?" fi if [ -z "$PKGOUT" ]; then die "--output was not set" fi # Cleanup any old component if [ -e "$PKGOUT" ]; then echo "Deleting old component '$PKGOUT'..." rm "$PKGOUT" fi # Ensure the parent directory for the component exists mkdir -p "$(dirname "$PKGOUT")" # Set full read, write, execute permissions for owner and just read and execute permissions for group and other echo "Setting file permissions..." /bin/chmod -R 755 "$PAYLOAD" || exit 1 # Remove any extended attributes (ACEs) echo "Removing extended attributes..." /usr/bin/xattr -rc "$PAYLOAD" || exit 1 # Build component packages echo "Building core component package..." /usr/bin/pkgbuild \ --root "$PAYLOAD/" \ --install-location "$INSTALL_LOCATION" \ --scripts "$INSTALLER_SRC/scripts" \ --identifier "$IDENTIFIER" \ --version "$VERSION" \ "$PKGOUT" || exit 1 echo "Component pack complete." ================================================ FILE: src/osx/Installer.Mac/resources/en.lproj/conclusion.html ================================================

Git Credential Manager was installed in /usr/local/share/gcm-core and configured for the current user.

Other users

GCM has already been automatically configured for use by the current user with Git. If other users wish to use GCM, have them run the following command to update their global Git configuration (~/.gitconfig):

$ git-credential-manager configure

To configure GCM for all users, run the following command to update the system Git configuration:

$ git-credential-manager configure --system

Uninstall

If you wish to uninstall GCM, run the following script:

$ /usr/local/share/gcm-core/uninstall.sh

================================================ FILE: src/osx/Installer.Mac/resources/en.lproj/welcome.html ================================================

Git Credential Manager

Git Credential Manager is a secure, cross-platform Git credential helper with authentication support for GitHub, Azure Repos, and other popular Git hosting services.

Installation notes

If you have the old Java-based Git Credential Manager for Mac & Linux installed through Homebrew, it will be unlinked after installation.

Git Credential Manager will be configured as the Git credential helper for the current user by updating the global Git configuration file (~/.gitconfig).

================================================ FILE: src/osx/Installer.Mac/scripts/postinstall ================================================ #!/bin/bash set -e PACKAGE=$1 INSTALL_DESTINATION=$2 function IsBrewPkgInstalled { # Check if Homebrew is installed /usr/bin/which brew > /dev/null if [ $? -eq 0 ] then # Check if the package has been installed brew ls --versions "$1" > /dev/null if [ $? -eq 0 ] then return 0 fi fi return 1 } # Check if Java GCM is present on this system and unlink it should it exist if [ -L /usr/local/bin/git-credential-manager ] && IsBrewPkgInstalled "git-credential-manager"; then brew unlink git-credential-manager fi # Create symlink to GCM in /usr/local/bin mkdir -p /usr/local/bin /bin/ln -Fs "$INSTALL_DESTINATION/git-credential-manager" /usr/local/bin/git-credential-manager # Configure GCM for the current user (running as the current user to avoid root # from taking ownership of ~/.gitconfig) sudo -u ${USER} "$INSTALL_DESTINATION/git-credential-manager" configure exit 0 ================================================ FILE: src/osx/Installer.Mac/uninstall.sh ================================================ #!/bin/bash THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" GCMBIN="$THISDIR/git-credential-manager" # Ensure we're running as root if [ $(id -u) != "0" ] then sudo "$0" "$@" exit $? fi # Unconfigure (as the current user) echo "Unconfiguring credential helper..." sudo -u `/usr/bin/logname` -E "$GCMBIN" unconfigure # Remove symlink if [ -L /usr/local/bin/git-credential-manager ] then echo "Deleting symlink..." rm /usr/local/bin/git-credential-manager else echo "No symlink found." fi # Remove legacy symlink if [ -L /usr/local/bin/git-credential-manager-core ] then echo "Deleting legacy symlink..." rm /usr/local/bin/git-credential-manager-core else echo "No legacy symlink found." fi # Forget package installation/delete receipt echo "Removing installation receipt..." pkgutil --forget com.microsoft.gitcredentialmanager # Remove application files if [ -d "$THISDIR" ] then echo "Deleting application files..." rm -rf "$THISDIR" else echo "No application files found." fi ================================================ FILE: src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj ================================================  net8.0 net8.0;net472 Atlassian.Bitbucket Atlassian.Bitbucket false latest ================================================ FILE: src/shared/Atlassian.Bitbucket/AuthenticationMethod.cs ================================================ using System; namespace Atlassian.Bitbucket { public enum AuthenticationMethod { BasicAuth, Sso } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketAuthentication.cs ================================================ using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Atlassian.Bitbucket.UI.ViewModels; using Atlassian.Bitbucket.UI.Views; using GitCredentialManager; using GitCredentialManager.Authentication; using GitCredentialManager.Authentication.OAuth; using GitCredentialManager.UI; namespace Atlassian.Bitbucket { [Flags] public enum AuthenticationModes { None = 0, Basic = 1, OAuth = 1 << 1, All = Basic | OAuth } public interface IBitbucketAuthentication : IDisposable { Task GetCredentialsAsync(Uri targetUri, string userName, AuthenticationModes modes); Task CreateOAuthCredentialsAsync(InputArguments input); Task RefreshOAuthCredentialsAsync(InputArguments input, string refreshToken); string GetRefreshTokenServiceName(InputArguments input); } public class CredentialsPromptResult { public CredentialsPromptResult(AuthenticationModes mode) { AuthenticationMode = mode; } public CredentialsPromptResult(AuthenticationModes mode, ICredential credential) : this(mode) { Credential = credential; } public AuthenticationModes AuthenticationMode { get; } public ICredential Credential { get; set; } } public class BitbucketAuthentication : AuthenticationBase, IBitbucketAuthentication { public static readonly string[] AuthorityIds = { BitbucketConstants.Id, }; private readonly IRegistry _oauth2ClientRegistry; public BitbucketAuthentication(ICommandContext context) : this(context, new OAuth2ClientRegistry(context)) { } public BitbucketAuthentication(ICommandContext context, IRegistry oauth2ClientRegistry) : base(context) { EnsureArgument.NotNull(oauth2ClientRegistry, nameof(oauth2ClientRegistry)); this._oauth2ClientRegistry = oauth2ClientRegistry; } public async Task GetCredentialsAsync(Uri targetUri, string userName, AuthenticationModes modes) { ThrowIfUserInteractionDisabled(); // If we don't have a desktop session/GUI then we cannot offer OAuth since the only // supported grant is authcode (i.e, using a web browser; device code is not supported). if (!Context.SessionManager.IsDesktopSession) { modes = modes & ~AuthenticationModes.OAuth; } // If the only supported mode is OAuth then just return immediately if (modes == AuthenticationModes.OAuth) { return new CredentialsPromptResult(AuthenticationModes.OAuth); } // We need at least one mode! if (modes == AuthenticationModes.None) { throw new ArgumentException(@$"Must specify at least one {nameof(AuthenticationModes)}", nameof(modes)); } // Shell out to the UI helper and show the Bitbucket u/p prompt if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession) { if (TryFindHelperCommand(out string helperCommand, out string args)) { return await GetCredentialsViaHelperAsync(targetUri, userName, modes, helperCommand, args); } return await GetCredentialsViaUiAsync(targetUri, userName, modes); } return GetCredentialsViaTty(targetUri, userName, modes); } private async Task GetCredentialsViaUiAsync( Uri targetUri, string userName, AuthenticationModes modes) { var viewModel = new CredentialsViewModel(Context.SessionManager) { ShowOAuth = (modes & AuthenticationModes.OAuth) != 0, ShowBasic = (modes & AuthenticationModes.Basic) != 0 }; if (!BitbucketHelper.IsBitbucketOrg(targetUri)) { viewModel.Url = targetUri; } if (!string.IsNullOrWhiteSpace(userName)) { viewModel.UserName = userName; } await AvaloniaUi.ShowViewAsync(viewModel, GetParentWindowHandle(), CancellationToken.None); ThrowIfWindowCancelled(viewModel); switch (viewModel.SelectedMode) { case AuthenticationModes.OAuth: return new CredentialsPromptResult(AuthenticationModes.OAuth); case AuthenticationModes.Basic: return new CredentialsPromptResult( AuthenticationModes.Basic, new GitCredential(viewModel.UserName, viewModel.Password) ); default: throw new ArgumentOutOfRangeException(nameof(AuthenticationModes), "Unknown authentication mode", viewModel.SelectedMode.ToString()); } } private CredentialsPromptResult GetCredentialsViaTty(Uri targetUri, string userName, AuthenticationModes modes) { ThrowIfTerminalPromptsDisabled(); switch (modes) { case AuthenticationModes.Basic: Context.Terminal.WriteLine("Enter Bitbucket credentials for '{0}'...", targetUri); if (!string.IsNullOrWhiteSpace(userName)) { // Don't need to prompt for the username if it has been specified already Context.Terminal.WriteLine("Username: {0}", userName); } else { // Prompt for username userName = Context.Terminal.Prompt("Username"); } // Prompt for password string password = Context.Terminal.PromptSecret("Password"); return new CredentialsPromptResult( AuthenticationModes.Basic, new GitCredential(userName, password)); case AuthenticationModes.OAuth: return new CredentialsPromptResult(AuthenticationModes.OAuth); case AuthenticationModes.None: throw new ArgumentOutOfRangeException(nameof(modes), @$"At least one {nameof(AuthenticationModes)} must be supplied"); default: var menuTitle = $"Select an authentication method for '{targetUri}'"; var menu = new TerminalMenu(Context.Terminal, menuTitle); TerminalMenuItem oauthItem = null; TerminalMenuItem basicItem = null; if ((modes & AuthenticationModes.OAuth) != 0) oauthItem = menu.Add("OAuth"); if ((modes & AuthenticationModes.Basic) != 0) basicItem = menu.Add("Username/password"); // Default to the 'first' choice in the menu TerminalMenuItem choice = menu.Show(0); if (choice == oauthItem) goto case AuthenticationModes.OAuth; if (choice == basicItem) goto case AuthenticationModes.Basic; throw new Exception(); } } private async Task GetCredentialsViaHelperAsync( Uri targetUri, string userName, AuthenticationModes modes, string helperCommand, string args) { var promptArgs = new StringBuilder(args); promptArgs.Append("prompt"); if (!BitbucketHelper.IsBitbucketOrg(targetUri)) { promptArgs.AppendFormat(" --url {0}", QuoteCmdArg(targetUri.ToString())); } if (!string.IsNullOrWhiteSpace(userName)) { promptArgs.AppendFormat(" --username {0}", QuoteCmdArg(userName)); } if ((modes & AuthenticationModes.Basic) != 0) { promptArgs.Append(" --show-basic"); } if ((modes & AuthenticationModes.OAuth) != 0) { promptArgs.Append(" --show-oauth"); } IDictionary output = await InvokeHelperAsync(helperCommand, promptArgs.ToString()); if (output.TryGetValue("mode", out string mode) && StringComparer.OrdinalIgnoreCase.Equals(mode, "oauth")) { return new CredentialsPromptResult(AuthenticationModes.OAuth); } else { if (!output.TryGetValue("username", out userName)) { throw new Trace2Exception(Context.Trace2, "Missing username in response"); } if (!output.TryGetValue("password", out string password)) { throw new Trace2Exception(Context.Trace2, "Missing password in response"); } return new CredentialsPromptResult( AuthenticationModes.Basic, new GitCredential(userName, password)); } } public async Task CreateOAuthCredentialsAsync(InputArguments input) { ThrowIfUserInteractionDisabled(); var browserOptions = new OAuth2WebBrowserOptions { SuccessResponseHtml = BitbucketResources.AuthenticationResponseSuccessHtml, FailureResponseHtmlFormat = BitbucketResources.AuthenticationResponseFailureHtmlFormat }; var browser = new OAuth2SystemWebBrowser(Context.SessionManager, browserOptions); var oauth2Client = _oauth2ClientRegistry.Get(input); var authCodeResult = await oauth2Client.GetAuthorizationCodeAsync(browser, CancellationToken.None); return await oauth2Client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None); } public async Task RefreshOAuthCredentialsAsync(InputArguments input, string refreshToken) { var client = _oauth2ClientRegistry.Get(input); return await client.GetTokenByRefreshTokenAsync(refreshToken, CancellationToken.None); } public string GetRefreshTokenServiceName(InputArguments input) { var client = _oauth2ClientRegistry.Get(input); return client.GetRefreshTokenServiceName(input); } protected internal virtual bool TryFindHelperCommand(out string command, out string args) { return TryFindHelperCommand( BitbucketConstants.EnvironmentVariables.AuthenticationHelper, BitbucketConstants.GitConfiguration.Credential.AuthenticationHelper, BitbucketConstants.DefaultAuthenticationHelper, out command, out args); } private HttpClient _httpClient; private HttpClient HttpClient => _httpClient ??= Context.HttpClientFactory.CreateClient(); public void Dispose() { _httpClient?.Dispose(); } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketConstants.cs ================================================ using System; namespace Atlassian.Bitbucket { public static class BitbucketConstants { public const string Id = "bitbucket"; public const string Name = "Bitbucket"; public const string DefaultAuthenticationHelper = "Atlassian.Bitbucket.UI"; public static class EnvironmentVariables { public const string AuthenticationHelper = "GCM_BITBUCKET_HELPER"; public const string AuthenticationModes = "GCM_BITBUCKET_AUTHMODES"; public const string AlwaysRefreshCredentials = "GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS"; public const String ValidateStoredCredentials = "GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS"; } public static class GitConfiguration { public static class Credential { public const string AuthenticationHelper = "bitbucketHelper"; public const string AuthenticationModes = "bitbucketAuthModes"; public const string AlwaysRefreshCredentials = "bitbucketAlwaysRefreshCredentials"; public const string ValidateStoredCredentials = "bitbucketValidateStoredCredentials"; } } public static class HelpUrls { public const string DataCenterPasswordReset = "/passwordreset"; public const string DataCenterLogin = "/login"; public const string PasswordReset = "https://bitbucket.org/account/password/reset/"; public const string SignUp = "https://bitbucket.org/account/signup/"; public const string TwoFactor = "https://support.atlassian.com/bitbucket-cloud/docs/enable-two-step-verification/"; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketHelper.cs ================================================ using System; using Atlassian.Bitbucket.Cloud; using GitCredentialManager; namespace Atlassian.Bitbucket { public static class BitbucketHelper { public static string GetBaseUri(Uri remoteUri) { var pathParts = remoteUri.PathAndQuery.Split('/'); var pathPart = remoteUri.PathAndQuery.StartsWith("/") ? pathParts[1] : pathParts[0]; var path = !string.IsNullOrWhiteSpace(pathPart) ? "/" + pathPart : null; return $"{remoteUri.Scheme}://{remoteUri.Host}:{remoteUri.Port}{path}"; } public static bool IsBitbucketOrg(InputArguments input) { return IsBitbucketOrg(input.GetRemoteUri()); } public static bool IsBitbucketOrg(Uri targetUri) { return IsBitbucketOrg(targetUri.Host); } public static bool IsBitbucketOrg(string targetHost) { return StringComparer.OrdinalIgnoreCase.Equals(targetHost, CloudConstants.BitbucketBaseUrlHost); } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Atlassian.Bitbucket.Cloud; using GitCredentialManager; using GitCredentialManager.Authentication.OAuth; namespace Atlassian.Bitbucket { public class BitbucketHostProvider : IHostProvider { private readonly ICommandContext _context; private readonly IBitbucketAuthentication _bitbucketAuth; private readonly IRegistry _restApiRegistry; public BitbucketHostProvider(ICommandContext context) : this(context, new BitbucketAuthentication(context), new BitbucketRestApiRegistry(context)) { } public BitbucketHostProvider(ICommandContext context, IBitbucketAuthentication bitbucketAuth, IRegistry restApiRegistry) { EnsureArgument.NotNull(context, nameof(context)); EnsureArgument.NotNull(bitbucketAuth, nameof(bitbucketAuth)); EnsureArgument.NotNull(restApiRegistry, nameof(restApiRegistry)); _context = context; _bitbucketAuth = bitbucketAuth; _restApiRegistry = restApiRegistry; } #region IHostProvider public string Id => BitbucketConstants.Id; public string Name => BitbucketConstants.Name; public IEnumerable SupportedAuthorityIds => BitbucketAuthentication.AuthorityIds; public bool IsSupported(InputArguments input) { if (input is null) { return false; } if (input.WwwAuth.Any(x => x.Contains("realm=\"Atlassian Bitbucket\"", StringComparison.InvariantCultureIgnoreCase))) { return true; } // Split port number and hostname from host input argument if (!input.TryGetHostAndPort(out string hostName, out _)) { return false; } // We do not recommend unencrypted HTTP communications to Bitbucket, but it is possible. // Therefore, we report `true` here for HTTP so that we can show a helpful // error message for the user in `GetCredentialAsync`. return (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") || StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https")) && hostName.EndsWith(CloudConstants.BitbucketBaseUrlHost, StringComparison.OrdinalIgnoreCase); } public bool IsSupported(HttpResponseMessage response) { if (response is null) { return false; } // Identify Bitbucket on-prem instances from the HTTP response using the Atlassian specific header X-AREQUESTID var supported = response.Headers.Contains("X-AREQUESTID"); _context.Trace.WriteLine($"Host is{(supported ? null : "n't")} supported as Bitbucket"); return supported; } public async Task GetCredentialAsync(InputArguments input) { // We should not allow unencrypted communication and should inform the user if (!_context.Settings.AllowUnsafeRemotes && StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") && BitbucketHelper.IsBitbucketOrg(input)) { throw new Trace2Exception(_context.Trace2, "Unencrypted HTTP is not recommended for Bitbucket.org. " + "Ensure the repository remote URL is using HTTPS " + $"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes."); } var authModes = await GetSupportedAuthenticationModesAsync(input); ICredential credential = await GetStoredCredentials(input, authModes) ?? await GetRefreshedCredentials(input, authModes); return new GetCredentialResult(credential); } private async Task GetStoredCredentials(InputArguments input, AuthenticationModes authModes) { if (_context.Settings.TryGetSetting(BitbucketConstants.EnvironmentVariables.AlwaysRefreshCredentials, Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.AlwaysRefreshCredentials, out string alwaysRefreshCredentials) && alwaysRefreshCredentials.ToBooleanyOrDefault(false)) { _context.Trace.WriteLine("Ignore stored credentials"); return null; } Uri remoteUri = input.GetRemoteUri(); string credentialService = GetServiceName(remoteUri); _context.Trace.WriteLine($"Look for existing credentials under {credentialService} ..."); ICredential credentials = _context.CredentialStore.Get(credentialService, input.UserName); if (credentials == null) { _context.Trace.WriteLine("No stored credentials found"); return null; } _context.Trace.WriteLineSecrets($"Found stored credentials: {credentials.Account}/{{0}}", new object[] { credentials.Password }); // Check credentials are still valid if (!await ValidateCredentialsWork(input, credentials, authModes)) { return null; } return credentials; } private async Task GetRefreshedCredentials(InputArguments input, AuthenticationModes authModes) { _context.Trace.WriteLine("Refresh credentials..."); // Check for presence of refresh_token entry in credential store Uri remoteUri = input.GetRemoteUri(); var refreshTokenService = GetRefreshTokenServiceName(remoteUri); _context.Trace.WriteLine("Checking for refresh token..."); ICredential refreshToken = SupportsOAuth(authModes) ? _context.CredentialStore.Get(refreshTokenService, input.UserName) : null; if (refreshToken is null) { _context.Trace.WriteLine("No stored refresh token found"); // There is no refresh token either because this is a non-2FA enabled account (where OAuth is not // required), or because we previously erased the RT. _context.Trace.WriteLine("Prompt for credentials..."); var result = await _bitbucketAuth.GetCredentialsAsync(remoteUri, input.UserName, authModes); if (result is null || result.AuthenticationMode == AuthenticationModes.None) { var message = "User cancelled credential prompt"; _context.Trace.WriteLine(message); throw new Trace2Exception(_context.Trace2, message); } switch (result.AuthenticationMode) { case AuthenticationModes.Basic: // Return the valid credential return result.Credential; case AuthenticationModes.OAuth: // If the user wants to use OAuth fall through to interactive auth break; default: throw new ArgumentOutOfRangeException( $"Unexpected {nameof(AuthenticationModes)} returned from prompt"); } // Fall through to the start of the interactive OAuth authentication flow } else { _context.Trace.WriteLineSecrets("Found stored refresh token: {0}", new object[] { refreshToken }); try { return await GetOAuthCredentialsViaRefreshFlow(input, refreshToken); } catch (OAuth2Exception ex) { var message = "Failed to refresh existing OAuth credential using refresh token"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); _context.Trace2.WriteError(message); // We failed to refresh the AT using the RT; log the refresh failure and fall through to restart // the OAuth authentication flow } } return await GetOAuthCredentialsViaInteractiveBrowserFlow(input); } private async Task GetOAuthCredentialsViaRefreshFlow(InputArguments input, ICredential refreshToken) { Uri remoteUri = input.GetRemoteUri(); var refreshTokenService = GetRefreshTokenServiceName(remoteUri); _context.Trace.WriteLine("Refreshing OAuth credentials using refresh token..."); OAuth2TokenResult refreshResult = await _bitbucketAuth.RefreshOAuthCredentialsAsync(input, refreshToken.Password); // Resolve the username _context.Trace.WriteLine("Resolving username for refreshed OAuth credential..."); string refreshUserName = await ResolveOAuthUserNameAsync(input, refreshResult.AccessToken); _context.Trace.WriteLine($"Username for refreshed OAuth credential is '{refreshUserName}'"); // Store the refreshed RT _context.Trace.WriteLine("Storing new refresh token..."); _context.CredentialStore.AddOrUpdate(refreshTokenService, remoteUri.GetUserName(), refreshResult.RefreshToken); // Return new access token return new GitCredential(refreshUserName, refreshResult.AccessToken); } private async Task GetOAuthCredentialsViaInteractiveBrowserFlow(InputArguments input) { Uri remoteUri = input.GetRemoteUri(); var refreshTokenService = GetRefreshTokenServiceName(remoteUri); // We failed to use the refresh token either because it didn't exist, or because the refresh token is no // longer valid. Either way we must now try authenticating using OAuth interactively. // Start OAuth authentication flow _context.Trace.WriteLine("Starting OAuth authentication flow..."); OAuth2TokenResult oauthResult = await _bitbucketAuth.CreateOAuthCredentialsAsync(input); // Resolve the username _context.Trace.WriteLine("Resolving username for OAuth credential..."); string newUserName = await ResolveOAuthUserNameAsync(input, oauthResult.AccessToken); _context.Trace.WriteLine($"Username for OAuth credential is '{newUserName}'"); // Store the new RT _context.Trace.WriteLine("Storing new refresh token..."); _context.CredentialStore.AddOrUpdate(refreshTokenService, newUserName, oauthResult.RefreshToken); _context.Trace.WriteLine("Refresh token was successfully stored."); // Return the new AT as the credential return new GitCredential(newUserName, oauthResult.AccessToken); } private static bool SupportsOAuth(AuthenticationModes authModes) { return (authModes & AuthenticationModes.OAuth) != 0; } private static bool SupportsBasicAuth(AuthenticationModes authModes) { return (authModes & AuthenticationModes.Basic) != 0; } public async Task GetSupportedAuthenticationModesAsync(InputArguments input) { // Check for an explicit override for supported authentication modes if (_context.Settings.TryGetSetting( BitbucketConstants.EnvironmentVariables.AuthenticationModes, Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.AuthenticationModes, out string authModesStr)) { if (Enum.TryParse(authModesStr, true, out AuthenticationModes authModes) && authModes != AuthenticationModes.None) { _context.Trace.WriteLine($"Supported authentication modes override present: {authModes}"); return authModes; } else { _context.Trace.WriteLine($"Invalid value for supported authentication modes override setting: '{authModesStr}'"); } } // It isn't possible to detect what Bitbucket.org is expecting so return the predefined answers. if (BitbucketHelper.IsBitbucketOrg(input)) { // Bitbucket should use Basic, OAuth or manual PAT based authentication only _context.Trace.WriteLine($"{input.GetRemoteUri()} is bitbucket.org - authentication schemes: '{CloudConstants.DotOrgAuthenticationModes}'"); return CloudConstants.DotOrgAuthenticationModes; } // For Bitbucket DC/Server the supported modes can be detected _context.Trace.WriteLine($"{input.GetRemoteUri()} is Bitbucket DC - checking for supported authentication schemes..."); try { var authenticationMethods = await _restApiRegistry.Get(input).GetAuthenticationMethodsAsync(); var modes = AuthenticationModes.None; if (authenticationMethods.Contains(AuthenticationMethod.BasicAuth)) { modes |= AuthenticationModes.Basic; } var isOauthInstalled = await _restApiRegistry.Get(input).IsOAuthInstalledAsync(); if (isOauthInstalled) { modes |= AuthenticationModes.OAuth; } _context.Trace.WriteLine($"Bitbucket DC/Server instance supports authentication schemes: {modes}"); return modes; } catch (Exception ex) { var format = "Failed to query '{0}' for supported authentication schemes."; var message = string.Format(format, input.GetRemoteUri()); _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); _context.Trace2.WriteError(message, format); _context.Terminal.WriteLine($"warning: {message}"); // Fall-back to offering all modes so the user is never blocked from authenticating by at least one mode return AuthenticationModes.All; } } public Task StoreCredentialAsync(InputArguments input) { // It doesn't matter if this is an OAuth access token, or the literal username & password // because we store them the same way, against the same credential key in the store. // The OAuth refresh token is already stored on the 'get' request. Uri remoteUri = input.GetRemoteUri(); string service = GetServiceName(remoteUri); _context.Trace.WriteLine("Storing credential..."); _context.CredentialStore.AddOrUpdate(service, input.UserName, input.Password); _context.Trace.WriteLine("Credential was successfully stored."); return Task.CompletedTask; } public Task EraseCredentialAsync(InputArguments input) { // Erase the stored credential (which may be either the literal username & password, or // the OAuth access token). We don't need to erase the OAuth refresh token because on the // next 'get' request, if the RT is bad we will erase and reacquire a new one at that point. Uri remoteUri = input.GetRemoteUri(); string service = GetServiceName(remoteUri); _context.Trace.WriteLine("Erasing credential..."); if (_context.CredentialStore.Remove(service, input.UserName)) { _context.Trace.WriteLine("Credential was successfully erased."); } else { _context.Trace.WriteLine("Credential was not erased."); } return Task.CompletedTask; } #endregion #region Private Methods private async Task ResolveOAuthUserNameAsync(InputArguments input, string accessToken) { RestApiResult result = await _restApiRegistry.Get(input).GetUserInformationAsync(null, accessToken, isBearerToken: true); if (result.Succeeded) { return result.Response.UserName; } throw new Trace2Exception(_context.Trace2, $"Failed to resolve username. HTTP: {result.StatusCode}"); } private async Task ResolveBasicAuthUserNameAsync(InputArguments input, string username, string password) { RestApiResult result = await _restApiRegistry.Get(input).GetUserInformationAsync(username, password, isBearerToken: false); if (result.Succeeded) { return result.Response.UserName; } throw new Trace2Exception(_context.Trace2, $"Failed to resolve username. HTTP: {result.StatusCode}"); } private async Task ValidateCredentialsWork(InputArguments input, ICredential credentials, AuthenticationModes authModes) { if (_context.Settings.TryGetSetting( BitbucketConstants.EnvironmentVariables.ValidateStoredCredentials, Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials, out string validateStoredCredentials) && !validateStoredCredentials.ToBooleanyOrDefault(true)) { _context.Trace.WriteLine($"Skipping validation of stored credentials due to {BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials} = {validateStoredCredentials}"); return true; } if (credentials is null) { return false; } // TODO: ideally we'd also check if the credentials have expired based on some local metadata // (once/if we get such metadata storage), and return false if they have. // This would be more efficient than having to make REST API calls to check. Uri remoteUri = input.GetRemoteUri(); _context.Trace.WriteLineSecrets($"Validate credentials ({credentials.Account}/{{0}}) are fresh for {remoteUri} ...", new object[] { credentials.Password }); // Bitbucket supports both OAuth + Basic Auth unless there is explicit GCM configuration. // The credentials could be for either scheme therefore need to potentially test both possibilities. if (SupportsOAuth(authModes)) { try { await ResolveOAuthUserNameAsync(input, credentials.Password); _context.Trace.WriteLine("Validated existing credentials using OAuth"); return true; } catch (Exception ex) { var message = "Failed to validate existing credentials using OAuth"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); _context.Trace2.WriteError(message); } } if (SupportsBasicAuth(authModes)) { try { await ResolveBasicAuthUserNameAsync(input, credentials.Account, credentials.Password); _context.Trace.WriteLine("Validated existing credentials using BasicAuth"); return true; } catch (Exception ex) { var message = "Failed to validate existing credentials using Basic Auth"; _context.Trace.WriteLine(message); _context.Trace.WriteException(ex); _context.Trace2.WriteError(message); return false; } } return true; } private static string GetServiceName(Uri remoteUri) { return remoteUri.WithoutUserInfo().AbsoluteUri.TrimEnd('/'); } internal /* for testing */ static string GetRefreshTokenServiceName(Uri remoteUri) { Uri baseUri = remoteUri.WithoutUserInfo(); // The refresh token key never includes the path component. // Instead we use the path component to specify this is the "refresh_token". Uri uri = new UriBuilder(baseUri) { Path = "/refresh_token" }.Uri; return uri.AbsoluteUri.TrimEnd('/'); } #endregion public void Dispose() { _restApiRegistry.Dispose(); _bitbucketAuth.Dispose(); } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs ================================================ using System; using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using GitCredentialManager; using GitCredentialManager.Authentication.OAuth; namespace Atlassian.Bitbucket { public abstract class BitbucketOAuth2Client : OAuth2Client { public BitbucketOAuth2Client(HttpClient httpClient, OAuth2ServerEndpoints endpoints, string clientId, Uri redirectUri, string clientSecret, ITrace2 trace2) : base(httpClient, endpoints, clientId, trace2, redirectUri, clientSecret, false) { } public abstract IEnumerable Scopes { get; } public string GetRefreshTokenServiceName(InputArguments input) { Uri baseUri = input.GetRemoteUri(includeUser: false); // The refresh token key never includes the path component. // Instead we use the path component to specify this is the "refresh_token". Uri uri = new UriBuilder(baseUri) { Path = "/refresh_token" }.Uri; return uri.AbsoluteUri.TrimEnd('/'); } public Task GetAuthorizationCodeAsync(IOAuth2WebBrowser browser, CancellationToken ct) { return this.GetAuthorizationCodeAsync(Scopes, browser, ct); } protected override bool TryCreateTokenEndpointResult(string json, out OAuth2TokenResult result) { // We override the token endpoint response parsing because the Bitbucket authority returns // the non-standard 'scopes' property for the list of scopes, rather than the (optional) // 'scope' (note the singular vs plural) property as outlined in the standard. if (TryDeserializeJson(json, out BitbucketTokenEndpointResponseJson jsonObj)) { result = jsonObj.ToResult(); return true; } result = null; return false; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketResources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Atlassian.Bitbucket { using System; [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class BitbucketResources { private static System.Resources.ResourceManager resourceMan; private static System.Globalization.CultureInfo resourceCulture; [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal BitbucketResources() { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Resources.ResourceManager ResourceManager { get { if (object.Equals(null, resourceMan)) { System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Atlassian.Bitbucket.BitbucketResources", typeof(BitbucketResources).Assembly); resourceMan = temp; } return resourceMan; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } internal static string AuthenticationResponseSuccessHtml { get { return ResourceManager.GetString("AuthenticationResponseSuccessHtml", resourceCulture); } } internal static string AuthenticationResponseFailureHtmlFormat { get { return ResourceManager.GetString("AuthenticationResponseFailureHtmlFormat", resourceCulture); } } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketResources.resx ================================================ text/microsoft-resx 1.3 System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Bitbucket Authentication
Bitbucket

Authentication Successful

Git Credential Manager has been successfully authenticated. You may now close this page.

]]>
Bitbucket Authentication
Bitbucket

Authentication Failed

Git Credential Manager failed to be authenticated.

Error
{0}
Description
{1}
URL
{2}
]]>
================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketRestApiRegistry.cs ================================================ using Atlassian.Bitbucket.Cloud; using GitCredentialManager; namespace Atlassian.Bitbucket { public class BitbucketRestApiRegistry : IRegistry { private readonly ICommandContext context; private BitbucketRestApi cloudApi; private DataCenter.BitbucketRestApi dataCenterApi; public BitbucketRestApiRegistry(ICommandContext context) { this.context = context; } public IBitbucketRestApi Get(InputArguments input) { if(!BitbucketHelper.IsBitbucketOrg(input)) { return DataCenterApi; } return CloudApi; } public void Dispose() { context.Dispose(); cloudApi?.Dispose(); dataCenterApi?.Dispose(); } private Cloud.BitbucketRestApi CloudApi => cloudApi ??= new Cloud.BitbucketRestApi(context); private DataCenter.BitbucketRestApi DataCenterApi => dataCenterApi ??= new DataCenter.BitbucketRestApi(context); } } ================================================ FILE: src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs ================================================ using System; using System.Text.Json; using GitCredentialManager.Authentication.OAuth.Json; using System.Text.Json.Serialization; namespace Atlassian.Bitbucket { [JsonConverter(typeof(BitbucketCustomTokenEndpointResponseJsonConverter))] public class BitbucketTokenEndpointResponseJson : TokenEndpointResponseJson { // To ensure the "scopes" property used by Bitbucket is deserialized successfully with System.Text.Json, we must // use a custom converter. Otherwise, ordering will matter (i.e. if "scopes" is the final property, its value // will be used, but if "scope" is the final property, its value will be used). } public class BitbucketCustomTokenEndpointResponseJsonConverter : JsonConverter { public override BitbucketTokenEndpointResponseJson Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException(); } var response = new BitbucketTokenEndpointResponseJson(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) { return response; } if (reader.TokenType == JsonTokenType.PropertyName) { var propertyName = reader.GetString(); reader.Read(); if (propertyName != null) { switch (propertyName) { case "access_token": response.AccessToken = reader.GetString(); break; case "token_type": response.TokenType = reader.GetString(); break; case "expires_in": if (reader.TryGetUInt32(out var expiration)) response.ExpiresIn = expiration; else response.ExpiresIn = null; break; case "refresh_token": response.RefreshToken = reader.GetString(); break; case "scopes": response.Scope = reader.GetString(); break; } } } } throw new JsonException(); } public override void Write( Utf8JsonWriter writer, BitbucketTokenEndpointResponseJson tokenEndpointResponseJson, JsonSerializerOptions options) { } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Net.Http; using GitCredentialManager; using GitCredentialManager.Authentication.OAuth; namespace Atlassian.Bitbucket.Cloud { public class BitbucketOAuth2Client : Bitbucket.BitbucketOAuth2Client { public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2 trace2) : base(httpClient, GetEndpoints(), GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings), trace2) { } public override IEnumerable Scopes => new string[] { CloudConstants.OAuthScopes.RepositoryWrite, CloudConstants.OAuthScopes.Account, }; private static string GetClientId(ISettings settings) { // Check for developer override value if (settings.TryGetSetting( CloudConstants.EnvironmentVariables.OAuthClientId, Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientId, out string clientId)) { return clientId; } return CloudConstants.OAuth2ClientId; } private static Uri GetRedirectUri(ISettings settings) { // Check for developer override value if (settings.TryGetSetting( CloudConstants.EnvironmentVariables.OAuthRedirectUri, Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthRedirectUri, out string redirectUriStr) && Uri.TryCreate(redirectUriStr, UriKind.Absolute, out Uri redirectUri)) { return redirectUri; } return CloudConstants.OAuth2RedirectUri; } private static string GetClientSecret(ISettings settings) { // Check for developer override value if (settings.TryGetSetting( CloudConstants.EnvironmentVariables.OAuthClientSecret, Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientSecret, out string clientSecret)) { return clientSecret; } return CloudConstants.OAuth2ClientSecret; } private static OAuth2ServerEndpoints GetEndpoints() { return new OAuth2ServerEndpoints( CloudConstants.OAuth2AuthorizationEndpoint, CloudConstants.OAuth2TokenEndpoint ); } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs ================================================ using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using GitCredentialManager; using System.Text.Json; using System.Text.Json.Serialization; namespace Atlassian.Bitbucket.Cloud { public class BitbucketRestApi : IBitbucketRestApi { private readonly ICommandContext _context; private readonly Uri _apiUri = CloudConstants.BitbucketApiUri; public BitbucketRestApi(ICommandContext context) { EnsureArgument.NotNull(context, nameof(context)); _context = context; } public async Task> GetUserInformationAsync(string userName, string password, bool isBearerToken) { var requestUri = new Uri(_apiUri, "2.0/user"); using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) { if (isBearerToken) { request.AddBearerAuthenticationHeader(password); } else { request.AddBasicAuthenticationHeader(userName, password); } _context.Trace.WriteLine($"HTTP: GET {requestUri}"); using (HttpResponseMessage response = await HttpClient.SendAsync(request)) { _context.Trace.WriteLine($"HTTP: Response {(int) response.StatusCode} [{response.StatusCode}]"); string json = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { var obj = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, }); return new RestApiResult(response.StatusCode, obj); } return new RestApiResult(response.StatusCode); } } } public Task IsOAuthInstalledAsync() { return Task.FromResult(true); } public Task> GetAuthenticationMethodsAsync() { // For Bitbucket Cloud there is no REST API to determine login methods // instead this is determined later in the process by attempting // authenticated REST API requests and checking the response. return Task.FromResult(new List()); } private HttpClient _httpClient; private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); public void Dispose() { _httpClient?.Dispose(); } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/Cloud/CloudConstants.cs ================================================ using System; namespace Atlassian.Bitbucket.Cloud { public static class CloudConstants { public const string BitbucketBaseUrlHost = "bitbucket.org"; public static readonly Uri BitbucketApiUri = new Uri("https://api.bitbucket.org"); // TODO: use the GCM client ID and secret once we have this approved. // Until then continue to use Sourcetree's values like GCM Windows. //public const string OAuth2ClientId = "b5AKdPfpgFdEGpKzPE"; //public const string OAuth2ClientSecret = "7NUP5qUtSR3SxdFK4xAGaU6PMNvNdE59"; //public static readonly Uri OAuth2RedirectUri = new Uri("http://localhost:46337/"); public const string OAuth2ClientId = "HJdmKXV87DsmC9zSWB"; public const string OAuth2ClientSecret = "wwWw47VB9ZHwMsD4Q4rAveHkbxNrMp3n"; public static readonly Uri OAuth2RedirectUri = new Uri("http://localhost:34106/"); public static readonly Uri OAuth2AuthorizationEndpoint = new Uri("https://bitbucket.org/site/oauth2/authorize"); public static readonly Uri OAuth2TokenEndpoint = new Uri("https://bitbucket.org/site/oauth2/access_token"); public static class OAuthScopes { public const string RepositoryWrite = "repository:write"; public const string Account = "account"; } /// /// Supported authentication modes for Bitbucket.org /// public const AuthenticationModes DotOrgAuthenticationModes = AuthenticationModes.Basic | AuthenticationModes.OAuth; public static class EnvironmentVariables { public const string OAuthClientId = "GCM_BITBUCKET_CLOUD_CLIENTID"; public const string OAuthClientSecret = "GCM_BITBUCKET_CLOUD_CLIENTSECRET"; public const string OAuthRedirectUri = "GCM_BITBUCKET_CLOUD_OAUTH_REDIRECTURI"; } public static class GitConfiguration { public static class Credential { public const string OAuthClientId = "cloudOAuthClientId"; public const string OAuthClientSecret = "cloudOAuthClientSecret"; public const string OAuthRedirectUri = "cloudOauthRedirectUri"; } } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/Cloud/UserInfo.cs ================================================ using System; using System.Text.Json; using System.Text.Json.Serialization; namespace Atlassian.Bitbucket.Cloud { public class UserInfo : IUserInfo { [JsonPropertyName("username")] public string UserName { get; set; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using GitCredentialManager; using GitCredentialManager.Authentication.OAuth; namespace Atlassian.Bitbucket.DataCenter { public class BitbucketOAuth2Client : Bitbucket.BitbucketOAuth2Client { public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2 trace2) : base(httpClient, GetEndpoints(settings), GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings), trace2) { } public override IEnumerable Scopes => new string[] { DataCenterConstants.OAuthScopes.PublicRepos, DataCenterConstants.OAuthScopes.RepoRead, DataCenterConstants.OAuthScopes.RepoWrite }; private static string GetClientId(ISettings settings) { // Check for developer override value if (settings.TryGetSetting( DataCenterConstants.EnvironmentVariables.OAuthClientId, Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientId, out string clientId)) { return clientId; } throw new ArgumentException("Bitbucket DC OAuth Client ID must be defined"); } private static Uri GetRedirectUri(ISettings settings) { // Check for developer override value if (settings.TryGetSetting( DataCenterConstants.EnvironmentVariables.OAuthRedirectUri, Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthRedirectUri, out string redirectUriStr) && Uri.TryCreate(redirectUriStr, UriKind.Absolute, out Uri redirectUri)) { return redirectUri; } return DataCenterConstants.OAuth2RedirectUri; } private static string GetClientSecret(ISettings settings) { // Check for developer override value if (settings.TryGetSetting( DataCenterConstants.EnvironmentVariables.OAuthClientSecret, Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientSecret, out string clientSecret)) { return clientSecret; } throw new ArgumentException("Bitbucket DC OAuth Client Secret must be defined"); } private static OAuth2ServerEndpoints GetEndpoints(ISettings settings) { var remoteUri = settings.RemoteUri; if (remoteUri == null) { throw new ArgumentException("RemoteUri must be defined to generate Bitbucket DC OAuth2 endpoint Urls"); } return new OAuth2ServerEndpoints( new Uri(BitbucketHelper.GetBaseUri(remoteUri) + "/rest/oauth2/latest/authorize"), new Uri(BitbucketHelper.GetBaseUri(remoteUri) + "/rest/oauth2/latest/token") ); } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using GitCredentialManager; using System.Text.Json; using System.Text.Json.Serialization; namespace Atlassian.Bitbucket.DataCenter { public class BitbucketRestApi : IBitbucketRestApi { private readonly ICommandContext _context; private HttpClient _httpClient; public BitbucketRestApi(ICommandContext context) { EnsureArgument.NotNull(context, nameof(context)); _context = context; } public async Task> GetUserInformationAsync(string userName, string password, bool isBearerToken) { if (_context.Settings.TryGetSetting( BitbucketConstants.EnvironmentVariables.ValidateStoredCredentials, Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials, out string validateStoredCredentials) && !validateStoredCredentials.ToBooleanyOrDefault(true)) { _context.Trace.WriteLine($"Skipping retreival of user information due to {BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials} = {validateStoredCredentials}"); return new RestApiResult(HttpStatusCode.OK, new UserInfo() { UserName = DataCenterConstants.OAuthUserName });; } // Bitbucket Server/DC doesn't actually provide a REST API we can use to trade an access_token for the owning username, // therefore this is always going to return a placeholder username, however this call does provide a way to validate the // credentials we do have var requestUri = new Uri(ApiUri, "api/1.0/users"); using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) { if (isBearerToken) { request.AddBearerAuthenticationHeader(password); } else { request.AddBasicAuthenticationHeader(userName, password); } _context.Trace.WriteLine($"HTTP: GET {requestUri}"); using (HttpResponseMessage response = await HttpClient.SendAsync(request)) { _context.Trace.WriteLine($"HTTP: Response {(int) response.StatusCode} [{response.StatusCode}]"); string json = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { // No REST API in BBS that can be used to return just my user account based on my login AFAIK. // but we can prove the credentials work. return new RestApiResult(HttpStatusCode.OK, new UserInfo() { UserName = DataCenterConstants.OAuthUserName }); } return new RestApiResult(response.StatusCode); } } } public async Task IsOAuthInstalledAsync() { var requestUri = new Uri(ApiUri.AbsoluteUri + "oauth2/1.0/client"); using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) { _context.Trace.WriteLine($"HTTP: GET {requestUri}"); using (HttpResponseMessage response = await HttpClient.SendAsync(request)) { _context.Trace.WriteLine($"HTTP: Response {(int)response.StatusCode} [{response.StatusCode}]"); if (HttpStatusCode.Unauthorized == response.StatusCode) { // accessed anonymously so no access but it does exist. return true; } return false; } } } public async Task> GetAuthenticationMethodsAsync() { var authenticationMethods = new List(); var requestUri = new Uri(ApiUri.AbsoluteUri + "authconfig/1.0/login-options"); using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) { _context.Trace.WriteLine($"HTTP: GET {requestUri}"); using (HttpResponseMessage response = await HttpClient.SendAsync(request)) { _context.Trace.WriteLine($"HTTP: Response {(int)response.StatusCode} [{response.StatusCode}]"); string json = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { var loginOptions = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); if (loginOptions.Results.Any(r => "LOGIN_FORM".Equals(r.Type))) { authenticationMethods.Add(AuthenticationMethod.BasicAuth); } if (loginOptions.Results.Any(r => "IDP".Equals(r.Type))) { authenticationMethods.Add(AuthenticationMethod.Sso); } } } } return authenticationMethods; } public void Dispose() { _httpClient?.Dispose(); } private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); private Uri ApiUri { get { var remoteUri = _context.Settings?.RemoteUri; if (remoteUri == null) { throw new ArgumentException("RemoteUri must be defined to generate Bitbucket DC OAuth2 endpoint Urls"); } return new Uri(BitbucketHelper.GetBaseUri(remoteUri) + "/rest/"); } } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/DataCenter/DataCenterConstants.cs ================================================ using System; namespace Atlassian.Bitbucket.DataCenter { public static class DataCenterConstants { public static class OAuthScopes { public const string PublicRepos = "PUBLIC_REPOS"; public const string RepoWrite = "REPO_WRITE"; public const string RepoRead = "REPO_READ"; } public static readonly Uri OAuth2RedirectUri = new Uri("http://localhost:34106/"); /// /// Supported authentication modes for Bitbucket Server/DC /// public const AuthenticationModes ServerAuthenticationModes = AuthenticationModes.Basic | AuthenticationModes.OAuth; /// /// Bitbucket Server/DC does not have a REST API we can use to trade an OAuth access_token for the owning username. /// However one is needed to construct the Basic Auth request made by Git HTTP requests, therefore use a hardcoded /// placeholder for the username. /// public const string OAuthUserName = "OAUTH_USERNAME"; public static class EnvironmentVariables { public const string OAuthClientId = "GCM_BITBUCKET_DATACENTER_CLIENTID"; public const string OAuthClientSecret = "GCM_BITBUCKET_DATACENTER_CLIENTSECRET"; public const string OAuthRedirectUri = "GCM_BITBUCKET_DATACENTER_OAUTH_REDIRECTURI"; } public static class GitConfiguration { public static class Credential { public const string OAuthClientId = "bitbucketDataCenterOAuthClientId"; public const string OAuthClientSecret = "bitbucketDataCenterOAuthClientSecret"; public const string OAuthRedirectUri = "bitbucketDataCenterOauthRedirectUri"; } } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/DataCenter/LoginOption.cs ================================================ using System.Text.Json.Serialization; namespace Atlassian.Bitbucket.DataCenter { public class LoginOption { [JsonPropertyName("type")] public string Type { get ; set; } [JsonPropertyName("id")] public int Id { get; set; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/DataCenter/LoginOptions.cs ================================================ using System.Collections.Generic; using System.Text.Json.Serialization; namespace Atlassian.Bitbucket.DataCenter { public class LoginOptions { [JsonPropertyName("results")] public List Results { get; set; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/DataCenter/UserInfo.cs ================================================ using System; namespace Atlassian.Bitbucket.DataCenter { public class UserInfo : IUserInfo { public string UserName { get; set; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/IBitbucketRestApi.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Atlassian.Bitbucket { public interface IBitbucketRestApi : IDisposable { Task> GetUserInformationAsync(string userName, string password, bool isBearerToken); Task IsOAuthInstalledAsync(); Task> GetAuthenticationMethodsAsync(); } } ================================================ FILE: src/shared/Atlassian.Bitbucket/IRegistry.cs ================================================ using System; using GitCredentialManager; namespace Atlassian.Bitbucket { public interface IRegistry : IDisposable { T Get(InputArguments input); } } ================================================ FILE: src/shared/Atlassian.Bitbucket/IUserInfo.cs ================================================ using System; namespace Atlassian.Bitbucket { public interface IUserInfo { string UserName{ get; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/InternalsVisibleTo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Atlassian.Bitbucket.Tests")] ================================================ FILE: src/shared/Atlassian.Bitbucket/OAuth2ClientRegistry.cs ================================================ using System.Net.Http; using GitCredentialManager; namespace Atlassian.Bitbucket { public class OAuth2ClientRegistry : DisposableObject, IRegistry { private readonly ICommandContext _context; private HttpClient _httpClient; private Cloud.BitbucketOAuth2Client _cloudClient; private DataCenter.BitbucketOAuth2Client _dataCenterClient; public OAuth2ClientRegistry(ICommandContext context) { EnsureArgument.NotNull(context, nameof(context)); _context = context; } public BitbucketOAuth2Client Get(InputArguments input) { if (!BitbucketHelper.IsBitbucketOrg(input)) { return DataCenterClient; } return CloudClient; } protected override void ReleaseManagedResources() { _httpClient?.Dispose(); _cloudClient = null; _dataCenterClient = null; base.ReleaseManagedResources(); } private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); private Cloud.BitbucketOAuth2Client CloudClient => _cloudClient ??= new Cloud.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); private DataCenter.BitbucketOAuth2Client DataCenterClient => _dataCenterClient ??= new DataCenter.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); } } ================================================ FILE: src/shared/Atlassian.Bitbucket/RestApiResult.cs ================================================ using System.Net; namespace Atlassian.Bitbucket { public class RestApiResult { public RestApiResult(HttpStatusCode statusCode) : this(statusCode, default(T)) { } public RestApiResult(HttpStatusCode statusCode, T response) { StatusCode = statusCode; Response = response; } public HttpStatusCode StatusCode { get; } public T Response { get; } public bool Succeeded => 199 < (int)StatusCode && (int)StatusCode < 300; } } ================================================ FILE: src/shared/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs ================================================ using System; using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.Threading; using System.Threading.Tasks; using Atlassian.Bitbucket.UI.ViewModels; using GitCredentialManager; using GitCredentialManager.UI; namespace Atlassian.Bitbucket.UI.Commands { public abstract class CredentialsCommand : HelperCommand { protected CredentialsCommand(ICommandContext context) : base(context, "prompt", "Show authentication prompt.") { var url = new Option("--url", "Bitbucket Server or Data Center URL"); AddOption(url); var userName = new Option("--username", "Username or email."); AddOption(userName); var oauth = new Option("--show-oauth", "Show OAuth option."); AddOption(oauth); var basic = new Option("--show-basic", "Show username/password option."); AddOption(basic); this.SetHandler(ExecuteAsync, url, userName, oauth, basic); } private async Task ExecuteAsync(Uri url, string userName, bool showOAuth, bool showBasic) { var viewModel = new CredentialsViewModel(Context.SessionManager) { Url = url, UserName = userName, ShowOAuth = showOAuth, ShowBasic = showBasic }; await ShowAsync(viewModel, CancellationToken.None); if (!viewModel.WindowResult || viewModel.SelectedMode == AuthenticationModes.None) { throw new Trace2Exception(Context.Trace2, "User cancelled dialog."); } switch (viewModel.SelectedMode) { case AuthenticationModes.OAuth: WriteResult(new Dictionary { ["mode"] = "oauth" }); break; case AuthenticationModes.Basic: WriteResult(new Dictionary { ["mode"] = "basic", ["username"] = viewModel.UserName, ["password"] = viewModel.Password, }); break; default: throw new ArgumentOutOfRangeException(nameof(AuthenticationModes), "Unknown authentication mode", viewModel.SelectedMode.ToString()); } return 0; } protected abstract Task ShowAsync(CredentialsViewModel viewModel, CancellationToken ct); } } ================================================ FILE: src/shared/Atlassian.Bitbucket/UI/ViewModels/CredentialsViewModel.cs ================================================ using System; using System.ComponentModel; using System.Windows.Input; using GitCredentialManager; using GitCredentialManager.UI; using GitCredentialManager.UI.ViewModels; namespace Atlassian.Bitbucket.UI.ViewModels { public class CredentialsViewModel : WindowViewModel { private readonly ISessionManager _sessionManager; private Uri _url; private string _userName; private string _password; private bool _showOAuth; private bool _showBasic; public CredentialsViewModel() { // Constructor the XAML designer } public CredentialsViewModel(ISessionManager sessionManager) { EnsureArgument.NotNull(sessionManager, nameof(sessionManager)); _sessionManager = sessionManager; Title = "Connect to Bitbucket"; LoginCommand = new RelayCommand(AcceptBasic, CanLogin); CancelCommand = new RelayCommand(Cancel); OAuthCommand = new RelayCommand(AcceptOAuth, CanAcceptOAuth); ForgotPasswordCommand = new RelayCommand(ForgotPassword); SignUpCommand = new RelayCommand(SignUp); PropertyChanged += OnPropertyChanged; } private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(UserName): case nameof(Password): LoginCommand.RaiseCanExecuteChanged(); break; } } private bool CanLogin() { return !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password); } private void AcceptBasic() { SelectedMode = AuthenticationModes.Basic; Accept(); } private void AcceptOAuth() { SelectedMode = AuthenticationModes.OAuth; Accept(); } private bool CanAcceptOAuth() { return ShowOAuth; } private void ForgotPassword() { Uri passwordResetUri = _url is null ? new Uri(BitbucketConstants.HelpUrls.PasswordReset) : new Uri(_url, BitbucketConstants.HelpUrls.DataCenterPasswordReset); _sessionManager.OpenBrowser(passwordResetUri); } private void SignUp() { Uri signUpUri = _url is null ? new Uri(BitbucketConstants.HelpUrls.SignUp) : new Uri(_url, BitbucketConstants.HelpUrls.DataCenterLogin); _sessionManager.OpenBrowser(signUpUri); } public Uri Url { get => _url; set => SetAndRaisePropertyChanged(ref _url, value); } public string UserName { get => _userName; set => SetAndRaisePropertyChanged(ref _userName, value); } public string Password { get => _password; set => SetAndRaisePropertyChanged(ref _password, value); } /// /// Show the OAuth option. /// public bool ShowOAuth { get => _showOAuth; set => SetAndRaisePropertyChanged(ref _showOAuth, value); } /// /// Show the basic authentication options. /// public bool ShowBasic { get => _showBasic; set => SetAndRaisePropertyChanged(ref _showBasic, value); } public AuthenticationModes SelectedMode { get; private set; } /// /// Start the process to validate the username/password /// public RelayCommand LoginCommand { get; } /// /// Cancel the authentication attempt. /// public ICommand CancelCommand { get; } /// /// Use OAuth authentication instead of username/password. /// public ICommand OAuthCommand { get; } /// /// Hyperlink to the Bitbucket forgotten password process. /// public ICommand ForgotPasswordCommand { get; } /// /// Hyperlink to the Bitbucket sign up process. /// public ICommand SignUpCommand { get; } } } ================================================ FILE: src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml ================================================