├── .code-coverage └── coverlet.settings.xml ├── .github ├── ISSUE_TEMPLATE │ ├── auth-issue.yml │ └── feature-req.yml ├── dependabot.yml ├── run_esrp_signing.py ├── set_up_esrp.ps1 └── workflows │ ├── codeql-analysis.yml │ ├── continuous-integration.yml │ ├── lint-docs.yml │ ├── maintainer-absence.yml │ ├── release-dotnet-tool.yaml │ ├── release-homebrew.yaml │ ├── release.yml │ └── validate-install-from-source.yml ├── .gitignore ├── .lycheeignore ├── .markdownlint.jsonc ├── .vscode ├── launch.json └── tasks.json ├── 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-banner.png ├── gcm-transparent.png ├── gcm.svg ├── gcm.xaml ├── gcmicon.ico ├── gcmicon128.bmp ├── gcmicon16.bmp ├── gcmicon256.bmp ├── gcmicon32.bmp ├── gcmicon64.bmp └── gcmweb.png ├── 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 ├── img │ ├── aad-bitlocker.png │ ├── aad-disconnect.png │ ├── aad-questions-21H1.png │ ├── aad-questions.png │ ├── aad-work-school.png │ ├── all-microsoft.png │ ├── app-password.png │ ├── apps-must-ask.png │ ├── gcmcore-rename.png │ ├── get-signed-in.png │ ├── github-display-pat.png │ ├── github-generate-pat.png │ ├── github-oauthapp-revoke.png │ ├── github-pat-gist-scope.png │ ├── github-pat-note.png │ ├── github-pat-repo-scope.png │ ├── github-pat-workflow-scope.png │ ├── gitlab-oauthapp-revoke.png │ ├── gitlab-oauthapp-revoked.png │ ├── msa-confirm.png │ ├── msa-remove.png │ ├── windows-cli-save-pat.png │ ├── windows-gui-add-pat.png │ └── windows-gui-credentials.png ├── install.md ├── linux-fromsrc-uninstall.md ├── linux-validate-gpg.md ├── migration.md ├── multiple-users.md ├── netconfig.md ├── rename.md ├── usage.md ├── windows-broker.md └── wsl.md ├── 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 │ ├── background.png │ └── en.lproj │ │ ├── LICENSE │ │ ├── conclusion.html │ │ └── welcome.html │ ├── scripts │ └── postinstall │ └── uninstall.sh ├── shared ├── 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 ├── 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 │ │ ├── Assets │ │ └── atlassian-logo.png │ │ ├── Commands │ │ └── CredentialsCommand.cs │ │ ├── ViewModels │ │ └── CredentialsViewModel.cs │ │ └── Views │ │ ├── CredentialsView.axaml │ │ └── CredentialsView.axaml.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 │ ├── GitTests.cs │ ├── GitVersionTests.cs │ ├── HostProviderRegistryTests.cs │ ├── HostProviderTests.cs │ ├── HttpClientExtensionsTests.cs │ ├── HttpClientFactoryTests.cs │ ├── HttpRequestExtensionsTests.cs │ ├── IniFileTests.cs │ ├── InputArgumentsTests.cs │ ├── Interop │ │ ├── Linux │ │ │ ├── LinuxFileSystemTests.cs │ │ │ └── SecretServiceCollectionTests.cs │ │ ├── MacOS │ │ │ ├── MacOSFileSystemTests.cs │ │ │ └── MacOSKeychainTests.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 ├── 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 │ ├── BrowserUtils.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 │ ├── 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 │ │ │ ├── LinuxFileSystem.cs │ │ │ ├── LinuxSessionManager.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 │ │ │ ├── MacOSSessionManager.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 │ ├── 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 │ │ │ ├── OAuthViewModel.cs │ │ │ ├── ViewModel.cs │ │ │ └── WindowViewModel.cs │ │ └── Views │ │ │ ├── CredentialsView.axaml │ │ │ ├── CredentialsView.axaml.cs │ │ │ ├── DefaultAccountView.axaml │ │ │ ├── DefaultAccountView.axaml.cs │ │ │ ├── DeviceCodeView.axaml │ │ │ ├── DeviceCodeView.axaml.cs │ │ │ ├── OAuthView.axaml │ │ │ └── OAuthView.axaml.cs │ ├── UriExtensions.cs │ ├── WslUtils.cs │ └── X509Utils.cs ├── Directory.Build.props ├── DotnetTool │ ├── DotnetTool.csproj │ ├── DotnetToolSettings.xml │ ├── dotnet-tool.nuspec │ ├── icon.png │ ├── layout.sh │ └── pack.sh ├── Git-Credential-Manager │ ├── Git-Credential-Manager.csproj │ ├── NOTICE │ └── Program.cs ├── GitHub.Tests │ ├── GitHub.Tests.csproj │ ├── GitHubAuthChallengeTests.cs │ ├── GitHubAuthenticationTests.cs │ ├── GitHubHostProviderTests.cs │ └── GitHubRestApiTests.cs ├── GitHub.UI.Avalonia │ └── Commands │ │ └── SelectAccountCommandImpl.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 ├── GitLab.Tests │ ├── GitLab.Tests.csproj │ ├── GitLabAuthenticationTests.cs │ └── GitLabHostProviderTests.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 ├── Microsoft.AzureRepos.Tests │ ├── AzureDevOpsApiTests.cs │ ├── AzureReposAuthorityCacheTests.cs │ ├── AzureReposBindingManagerTests.cs │ ├── AzureReposHostProviderTests.cs │ ├── Microsoft.AzureRepos.Tests.csproj │ └── UriHelpersTests.cs ├── Microsoft.AzureRepos │ ├── AzureDevOpsAuthorityCache.cs │ ├── AzureDevOpsConstants.cs │ ├── AzureDevOpsRestApi.cs │ ├── AzureReposBindingManager.cs │ ├── AzureReposHostProvider.cs │ ├── InternalsVisibleTo.cs │ ├── Microsoft.AzureRepos.csproj │ └── UriHelpers.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 /.code-coverage/coverlet.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | cobertura,lcov 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-req.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: A suggestion for a new feature in Git Credential Manager. 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this feature request! 9 | 10 | Please be as detailed as possible describing your idea; how it fixes a problem or makes something easier. 11 | 12 | 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: 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Feature description 17 | description: | 18 | A clear and concise description of the new feature. 19 | placeholder: | 20 | ex: Add spline reticulation option to widget authentication mechanism. 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | 5 | # Enable version updates for GitHub ecosystem 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/set_up_esrp.ps1: -------------------------------------------------------------------------------- 1 | # Install ESRP client 2 | az storage blob download --file esrp.zip --auth-mode login --account-name $env:AZURE_STORAGE_ACCOUNT --container $env:AZURE_STORAGE_CONTAINER --name $env:ESRP_TOOL 3 | Expand-Archive -Path esrp.zip -DestinationPath .\esrp 4 | 5 | # Install certificates 6 | az keyvault secret download --vault-name "$env:AZURE_VAULT" --name "$env:AUTH_CERT" --file out.pfx 7 | certutil -f -importpfx out.pfx 8 | Remove-Item out.pfx 9 | 10 | az keyvault secret download --vault-name "$env:AZURE_VAULT" --name "$env:REQUEST_SIGNING_CERT" --file out.pfx 11 | certutil -f -importpfx out.pfx 12 | Remove-Item out.pfx -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main, release ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ main ] 9 | 10 | jobs: 11 | analyze: 12 | name: Analyze 13 | runs-on: ubuntu-latest 14 | permissions: 15 | actions: read 16 | contents: read 17 | security-events: write 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | language: [ 'csharp' ] 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | # Initializes the CodeQL tools for scanning. 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v3 30 | with: 31 | languages: ${{ matrix.language }} 32 | 33 | - run: | 34 | dotnet build 35 | 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@v3 38 | -------------------------------------------------------------------------------- /.github/workflows/release-dotnet-tool.yaml: -------------------------------------------------------------------------------- 1 | name: release-dotnet-tool 2 | on: 3 | release: 4 | types: [released] 5 | 6 | jobs: 7 | release: 8 | runs-on: windows-latest 9 | environment: release 10 | steps: 11 | - name: Download NuGet package from release and publish 12 | run: | 13 | # Get asset information 14 | $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json 15 | $asset = $github.release.assets | Where-Object -Property name -match '.nupkg$' 16 | 17 | # Download asset 18 | Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $asset.name 19 | 20 | # Publish asset 21 | dotnet nuget push $asset.name --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json 22 | shell: powershell 23 | -------------------------------------------------------------------------------- /.github/workflows/release-homebrew.yaml: -------------------------------------------------------------------------------- 1 | name: "release-homebrew" 2 | on: 3 | release: 4 | types: [released] 5 | 6 | jobs: 7 | release: 8 | runs-on: macos-latest 9 | environment: release 10 | env: 11 | HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_TOKEN }} 12 | steps: 13 | - name: Open PR against homebrew/homebrew-cask 14 | run: | 15 | # Get latest version 16 | version=$(curl --silent "https://api.github.com/repos/git-ecosystem/git-credential-manager/releases/latest" | 17 | grep '"tag_name":' | 18 | sed -E 's/.*"v([0-9\.]+).*/\1/') 19 | 20 | # Ensure local Homebrew repository is up to date 21 | cd "$(brew --repository homebrew/cask)" 22 | git pull 23 | 24 | # Open PR to update to latest version 25 | brew bump-cask-pr git-credential-manager --version $version --no-audit --no-browse 26 | -------------------------------------------------------------------------------- /.github/workflows/validate-install-from-source.yml: -------------------------------------------------------------------------------- 1 | name: validate-install-from-source 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | docker: 11 | name: ${{matrix.vector.image}} 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | vector: 17 | - image: ubuntu 18 | - image: debian:bullseye 19 | - image: fedora 20 | # Centos no longer officially maintains images on Docker Hub. However, 21 | # tgagor is a contributor who pushes updated images weekly, which should 22 | # be sufficient for our validation needs. 23 | - image: tgagor/centos 24 | - image: tgagor/centos-stream 25 | - image: redhat/ubi8 26 | - image: alpine 27 | - image: opensuse/leap 28 | - image: opensuse/tumbleweed 29 | - image: registry.suse.com/suse/sle15:15.4.27.11.31 30 | container: ${{matrix.vector.image}} 31 | steps: 32 | - run: | 33 | if [[ ${{matrix.vector.image}} == *"suse"* ]]; then 34 | zypper -n install tar gzip 35 | elif [[ ${{matrix.vector.image}} == *"centos"* ]]; then 36 | dnf install which -y 37 | fi 38 | 39 | - uses: actions/checkout@v4 40 | 41 | - run: | 42 | sh "${GITHUB_WORKSPACE}/src/linux/Packaging.Linux/install-from-source.sh" -y 43 | git-credential-manager --help || exit 1 44 | -------------------------------------------------------------------------------- /.lycheeignore: -------------------------------------------------------------------------------- 1 | godaddy\.com[\\/]?$ 2 | gitlab\.com/-/profile/applications 3 | file:[\\/][\\/][\\/].* 4 | -------------------------------------------------------------------------------- /.markdownlint.jsonc: -------------------------------------------------------------------------------- 1 | // For information on writing markdownlint configuration see: 2 | // https://github.com/DavidAnson/markdownlint/blob/main/README.md#optionsconfig 3 | { 4 | "MD013": { 5 | "line_length": 80, 6 | "code_blocks": false, 7 | "headings": false, 8 | "tables": false 9 | }, 10 | "MD024": false // The format for some files require repeated headings, e.g. "Example" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Git Credential Manager (get)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/net7.0/git-credential-manager.dll", 14 | "args": ["get"], 15 | "cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager", 16 | "console": "integratedTerminal", 17 | "stopAtEntry": false, 18 | }, 19 | { 20 | "name": "Git Credential Manager (store)", 21 | "type": "coreclr", 22 | "request": "launch", 23 | "preLaunchTask": "build", 24 | // If you have changed target frameworks, make sure to update the program path. 25 | "program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/net7.0/git-credential-manager.dll", 26 | "args": ["store"], 27 | "cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager", 28 | "console": "integratedTerminal", 29 | "stopAtEntry": false, 30 | }, 31 | { 32 | "name": ".NET Attach", 33 | "type": "coreclr", 34 | "request": "attach", 35 | "processId": "${command:pickProcess}" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 10 | windows 11 | osx 12 | linux 13 | true 14 | false 15 | 16 | 17 | $(MSBuildThisFileDirectory) 18 | $(RepoPath)src\ 19 | $(RepoPath)out\ 20 | $(RepoPath)assets\ 21 | 22 | 23 | <_IsExeProject Condition="'$(OutputType)' == 'Exe' OR '$(OutputType)' == 'WinExe'">true 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 7.0.2 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | $(IntermediateOutputPath)app.manifest 20 | 21 | 22 | 23 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Git-Credential-Manager.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | OS 3 | No 4 | True 5 | True 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Git Credential Manager 2 | Copyright © GitHub, Inc. and contributors 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE 23 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | If you discover a security issue in this repo, please submit it through the 4 | [GitHub Security Bug Bounty][hackerone-github] 5 | 6 | Thanks for helping make GitHub products safe for everyone. 7 | 8 | [hackerone-github]: https://hackerone.com/github 9 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.4.1.0 2 | -------------------------------------------------------------------------------- /assets/gcm-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcm-banner.png -------------------------------------------------------------------------------- /assets/gcm-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcm-transparent.png -------------------------------------------------------------------------------- /assets/gcmicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmicon.ico -------------------------------------------------------------------------------- /assets/gcmicon128.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmicon128.bmp -------------------------------------------------------------------------------- /assets/gcmicon16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmicon16.bmp -------------------------------------------------------------------------------- /assets/gcmicon256.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmicon256.bmp -------------------------------------------------------------------------------- /assets/gcmicon32.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmicon32.bmp -------------------------------------------------------------------------------- /assets/gcmicon64.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmicon64.bmp -------------------------------------------------------------------------------- /assets/gcmweb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/assets/gcmweb.png -------------------------------------------------------------------------------- /build/GCM.MSBuild.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /build/GCM.tasks: -------------------------------------------------------------------------------- 1 | 2 | 3 | <_TaskAssembly>$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll 4 | <_TaskFactory>CodeTaskFactory 5 | 6 | 7 | <_TaskAssembly>$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll 8 | <_TaskFactory>RoslynCodeTaskFactory 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /build/GenerateWindowsAppManifest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Framework; 2 | using Microsoft.Build.Utilities; 3 | using System.IO; 4 | 5 | namespace GitCredentialManager.MSBuild 6 | { 7 | public class GenerateWindowsAppManifest : Task 8 | { 9 | [Required] 10 | public string Version { get; set; } 11 | 12 | [Required] 13 | public string ApplicationName { get; set; } 14 | 15 | [Required] 16 | public string OutputFile { get; set; } 17 | 18 | public override bool Execute() 19 | { 20 | Log.LogMessage(MessageImportance.Normal, "Creating application manifest file for '{0}'...", ApplicationName); 21 | 22 | string manifestDirectory = Path.GetDirectoryName(OutputFile); 23 | if (!Directory.Exists(manifestDirectory)) 24 | { 25 | Directory.CreateDirectory(manifestDirectory); 26 | } 27 | 28 | File.WriteAllText( 29 | OutputFile, 30 | $@" 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | "); 47 | 48 | return true; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /build/GetVersion.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Framework; 2 | using Microsoft.Build.Utilities; 3 | using System.IO; 4 | 5 | namespace GitCredentialManager.MSBuild 6 | { 7 | public class GetVersion : Task 8 | { 9 | [Required] 10 | public string VersionFile { get; set; } 11 | 12 | [Output] 13 | public string Version { get; set; } 14 | 15 | [Output] 16 | public string AssemblyVersion { get; set; } 17 | 18 | [Output] 19 | public string FileVersion { get; set; } 20 | 21 | public override bool Execute() 22 | { 23 | Log.LogMessage(MessageImportance.Normal, "Reading VERSION file..."); 24 | string textVersion = File.ReadAllText(VersionFile); 25 | 26 | if (!System.Version.TryParse(textVersion, out System.Version fullVersion)) 27 | { 28 | Log.LogError("Invalid version '{0}' specified.", textVersion); 29 | return false; 30 | } 31 | 32 | // System.Version names its version components as follows: 33 | // major.minor[.build[.revision]] 34 | // The main version number we use for GCM contains the first three 35 | // components. 36 | // The assembly and file version numbers contain all components, as 37 | // ommitting the revision portion from these properties causes 38 | // runtime failures on Windows. 39 | Version = $"{fullVersion.Major}.{fullVersion.Minor}.{fullVersion.Build}"; 40 | AssemblyVersion = FileVersion = fullVersion.ToString(); 41 | 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # User documentation 2 | 3 | The following are links to GCM user support documentation: 4 | 5 | - [Frequently asked questions][gcm-faq] 6 | - [Command-line usage][gcm-usage] 7 | - [Configuration options][gcm-config] 8 | - [Environment variables][gcm-env] 9 | - [Enterprise configuration][gcm-enterprise-config] 10 | - [Network and HTTP configuration][gcm-net-config] 11 | - [Credential stores][gcm-credstores] 12 | - [Host provider specification][gcm-host-provider] 13 | - [Azure Repos OAuth tokens][gcm-azure-tokens] 14 | - [Azure Managed Identities and Service Principals][gcm-misp] 15 | - [GitLab support][gcm-gitlab] 16 | - [Generic OAuth support][gcm-oauth] 17 | 18 | [gcm-azure-tokens]: azrepos-users-and-tokens.md 19 | [gcm-config]: configuration.md 20 | [gcm-credstores]: credstores.md 21 | [gcm-enterprise-config]: enterprise-config.md 22 | [gcm-env]: environment.md 23 | [gcm-faq]: faq.md 24 | [gcm-gitlab]: gitlab.md 25 | [gcm-host-provider]: hostprovider.md 26 | [gcm-misp]: azrepos-misp.md 27 | [gcm-net-config]: netconfig.md 28 | [gcm-oauth]: generic-oauth.md 29 | [gcm-usage]: usage.md 30 | -------------------------------------------------------------------------------- /docs/img/aad-bitlocker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/aad-bitlocker.png -------------------------------------------------------------------------------- /docs/img/aad-disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/aad-disconnect.png -------------------------------------------------------------------------------- /docs/img/aad-questions-21H1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/aad-questions-21H1.png -------------------------------------------------------------------------------- /docs/img/aad-questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/aad-questions.png -------------------------------------------------------------------------------- /docs/img/aad-work-school.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/aad-work-school.png -------------------------------------------------------------------------------- /docs/img/all-microsoft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/all-microsoft.png -------------------------------------------------------------------------------- /docs/img/app-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/app-password.png -------------------------------------------------------------------------------- /docs/img/apps-must-ask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/apps-must-ask.png -------------------------------------------------------------------------------- /docs/img/gcmcore-rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/gcmcore-rename.png -------------------------------------------------------------------------------- /docs/img/get-signed-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/get-signed-in.png -------------------------------------------------------------------------------- /docs/img/github-display-pat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-display-pat.png -------------------------------------------------------------------------------- /docs/img/github-generate-pat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-generate-pat.png -------------------------------------------------------------------------------- /docs/img/github-oauthapp-revoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-oauthapp-revoke.png -------------------------------------------------------------------------------- /docs/img/github-pat-gist-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-pat-gist-scope.png -------------------------------------------------------------------------------- /docs/img/github-pat-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-pat-note.png -------------------------------------------------------------------------------- /docs/img/github-pat-repo-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-pat-repo-scope.png -------------------------------------------------------------------------------- /docs/img/github-pat-workflow-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/github-pat-workflow-scope.png -------------------------------------------------------------------------------- /docs/img/gitlab-oauthapp-revoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/gitlab-oauthapp-revoke.png -------------------------------------------------------------------------------- /docs/img/gitlab-oauthapp-revoked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/gitlab-oauthapp-revoked.png -------------------------------------------------------------------------------- /docs/img/msa-confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/msa-confirm.png -------------------------------------------------------------------------------- /docs/img/msa-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/msa-remove.png -------------------------------------------------------------------------------- /docs/img/windows-cli-save-pat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/windows-cli-save-pat.png -------------------------------------------------------------------------------- /docs/img/windows-gui-add-pat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/windows-gui-add-pat.png -------------------------------------------------------------------------------- /docs/img/windows-gui-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/docs/img/windows-gui-credentials.png -------------------------------------------------------------------------------- /docs/migration.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | ## Migrating from Git Credential Manager for Windows 4 | 5 | ### GCM_AUTHORITY 6 | 7 | This setting (and the corresponding `credential.authority` configuration) is 8 | deprecated and should be replaced with the `GCM_PROVIDER` (or corresponding 9 | `credential.authority` configuration) setting. 10 | 11 | Because both Basic HTTP authentication and Windows Integrated Authentication 12 | (WIA) are now handled by one provider, if you specified `basic` as your 13 | authority you also need to disable WIA using `GCM_ALLOW_WINDOWSAUTH` / 14 | `credential.allowWindowsAuth`. 15 | 16 | The following table shows the correct replacement for all legacy authorities 17 | values: 18 | 19 | GCM_AUTHORITY (credential.authority)|→|GCM_PROVIDER (credential.provider)|GCM_ALLOW_WINDOWSAUTH (credential.allowWindowsAuth) 20 | -|-|-|- 21 | `msa`, `microsoft`, `microsoftaccount`, `aad`, `azure`, `azuredirectory`, `live`, `liveconnect`, `liveid`|→|`azure-repos`|_N/A_ 22 | `github`|→|`github`|_N/A_ 23 | `basic`|→|`generic`|`false` 24 | `integrated`, `windows`, `kerberos`, `ntlm`, `tfs`, `sso`|→|`generic`|`true` _(default)_ 25 | 26 | For example if you had previous set the authority for the `example.com` host to 27 | `basic`.. 28 | 29 | ```shell 30 | git config --global credential.example.com.authority basic 31 | ``` 32 | 33 | ..then you can replace this with the following.. 34 | 35 | ```shell 36 | git config --global --unset credential.example.com.authority 37 | git config --global credential.example.com.provider generic 38 | git config --global credential.example.com.allowWindowsAuth false 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Command-line usage 2 | 3 | After installation, Git will use Git Credential Manager and you will only need 4 | to interact with any authentication dialogs asking for credentials. 5 | GCM stays invisible as much as possible, so ideally you’ll forget that you’re 6 | depending on GCM at all. 7 | 8 | Assuming GCM has been installed, use your favorite terminal to execute the 9 | following commands to interact directly with GCM. 10 | 11 | ```shell 12 | git credential-manager [ []] 13 | ``` 14 | 15 | ## Commands 16 | 17 | ### --help / -h / -? 18 | 19 | Displays a list of available commands. 20 | 21 | ### --version 22 | 23 | Displays the current version. 24 | 25 | ### get / store / erase 26 | 27 | Commands for interaction with Git. You shouldn't need to run these manually. 28 | 29 | Read the [Git manual][git-credentials-custom-helpers] about custom helpers for 30 | more information. 31 | 32 | ### configure/unconfigure 33 | 34 | Set your user-level Git configuration (`~/.gitconfig`) to use GCM. If you pass 35 | `--system` to these commands, they act on the system-level Git configuration 36 | (`/etc/gitconfig`) instead. 37 | 38 | ### azure-repos 39 | 40 | Interact with the Azure Repos host provider to bind/unbind user accounts to 41 | Azure DevOps organizations or specific remote URLs, and manage the 42 | authentication authority cache. 43 | 44 | For more information about managing user account bindings see 45 | [here][azure-access-tokens-ua]. 46 | 47 | [azure-access-tokens-ua]: azrepos-users-and-tokens.md#useraccounts 48 | [git-credentials-custom-helpers]: https://git-scm.com/docs/gitcredentials#_custom_helpers 49 | 50 | ### github 51 | 52 | Interact with the GitHub host provider to manage your accounts on GitHub.com and 53 | GitHub Enterprise Server instances. 54 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/linux/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | $(RepoOutPath)linux\ 9 | $(PlatformOutPath)$(MSBuildProjectName)\ 10 | $(ProjectOutPath)bin\ 11 | $(ProjectOutPath)obj\ 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/linux/Packaging.Linux/Packaging.Linux.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | net7.0 7 | false 8 | 9 | 10 | 11 | false 12 | /usr/local 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/osx/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | Pods/ 38 | # 39 | # Add this line if you want to avoid checking in source code from the Xcode workspace 40 | # *.xcworkspace 41 | 42 | # Carthage 43 | # 44 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 45 | # Carthage/Checkouts 46 | 47 | Carthage/Build 48 | 49 | # fastlane 50 | # 51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 52 | # screenshots whenever they are needed. 53 | # For more information about the recommended setup visit: 54 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 55 | 56 | fastlane/report.xml 57 | fastlane/Preview.html 58 | fastlane/screenshots/**/*.png 59 | fastlane/test_output 60 | 61 | # Code Injection 62 | # 63 | # After new code Injection tools there's a generated folder /iOSInjectionProject 64 | # https://github.com/johnno1962/injectionforxcode 65 | 66 | iOSInjectionProject/ 67 | -------------------------------------------------------------------------------- /src/osx/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | $(RepoOutPath)osx\ 9 | $(PlatformOutPath)$(MSBuildProjectName)\ 10 | $(ProjectOutPath)bin\ 11 | $(ProjectOutPath)obj\ 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/Installer.Mac.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | net7.0 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/codesign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SIGN_DIR=$1 4 | DEVELOPER_ID=$2 5 | ENTITLEMENTS_FILE=$3 6 | 7 | if [ -z "$SIGN_DIR" ]; then 8 | echo "error: missing directory argument" 9 | exit 1 10 | elif [ -z "$DEVELOPER_ID" ]; then 11 | echo "error: missing developer id argument" 12 | exit 1 13 | elif [ -z "$ENTITLEMENTS_FILE" ]; then 14 | echo "error: missing entitlements file argument" 15 | exit 1 16 | fi 17 | 18 | echo "======== INPUTS ========" 19 | echo "Directory: $SIGN_DIR" 20 | echo "Developer ID: $DEVELOPER_ID" 21 | echo "Entitlements: $ENTITLEMENTS_FILE" 22 | echo "======== END INPUTS ========" 23 | 24 | cd $SIGN_DIR 25 | for f in * 26 | do 27 | macho=$(file --mime $f | grep mach) 28 | # Runtime sign dylibs and Mach-O binaries 29 | if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; 30 | then 31 | echo "Runtime Signing $f" 32 | codesign -s "$DEVELOPER_ID" $f --timestamp --force --options=runtime --entitlements $ENTITLEMENTS_FILE 33 | elif [ -d "$f" ]; 34 | then 35 | echo "Signing files in subdirectory $f" 36 | cd $f 37 | for i in * 38 | do 39 | codesign -s "$DEVELOPER_ID" $i --timestamp --force 40 | done 41 | cd .. 42 | else 43 | echo "Signing $f" 44 | codesign -s "$DEVELOPER_ID" $f --timestamp --force 45 | fi 46 | done 47 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/distribution.arm64.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Git Credential Manager 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | com.microsoft.gitcredentialmanager.component.pkg 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/distribution.x64.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Git Credential Manager 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | com.microsoft.gitcredentialmanager.component.pkg 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/entitlements.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/notarize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in "$@" 4 | do 5 | case "$i" in 6 | --package=*) 7 | PACKAGE="${i#*=}" 8 | shift # past argument=value 9 | ;; 10 | --keychain-profile=*) 11 | KEYCHAIN_PROFILE="${i#*=}" 12 | shift # past argument=value 13 | ;; 14 | *) 15 | die "unknown option '$i'" 16 | ;; 17 | esac 18 | done 19 | 20 | if [ -z "$PACKAGE" ]; then 21 | echo "error: missing package argument" 22 | exit 1 23 | elif [ -z "$KEYCHAIN_PROFILE" ]; then 24 | echo "error: missing keychain profile argument" 25 | exit 1 26 | fi 27 | 28 | # Exit as soon as any line fails 29 | set -e 30 | 31 | # Send the notarization request 32 | xcrun notarytool submit -v "$PACKAGE" -p "$KEYCHAIN_PROFILE" --wait 33 | 34 | # Staple the notarization ticket (to allow offline installation) 35 | xcrun stapler staple -v "$PACKAGE" 36 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/resources/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/src/osx/Installer.Mac/resources/background.png -------------------------------------------------------------------------------- /src/osx/Installer.Mac/resources/en.lproj/LICENSE: -------------------------------------------------------------------------------- 1 | ../../../../../LICENSE -------------------------------------------------------------------------------- /src/osx/Installer.Mac/resources/en.lproj/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 |

Git Credential Manager

14 |

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

17 |
18 |
19 |

Installation notes

20 |

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

23 |

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

26 |
27 |
28 |

Learn more

29 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/scripts/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | PACKAGE=$1 5 | INSTALL_DESTINATION=$2 6 | 7 | function IsBrewPkgInstalled 8 | { 9 | # Check if Homebrew is installed 10 | /usr/bin/which brew > /dev/null 11 | if [ $? -eq 0 ] 12 | then 13 | # Check if the package has been installed 14 | brew ls --versions "$1" > /dev/null 15 | if [ $? -eq 0 ] 16 | then 17 | return 0 18 | fi 19 | fi 20 | return 1 21 | } 22 | 23 | # Check if Java GCM is present on this system and unlink it should it exist 24 | if [ -L /usr/local/bin/git-credential-manager ] && IsBrewPkgInstalled "git-credential-manager"; 25 | then 26 | brew unlink git-credential-manager 27 | fi 28 | 29 | # Create symlink to GCM in /usr/local/bin 30 | mkdir -p /usr/local/bin 31 | /bin/ln -Fs "$INSTALL_DESTINATION/git-credential-manager" /usr/local/bin/git-credential-manager 32 | 33 | # Configure GCM for the current user (running as the current user to avoid root 34 | # from taking ownership of ~/.gitconfig) 35 | sudo -u ${USER} "$INSTALL_DESTINATION/git-credential-manager" configure 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /src/osx/Installer.Mac/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" 4 | GCMBIN="$THISDIR/git-credential-manager" 5 | 6 | # Ensure we're running as root 7 | if [ $(id -u) != "0" ] 8 | then 9 | sudo "$0" "$@" 10 | exit $? 11 | fi 12 | 13 | # Unconfigure (as the current user) 14 | echo "Unconfiguring credential helper..." 15 | sudo -u `/usr/bin/logname` -E "$GCMBIN" unconfigure 16 | 17 | # Remove symlink 18 | if [ -L /usr/local/bin/git-credential-manager ] 19 | then 20 | echo "Deleting symlink..." 21 | rm /usr/local/bin/git-credential-manager 22 | else 23 | echo "No symlink found." 24 | fi 25 | 26 | # Remove legacy symlink 27 | if [ -L /usr/local/bin/git-credential-manager-core ] 28 | then 29 | echo "Deleting legacy symlink..." 30 | rm /usr/local/bin/git-credential-manager-core 31 | else 32 | echo "No legacy symlink found." 33 | fi 34 | 35 | # Forget package installation/delete receipt 36 | echo "Removing installation receipt..." 37 | pkgutil --forget com.microsoft.gitcredentialmanager 38 | 39 | # Remove application files 40 | if [ -d "$THISDIR" ] 41 | then 42 | echo "Deleting application files..." 43 | rm -rf "$THISDIR" 44 | else 45 | echo "No application files found." 46 | fi 47 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | false 6 | true 7 | latest 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket.Tests/BitbucketTokenEndpointResponseJsonTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using Xunit; 4 | 5 | namespace Atlassian.Bitbucket.Tests 6 | { 7 | public class BitbucketTokenEndpointResponseJsonTest 8 | { 9 | [Fact] 10 | public void BitbucketTokenEndpointResponseJson_Deserialize_Uses_Scopes() 11 | { 12 | var accessToken = "123"; 13 | var tokenType = "Bearer"; 14 | var expiresIn = 1000; 15 | var scopesString = "x,y,z"; 16 | var scopeString = "a,b,c"; 17 | 18 | var json = $"{{\"access_token\": \"{accessToken}\", \"token_type\": \"{tokenType}\", \"expires_in\": {expiresIn}, \"scopes\": \"{scopesString}\", \"scope\": \"{scopeString}\"}}"; 19 | 20 | var result = JsonSerializer.Deserialize(json, 21 | new JsonSerializerOptions 22 | { 23 | PropertyNameCaseInsensitive = true 24 | }); 25 | 26 | Assert.Equal(accessToken, result.AccessToken); 27 | Assert.Equal(tokenType, result.TokenType); 28 | Assert.Equal(expiresIn, result.ExpiresIn); 29 | Assert.Equal(scopesString, result.Scope); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket.Tests/Cloud/UserInfoTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | using System.Threading.Tasks; 5 | using Atlassian.Bitbucket.Cloud; 6 | using Xunit; 7 | 8 | namespace Atlassian.Bitbucket.Tests.Cloud 9 | { 10 | public class UserInfoTest 11 | { 12 | [Fact] 13 | public void UserInfo_Set() 14 | { 15 | var userInfo = new UserInfo() 16 | { 17 | UserName = "123", 18 | }; 19 | 20 | Assert.Equal("123", userInfo.UserName); 21 | } 22 | 23 | [Fact] 24 | public void Deserialize_UserInfo() 25 | { 26 | var uuid = "{bef4bd75-03fe-4f19-9c6c-ed57b05ab6f6}"; 27 | var userName = "bob"; 28 | var accountId = "123abc"; 29 | 30 | var json = $"{{\"uuid\": \"{uuid}\", \"has_2fa_enabled\": null, \"username\": \"{userName}\", \"account_id\": \"{accountId}\"}}"; 31 | 32 | var result = JsonSerializer.Deserialize(json, new JsonSerializerOptions() 33 | { 34 | PropertyNameCaseInsensitive = true, 35 | }); 36 | 37 | Assert.Equal(userName, result.UserName); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket.Tests/DataCenter/LoginOptionsTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Atlassian.Bitbucket.DataCenter; 5 | using Xunit; 6 | 7 | namespace Atlassian.Bitbucket.Tests.DataCenter 8 | { 9 | public class LoginOptionsTest 10 | { 11 | 12 | [Fact] 13 | public void LoginOptions_Set() 14 | { 15 | var loginOption = new LoginOption() 16 | { 17 | Type = "abc", 18 | Id = 1 19 | }; 20 | 21 | var results = new List() 22 | { 23 | loginOption 24 | }; 25 | 26 | var loginOptions = new LoginOptions() 27 | { 28 | Results = results 29 | }; 30 | 31 | Assert.NotNull(loginOptions.Results); 32 | Assert.Contains(loginOption, loginOptions.Results); 33 | 34 | Assert.Equal("abc", loginOptions.Results.First().Type); 35 | Assert.Equal(1, loginOptions.Results.First().Id); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket.Tests/DataCenter/UserInfoTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Atlassian.Bitbucket.DataCenter; 3 | using Xunit; 4 | 5 | namespace Atlassian.Bitbucket.Tests.DataCenter 6 | { 7 | public class UserInfoTest 8 | { 9 | [Fact] 10 | public void UserInfo_Set() 11 | { 12 | var userInfo = new UserInfo() 13 | { 14 | UserName = "123" 15 | }; 16 | 17 | Assert.Equal("123", userInfo.UserName); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | net7.0;net472 6 | Atlassian.Bitbucket 7 | Atlassian.Bitbucket 8 | false 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/AuthenticationMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Atlassian.Bitbucket 3 | { 4 | public enum AuthenticationMethod 5 | { 6 | BasicAuth, 7 | Sso 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/BitbucketConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlassian.Bitbucket 4 | { 5 | public static class BitbucketConstants 6 | { 7 | public const string Id = "bitbucket"; 8 | 9 | public const string Name = "Bitbucket"; 10 | 11 | public const string DefaultAuthenticationHelper = "Atlassian.Bitbucket.UI"; 12 | 13 | public static class EnvironmentVariables 14 | { 15 | public const string AuthenticationHelper = "GCM_BITBUCKET_HELPER"; 16 | public const string AuthenticationModes = "GCM_BITBUCKET_AUTHMODES"; 17 | public const string AlwaysRefreshCredentials = "GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS"; 18 | public const String ValidateStoredCredentials = "GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS"; 19 | } 20 | 21 | public static class GitConfiguration 22 | { 23 | public static class Credential 24 | { 25 | public const string AuthenticationHelper = "bitbucketHelper"; 26 | public const string AuthenticationModes = "bitbucketAuthModes"; 27 | public const string AlwaysRefreshCredentials = "bitbucketAlwaysRefreshCredentials"; 28 | public const string ValidateStoredCredentials = "bitbucketValidateStoredCredentials"; 29 | } 30 | } 31 | public static class HelpUrls 32 | { 33 | public const string DataCenterPasswordReset = "/passwordreset"; 34 | public const string DataCenterLogin = "/login"; 35 | public const string PasswordReset = "https://bitbucket.org/account/password/reset/"; 36 | public const string SignUp = "https://bitbucket.org/account/signup/"; 37 | public const string TwoFactor = "https://support.atlassian.com/bitbucket-cloud/docs/enable-two-step-verification/"; 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/BitbucketHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Atlassian.Bitbucket.Cloud; 3 | using GitCredentialManager; 4 | 5 | namespace Atlassian.Bitbucket 6 | { 7 | public static class BitbucketHelper 8 | { 9 | public static string GetBaseUri(Uri remoteUri) 10 | { 11 | var pathParts = remoteUri.PathAndQuery.Split('/'); 12 | var pathPart = remoteUri.PathAndQuery.StartsWith("/") ? pathParts[1] : pathParts[0]; 13 | var path = !string.IsNullOrWhiteSpace(pathPart) ? "/" + pathPart : null; 14 | return $"{remoteUri.Scheme}://{remoteUri.Host}:{remoteUri.Port}{path}"; 15 | } 16 | 17 | public static bool IsBitbucketOrg(InputArguments input) 18 | { 19 | return IsBitbucketOrg(input.GetRemoteUri()); 20 | } 21 | 22 | public static bool IsBitbucketOrg(Uri targetUri) 23 | { 24 | return IsBitbucketOrg(targetUri.Host); 25 | } 26 | 27 | public static bool IsBitbucketOrg(string targetHost) 28 | { 29 | return StringComparer.OrdinalIgnoreCase.Equals(targetHost, CloudConstants.BitbucketBaseUrlHost); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/BitbucketRestApiRegistry.cs: -------------------------------------------------------------------------------- 1 | using Atlassian.Bitbucket.Cloud; 2 | using GitCredentialManager; 3 | 4 | namespace Atlassian.Bitbucket 5 | { 6 | public class BitbucketRestApiRegistry : IRegistry 7 | { 8 | private readonly ICommandContext context; 9 | private BitbucketRestApi cloudApi; 10 | private DataCenter.BitbucketRestApi dataCenterApi; 11 | 12 | public BitbucketRestApiRegistry(ICommandContext context) 13 | { 14 | this.context = context; 15 | } 16 | 17 | public IBitbucketRestApi Get(InputArguments input) 18 | { 19 | if(!BitbucketHelper.IsBitbucketOrg(input)) 20 | { 21 | return DataCenterApi; 22 | } 23 | 24 | return CloudApi; 25 | } 26 | 27 | public void Dispose() 28 | { 29 | context.Dispose(); 30 | cloudApi?.Dispose(); 31 | dataCenterApi?.Dispose(); 32 | } 33 | 34 | private Cloud.BitbucketRestApi CloudApi => cloudApi ??= new Cloud.BitbucketRestApi(context); 35 | private DataCenter.BitbucketRestApi DataCenterApi => dataCenterApi ??= new DataCenter.BitbucketRestApi(context); 36 | } 37 | } -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/Cloud/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Atlassian.Bitbucket.Cloud 6 | { 7 | public class UserInfo : IUserInfo 8 | { 9 | [JsonPropertyName("username")] 10 | public string UserName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/DataCenter/LoginOption.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Atlassian.Bitbucket.DataCenter 4 | { 5 | public class LoginOption 6 | { 7 | [JsonPropertyName("type")] 8 | public string Type { get ; set; } 9 | 10 | [JsonPropertyName("id")] 11 | public int Id { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/DataCenter/LoginOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Atlassian.Bitbucket.DataCenter 5 | { 6 | public class LoginOptions 7 | { 8 | [JsonPropertyName("results")] 9 | public List Results { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/DataCenter/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlassian.Bitbucket.DataCenter 4 | { 5 | public class UserInfo : IUserInfo 6 | { 7 | public string UserName { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/IBitbucketRestApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Atlassian.Bitbucket 6 | { 7 | public interface IBitbucketRestApi : IDisposable 8 | { 9 | Task> GetUserInformationAsync(string userName, string password, bool isBearerToken); 10 | Task IsOAuthInstalledAsync(); 11 | Task> GetAuthenticationMethodsAsync(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/IRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GitCredentialManager; 3 | 4 | namespace Atlassian.Bitbucket 5 | { 6 | public interface IRegistry : IDisposable 7 | { 8 | T Get(InputArguments input); 9 | } 10 | } -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/IUserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Atlassian.Bitbucket 3 | { 4 | public interface IUserInfo 5 | { 6 | string UserName{ get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Atlassian.Bitbucket.Tests")] 4 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/OAuth2ClientRegistry.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using GitCredentialManager; 3 | 4 | namespace Atlassian.Bitbucket 5 | { 6 | public class OAuth2ClientRegistry : DisposableObject, IRegistry 7 | { 8 | private readonly ICommandContext _context; 9 | 10 | private HttpClient _httpClient; 11 | private Cloud.BitbucketOAuth2Client _cloudClient; 12 | private DataCenter.BitbucketOAuth2Client _dataCenterClient; 13 | 14 | public OAuth2ClientRegistry(ICommandContext context) 15 | { 16 | EnsureArgument.NotNull(context, nameof(context)); 17 | _context = context; 18 | } 19 | 20 | public BitbucketOAuth2Client Get(InputArguments input) 21 | { 22 | if (!BitbucketHelper.IsBitbucketOrg(input)) 23 | { 24 | return DataCenterClient; 25 | } 26 | 27 | return CloudClient; 28 | } 29 | 30 | protected override void ReleaseManagedResources() 31 | { 32 | _httpClient?.Dispose(); 33 | _cloudClient = null; 34 | _dataCenterClient = null; 35 | base.ReleaseManagedResources(); 36 | } 37 | 38 | private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); 39 | 40 | private Cloud.BitbucketOAuth2Client CloudClient => 41 | _cloudClient ??= new Cloud.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); 42 | 43 | private DataCenter.BitbucketOAuth2Client DataCenterClient => 44 | _dataCenterClient ??= new DataCenter.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/RestApiResult.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Atlassian.Bitbucket 4 | { 5 | public class RestApiResult 6 | { 7 | public RestApiResult(HttpStatusCode statusCode) 8 | : this(statusCode, default(T)) { } 9 | 10 | public RestApiResult(HttpStatusCode statusCode, T response) 11 | { 12 | StatusCode = statusCode; 13 | Response = response; 14 | } 15 | 16 | public HttpStatusCode StatusCode { get; } 17 | 18 | public T Response { get; } 19 | 20 | public bool Succeeded => 199 < (int)StatusCode && (int)StatusCode < 300; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/UI/Assets/atlassian-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/src/shared/Atlassian.Bitbucket/UI/Assets/atlassian-logo.png -------------------------------------------------------------------------------- /src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Atlassian.Bitbucket.UI.ViewModels; 2 | using Avalonia.Controls; 3 | using Avalonia.Markup.Xaml; 4 | using GitCredentialManager; 5 | using GitCredentialManager.UI.Controls; 6 | 7 | namespace Atlassian.Bitbucket.UI.Views 8 | { 9 | public partial class CredentialsView : UserControl, IFocusable 10 | { 11 | public CredentialsView() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | public void SetFocus() 17 | { 18 | if (!(DataContext is CredentialsViewModel vm)) 19 | { 20 | return; 21 | } 22 | 23 | if (vm.ShowOAuth) 24 | { 25 | _authModesTabControl.SelectedIndex = 0; 26 | _oauthLoginButton.Focus(); 27 | } 28 | else if (vm.ShowBasic) 29 | { 30 | _authModesTabControl.SelectedIndex = 1; 31 | if (string.IsNullOrWhiteSpace(vm.UserName)) 32 | { 33 | // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293 34 | if (!PlatformUtils.IsMacOS()) 35 | _userNameTextBox.Focus(); 36 | } 37 | else 38 | { 39 | // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293 40 | if (!PlatformUtils.IsMacOS()) 41 | _passwordTextBox.Focus(); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Authentication/AuthenticationBaseTests.cs: -------------------------------------------------------------------------------- 1 | using GitCredentialManager.Authentication; 2 | using Xunit; 3 | 4 | namespace GitCredentialManager.Tests.Authentication 5 | { 6 | public class AuthenticationBaseTests 7 | { 8 | [Theory] 9 | [InlineData("foo", "foo")] 10 | [InlineData("foo bar", "\"foo bar\"")] 11 | [InlineData("foo\nbar", "\"foo\nbar\"")] 12 | [InlineData("foo\rbar", "\"foo\rbar\"")] 13 | [InlineData("foo\tbar", "\"foo\tbar\"")] 14 | [InlineData("foo\" bar", "\"foo\\\" bar\"")] 15 | [InlineData("foo\"", "\"foo\\\"\"")] 16 | [InlineData("\"foo", "\"\\\"foo\"")] 17 | [InlineData("foo\\", "\"foo\\\\\"")] 18 | [InlineData("foo\\\"", "\"foo\\\\\\\"\"")] 19 | public void AuthenticationBase_QuoteCmdArg(string input, string expected) 20 | { 21 | string actual = AuthenticationBase.QuoteCmdArg(input); 22 | Assert.Equal(expected, actual); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Base64UrlConvertTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace GitCredentialManager.Tests 4 | { 5 | public class Base64UrlConvertTests 6 | { 7 | [Theory] 8 | [InlineData(new byte[0], "")] 9 | [InlineData(new byte[]{4}, "BA==")] 10 | [InlineData(new byte[]{4,5}, "BAU=")] 11 | [InlineData(new byte[]{4,5,6}, "BAUG")] 12 | [InlineData(new byte[]{4,5,6,7}, "BAUGBw==")] 13 | [InlineData(new byte[]{4,5,6,7,8}, "BAUGBwg=")] 14 | [InlineData(new byte[]{4,5,6,7,8,9}, "BAUGBwgJ")] 15 | public void Base64UrlConvert_Encode_WithPadding(byte[] data, string expected) 16 | { 17 | string actual = Base64UrlConvert.Encode(data, includePadding: true); 18 | Assert.Equal(expected, actual); 19 | } 20 | 21 | [Theory] 22 | [InlineData(new byte[0], "")] 23 | [InlineData(new byte[]{4}, "BA")] 24 | [InlineData(new byte[]{4,5}, "BAU")] 25 | [InlineData(new byte[]{4,5,6}, "BAUG")] 26 | [InlineData(new byte[]{4,5,6,7}, "BAUGBw")] 27 | [InlineData(new byte[]{4,5,6,7,8}, "BAUGBwg")] 28 | [InlineData(new byte[]{4,5,6,7,8,9}, "BAUGBwgJ")] 29 | public void Base64UrlConvert_Encode_WithoutPadding(byte[] data, string expected) 30 | { 31 | string actual = Base64UrlConvert.Encode(data, includePadding: false); 32 | Assert.Equal(expected, actual); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Commands/ConfigureCommandTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using GitCredentialManager.Commands; 3 | using GitCredentialManager.Tests.Objects; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace GitCredentialManager.Tests.Commands 8 | { 9 | public class ConfigureCommandTests 10 | { 11 | [Fact] 12 | public async Task ConfigureCommand_ExecuteAsync_User_InvokesConfigurationServiceConfigureUser() 13 | { 14 | var configService = new Mock(); 15 | configService.Setup(x => x.ConfigureAsync(It.IsAny())) 16 | .Returns(Task.CompletedTask) 17 | .Verifiable(); 18 | 19 | var context = new TestCommandContext(); 20 | var command = new ConfigureCommand(context, configService.Object); 21 | 22 | await command.ExecuteAsync(false); 23 | 24 | configService.Verify(x => x.ConfigureAsync(ConfigurationTarget.User), Times.Once); 25 | } 26 | 27 | [Fact] 28 | public async Task ConfigureCommand_ExecuteAsync_System_InvokesConfigurationServiceConfigureSystem() 29 | { 30 | var configService = new Mock(); 31 | configService.Setup(x => x.ConfigureAsync(It.IsAny())) 32 | .Returns(Task.CompletedTask) 33 | .Verifiable(); 34 | 35 | var context = new TestCommandContext(); 36 | var command = new ConfigureCommand(context, configService.Object); 37 | 38 | await command.ExecuteAsync(true); 39 | 40 | configService.Verify(x => x.ConfigureAsync(ConfigurationTarget.System), Times.Once); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Commands/UnconfigureCommandTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using GitCredentialManager.Commands; 3 | using GitCredentialManager.Tests.Objects; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace GitCredentialManager.Tests.Commands 8 | { 9 | public class UnconfigureCommandTests 10 | { 11 | [Fact] 12 | public async Task UnconfigureCommand_ExecuteAsync_User_InvokesConfigurationServiceUnconfigureUser() 13 | { 14 | var configService = new Mock(); 15 | configService.Setup(x => x.UnconfigureAsync(It.IsAny())) 16 | .Returns(Task.CompletedTask) 17 | .Verifiable(); 18 | 19 | var context = new TestCommandContext(); 20 | var command = new UnconfigureCommand(context, configService.Object); 21 | 22 | await command.ExecuteAsync(false); 23 | 24 | configService.Verify(x => x.UnconfigureAsync(ConfigurationTarget.User), Times.Once); 25 | } 26 | 27 | [Fact] 28 | public async Task UnconfigureCommand_ExecuteAsync_System_InvokesConfigurationServiceUnconfigureSystem() 29 | { 30 | var configService = new Mock(); 31 | configService.Setup(x => x.UnconfigureAsync(It.IsAny())) 32 | .Returns(Task.CompletedTask) 33 | .Verifiable(); 34 | 35 | var context = new TestCommandContext(); 36 | var command = new UnconfigureCommand(context, configService.Object); 37 | 38 | await command.ExecuteAsync(true); 39 | 40 | configService.Verify(x => x.UnconfigureAsync(ConfigurationTarget.System), Times.Once); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Core.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | false 6 | true 7 | latest 8 | true 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/EnsureArgumentTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace GitCredentialManager.Tests 5 | { 6 | public class EnsureArgumentTests 7 | { 8 | [Theory] 9 | [InlineData(5, 0, 10, true, true)] 10 | [InlineData(0, 0, 10, true, true)] 11 | [InlineData(10, 0, 10, true, true)] 12 | [InlineData(0, -10, 0, true, true)] 13 | [InlineData(-10, -10, 0, true, true)] 14 | public void EnsureArgument_InRange_DoesNotThrow(int x, int lower, int upper, bool lowerInc, bool upperInc) 15 | { 16 | EnsureArgument.InRange(x, nameof(x), lower, upper, lowerInc, upperInc); 17 | } 18 | 19 | [Theory] 20 | [InlineData(-3, 0, 10, true, true)] 21 | [InlineData(13, 0, 10, true, true)] 22 | [InlineData(10, 0, 10, true, false)] 23 | [InlineData(0, 0, 10, false, true)] 24 | [InlineData(-10, -10, 0, false, true)] 25 | [InlineData(0, -10, 0, true, false)] 26 | public void EnsureArgument_InRange_ThrowsException(int x, int lower, int upper, bool lowerInc, bool upperInc) 27 | { 28 | Assert.Throws( 29 | () => EnsureArgument.InRange(x, nameof(x), lower, upper, lowerInc, upperInc) 30 | ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/GitVersionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace GitCredentialManager.Tests 5 | { 6 | public class GitVersionTests 7 | { 8 | [Theory] 9 | [InlineData(null, 1)] 10 | [InlineData("2", 1)] 11 | [InlineData("3", -1)] 12 | [InlineData("2.33", 0)] 13 | [InlineData("2.32.0", 1)] 14 | [InlineData("2.33.0.windows.0.1", 0)] 15 | [InlineData("2.33.0.2", -1)] 16 | public void GitVersion_CompareTo_2_33_0(string input, int expectedCompare) 17 | { 18 | GitVersion baseline = new GitVersion(2, 33, 0); 19 | GitVersion actual = new GitVersion(input); 20 | Assert.Equal(expectedCompare, baseline.CompareTo(actual)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Interop/Posix/PosixFileSystemTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using GitCredentialManager.Interop.Posix; 3 | using Xunit; 4 | using static GitCredentialManager.Tests.TestUtils; 5 | 6 | namespace GitCredentialManager.Tests.Interop.Posix 7 | { 8 | public class PosixFileSystemTests 9 | { 10 | [PlatformFact(Platforms.Posix)] 11 | public void PosixFileSystem_ResolveSymlinks_FileLinks() 12 | { 13 | string baseDir = GetTempDirectory(); 14 | string realPath = CreateFile(baseDir, "realFile.txt"); 15 | string linkPath = CreateFileSymlink(baseDir, "linkFile.txt", realPath); 16 | 17 | string actual = PosixFileSystem.ResolveSymbolicLinks(linkPath); 18 | 19 | Assert.Equal(realPath, actual); 20 | } 21 | 22 | [PlatformFact(Platforms.Posix)] 23 | public void PosixFileSystem_ResolveSymlinks_DirectoryLinks() 24 | { 25 | // 26 | // Create a real file inside of a directory that is a symlink 27 | // to another directory. 28 | // 29 | // /tmp/{uuid}/linkDir/ -> /tmp/{uuid}/realDir/ 30 | // 31 | string baseDir = GetTempDirectory(); 32 | string realDir = CreateDirectory(baseDir, "realDir"); 33 | string linkDir = CreateDirectorySymlink(baseDir, "linkDir", realDir); 34 | string filePath = CreateFile(linkDir, "file.txt"); 35 | 36 | string actual = PosixFileSystem.ResolveSymbolicLinks(filePath); 37 | 38 | string expected = Path.Combine(realDir, "file.txt"); 39 | 40 | Assert.Equal(expected, actual); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Interop/Windows/WindowsSystemPromptsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GitCredentialManager.Interop.Windows; 3 | using GitCredentialManager.Tests.Objects; 4 | using Xunit; 5 | 6 | namespace GitCredentialManager.Tests.Interop.Windows 7 | { 8 | public class WindowsSystemPromptsTests 9 | { 10 | [Fact] 11 | public void WindowsSystemPrompts_ShowCredentialPrompt_NullResource_ThrowsException() 12 | { 13 | var sysPrompts = new WindowsSystemPrompts(); 14 | Assert.Throws(() => sysPrompts.ShowCredentialPrompt(null, null, out _)); 15 | } 16 | 17 | [Fact] 18 | public void WindowsSystemPrompts_ShowCredentialPrompt_EmptyResource_ThrowsException() 19 | { 20 | var sysPrompts = new WindowsSystemPrompts(); 21 | Assert.Throws(() => sysPrompts.ShowCredentialPrompt(string.Empty, null, out _)); 22 | } 23 | 24 | [Fact] 25 | public void WindowsSystemPrompts_ShowCredentialPrompt_WhiteSpaceResource_ThrowsException() 26 | { 27 | var sysPrompts = new WindowsSystemPrompts(); 28 | Assert.Throws(() => sysPrompts.ShowCredentialPrompt(" ", null, out _)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/ProcessManagerTests.cs: -------------------------------------------------------------------------------- 1 | using GitCredentialManager; 2 | using Xunit; 3 | 4 | namespace Core.Tests; 5 | 6 | public class ProcessManagerTests 7 | { 8 | [Theory] 9 | [InlineData("", 0)] 10 | [InlineData("foo", 0)] 11 | [InlineData("foo/bar", 1)] 12 | [InlineData("foo/bar/baz", 2)] 13 | public void CreateSid_Envar_Returns_Expected_Sid(string input, int expected) 14 | { 15 | ProcessManager.Sid = input; 16 | var actual = ProcessManager.GetProcessDepth(); 17 | 18 | Assert.Equal(expected, actual); 19 | } 20 | 21 | [Theory] 22 | [InlineData("", 0)] 23 | [InlineData("foo", 0)] 24 | [InlineData("foo/bar", 1)] 25 | [InlineData("foo/bar/baz", 2)] 26 | public void TryGetProcessDepth_Returns_Expected_Depth(string input, int expected) 27 | { 28 | ProcessManager.Sid = input; 29 | var actual = ProcessManager.GetProcessDepth(); 30 | 31 | Assert.Equal(expected, actual); 32 | } 33 | } -------------------------------------------------------------------------------- /src/shared/Core.Tests/TestProcessManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using GitCredentialManager.Tests.Objects; 5 | using Moq; 6 | 7 | namespace GitCredentialManager.Tests; 8 | 9 | public class TestProcessManager : IProcessManager 10 | { 11 | public ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory) 12 | { 13 | var psi = new ProcessStartInfo(path, args) 14 | { 15 | RedirectStandardInput = true, 16 | RedirectStandardOutput = true, 17 | RedirectStandardError = true, // Ok to redirect stderr for testing 18 | UseShellExecute = useShellExecute, 19 | WorkingDirectory = workingDirectory ?? string.Empty 20 | }; 21 | 22 | return CreateProcess(psi); 23 | } 24 | 25 | public ChildProcess CreateProcess(ProcessStartInfo psi) 26 | { 27 | return new ChildProcess(new NullTrace2(), psi); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/TokenEndpointResponseJsonTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using GitCredentialManager.Authentication.OAuth.Json; 4 | using Xunit; 5 | 6 | namespace Core.Tests; 7 | 8 | public class TokenEndpointResponseJsonTest 9 | { 10 | [Fact] 11 | public void TokenEndpointResponseJson_Deserialize_Uses_Scope() 12 | { 13 | var accessToken = "123"; 14 | var tokenType = "Bearer"; 15 | var expiresIn = 1000; 16 | var scopesString = "x,y,z"; 17 | var scopeString = "a,b,c"; 18 | var json = $"{{\"access_token\": \"{accessToken}\", \"token_type\": \"{tokenType}\", \"expires_in\": {expiresIn}, \"scopes\": \"{scopesString}\", \"scope\": \"{scopeString}\"}}"; 19 | 20 | var result = JsonSerializer.Deserialize(json, 21 | new JsonSerializerOptions 22 | { 23 | PropertyNameCaseInsensitive = true 24 | }); 25 | 26 | Assert.Equal(accessToken, result.AccessToken); 27 | Assert.Equal(tokenType, result.TokenType); 28 | Assert.Equal(expiresIn, result.ExpiresIn); 29 | Assert.Equal(scopeString, result.Scope); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/Trace2Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace GitCredentialManager.Tests; 4 | 5 | public class Trace2Tests 6 | { 7 | [PlatformTheory(Platforms.Posix)] 8 | [InlineData("af_unix:foo", "foo")] 9 | [InlineData("af_unix:stream:foo-bar", "foo-bar")] 10 | [InlineData("af_unix:dgram:foo-bar-baz", "foo-bar-baz")] 11 | public void TryGetPipeName_Posix_Returns_Expected_Value(string input, string expected) 12 | { 13 | var isSuccessful = Trace2.TryGetPipeName(input, out var actual); 14 | 15 | Assert.True(isSuccessful); 16 | Assert.Matches(actual, expected); 17 | } 18 | 19 | [PlatformTheory(Platforms.Windows)] 20 | [InlineData("\\\\.\\pipe\\git-foo", "git-foo")] 21 | [InlineData("\\\\.\\pipe\\git-foo-bar", "git-foo-bar")] 22 | [InlineData("\\\\.\\pipe\\foo\\git-bar", "git-bar")] 23 | public void TryGetPipeName_Windows_Returns_Expected_Value(string input, string expected) 24 | { 25 | var isSuccessful = Trace2.TryGetPipeName(input, out var actual); 26 | 27 | Assert.True(isSuccessful); 28 | Assert.Matches(actual, expected); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/shared/Core.Tests/TraceUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Xunit; 5 | 6 | namespace GitCredentialManager.Tests; 7 | 8 | public class TraceUtilsTests 9 | { 10 | [Theory] 11 | [InlineData("/foo/bar/baz/boo", 10, "...baz/boo")] 12 | [InlineData("thisfileshouldbetruncated", 12, "...truncated")] 13 | public void FormatSource_ReturnsExpectedSourceValues(string path, int sourceColumnMaxWidth, string expectedSource) 14 | { 15 | string actualSource = TraceUtils.FormatSource(path, sourceColumnMaxWidth); 16 | Assert.Equal(actualSource, expectedSource); 17 | } 18 | } -------------------------------------------------------------------------------- /src/shared/Core/AssemblyUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace GitCredentialManager; 4 | 5 | public static class AssemblyUtils 6 | { 7 | public static bool TryGetAssemblyVersion(out string version) 8 | { 9 | try 10 | { 11 | var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); 12 | var assemblyVersionAttribute = assembly.GetCustomAttribute(); 13 | version = assemblyVersionAttribute is null 14 | ? assembly.GetName().Version.ToString() 15 | : assemblyVersionAttribute.InformationalVersion; 16 | return true; 17 | } 18 | catch 19 | { 20 | version = null; 21 | return false; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/HttpListenerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | 5 | namespace GitCredentialManager.Authentication.OAuth 6 | { 7 | public static class HttpListenerExtensions 8 | { 9 | public static async Task WriteResponseAsync(this HttpListenerResponse response, string responseText) 10 | { 11 | byte[] responseData = Encoding.UTF8.GetBytes(responseText); 12 | response.ContentLength64 = responseData.Length; 13 | await response.OutputStream.WriteAsync(responseData, 0, responseData.Length); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/IOAuth2WebBrowser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace GitCredentialManager.Authentication.OAuth 7 | { 8 | public interface IOAuth2WebBrowser 9 | { 10 | Uri UpdateRedirectUri(Uri uri); 11 | 12 | Task GetAuthenticationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken ct); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/Json/DeviceAuthorizationEndpointResponseJson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace GitCredentialManager.Authentication.OAuth.Json 5 | { 6 | public class DeviceAuthorizationEndpointResponseJson 7 | { 8 | [JsonRequired] 9 | [JsonPropertyName("device_code")] 10 | public string DeviceCode { get; set; } 11 | 12 | [JsonRequired] 13 | [JsonPropertyName("user_code")] 14 | public string UserCode { get; set; } 15 | 16 | [JsonRequired] 17 | [JsonPropertyName("verification_uri")] 18 | public Uri VerificationUri { get; set; } 19 | 20 | [JsonPropertyName("expires_in")] 21 | public long ExpiresIn { get; set; } 22 | 23 | [JsonPropertyName("interval")] 24 | public long PollingInterval { get; set; } 25 | 26 | public OAuth2DeviceCodeResult ToResult() 27 | { 28 | return new OAuth2DeviceCodeResult(DeviceCode, UserCode, VerificationUri, TimeSpan.FromSeconds(PollingInterval)) 29 | { 30 | ExpiresIn = TimeSpan.FromSeconds(ExpiresIn) 31 | }; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/Json/ErrorResponseJson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace GitCredentialManager.Authentication.OAuth.Json 7 | { 8 | public class ErrorResponseJson 9 | { 10 | [JsonRequired] 11 | [JsonPropertyName("error")] 12 | public string Error { get; set; } 13 | 14 | [JsonPropertyName("error_description")] 15 | public string Description { get; set; } 16 | 17 | [JsonPropertyName("error_uri")] 18 | public Uri Uri { get; set; } 19 | 20 | public OAuth2Exception ToException(Exception innerException = null) 21 | { 22 | var message = new StringBuilder(Error); 23 | 24 | if (!string.IsNullOrEmpty(Description)) 25 | { 26 | message.AppendFormat(": {0}", Description); 27 | } 28 | 29 | if (Uri != null) 30 | { 31 | message.AppendFormat(" [{0}]", Uri); 32 | } 33 | 34 | return new OAuth2Exception(message.ToString(), innerException) {HelpLink = Uri?.ToString()}; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/Json/TokenEndpointResponseJson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace GitCredentialManager.Authentication.OAuth.Json 6 | { 7 | public class TokenEndpointResponseJson 8 | { 9 | [JsonRequired] 10 | [JsonPropertyName("access_token")] 11 | public string AccessToken { get; set; } 12 | 13 | [JsonRequired] 14 | [JsonPropertyName("token_type")] 15 | public string TokenType { get; set; } 16 | 17 | [JsonPropertyName("expires_in")] 18 | public long? ExpiresIn { get; set; } 19 | 20 | [JsonPropertyName("refresh_token")] 21 | public string RefreshToken { get; set; } 22 | 23 | [JsonPropertyName("scope")] 24 | public virtual string Scope { get; set; } 25 | 26 | public OAuth2TokenResult ToResult() 27 | { 28 | return new OAuth2TokenResult(AccessToken, TokenType) 29 | { 30 | ExpiresIn = ExpiresIn.HasValue ? TimeSpan.FromSeconds(ExpiresIn.Value) : null, 31 | RefreshToken = RefreshToken, 32 | Scopes = Scope?.Split(' ') 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/OAuth2AuthorizationCodeResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager.Authentication.OAuth 4 | { 5 | public class OAuth2AuthorizationCodeResult 6 | { 7 | public OAuth2AuthorizationCodeResult(string code, Uri redirectUri = null, string codeVerifier = null) 8 | { 9 | Code = code; 10 | RedirectUri = redirectUri; 11 | CodeVerifier = codeVerifier; 12 | } 13 | 14 | public string Code { get; } 15 | public Uri RedirectUri { get; } 16 | public string CodeVerifier { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/OAuth2DeviceCodeResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager.Authentication.OAuth 4 | { 5 | public class OAuth2DeviceCodeResult 6 | { 7 | public OAuth2DeviceCodeResult(string deviceCode, string userCode, Uri verificationUri, TimeSpan? interval) 8 | { 9 | DeviceCode = deviceCode; 10 | UserCode = userCode; 11 | VerificationUri = verificationUri; 12 | PollingInterval = interval ?? TimeSpan.FromSeconds(5); 13 | } 14 | 15 | public string DeviceCode { get; } 16 | 17 | public string UserCode { get; } 18 | 19 | public Uri VerificationUri { get; } 20 | 21 | public TimeSpan PollingInterval { get; } 22 | 23 | public TimeSpan? ExpiresIn { get; internal set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/OAuth2Exception.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager.Authentication.OAuth 4 | { 5 | public class OAuth2Exception : Exception 6 | { 7 | public OAuth2Exception(string message) : base(message) { } 8 | 9 | public OAuth2Exception(string message, Exception innerException) : base(message, innerException) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/OAuth2ServerEndpoints.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager.Authentication.OAuth 4 | { 5 | /// 6 | /// Represents the various OAuth2 endpoints for an . 7 | /// 8 | public class OAuth2ServerEndpoints 9 | { 10 | private Uri _deviceAuthorizationEndpoint; 11 | 12 | public OAuth2ServerEndpoints(Uri authorizationEndpoint, Uri tokenEndpoint) 13 | { 14 | EnsureArgument.AbsoluteUri(authorizationEndpoint, nameof(authorizationEndpoint)); 15 | EnsureArgument.AbsoluteUri(tokenEndpoint, nameof(tokenEndpoint)); 16 | 17 | AuthorizationEndpoint = authorizationEndpoint; 18 | TokenEndpoint = tokenEndpoint; 19 | } 20 | 21 | public Uri AuthorizationEndpoint { get; } 22 | 23 | public Uri TokenEndpoint { get; } 24 | 25 | public Uri DeviceAuthorizationEndpoint 26 | { 27 | get => _deviceAuthorizationEndpoint; 28 | set 29 | { 30 | if (value != null) 31 | { 32 | EnsureArgument.AbsoluteUri(value, nameof(value)); 33 | } 34 | 35 | _deviceAuthorizationEndpoint = value; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/shared/Core/Authentication/OAuth/OAuth2TokenResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager.Authentication.OAuth 4 | { 5 | public class OAuth2TokenResult 6 | { 7 | public OAuth2TokenResult(string accessToken, string tokenType) 8 | { 9 | AccessToken = accessToken; 10 | TokenType = tokenType; 11 | } 12 | 13 | public string AccessToken { get; } 14 | 15 | public string TokenType { get; } 16 | 17 | public string RefreshToken { get; set; } 18 | 19 | public TimeSpan? ExpiresIn { get; set; } 20 | 21 | public string[] Scopes { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/Core/Base64UrlConvert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager 4 | { 5 | public static class Base64UrlConvert 6 | { 7 | public static string Encode(byte[] data, bool includePadding = true) 8 | { 9 | const char base64PadCharacter = '='; 10 | const char base64Character62 = '+'; 11 | const char base64Character63 = '/'; 12 | const char base64UrlCharacter62 = '-'; 13 | const char base64UrlCharacter63 = '_'; 14 | 15 | // The base64url format is the same as regular base64 format except: 16 | // 1. character 62 is "-" (minus) not "+" (plus) 17 | // 2. character 63 is "_" (underscore) not "/" (slash) 18 | string base64Url = Convert.ToBase64String(data) 19 | .Replace(base64Character62, base64UrlCharacter62) 20 | .Replace(base64Character63, base64UrlCharacter63); 21 | 22 | return includePadding ? base64Url : base64Url.TrimEnd(base64PadCharacter); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/shared/Core/Commands/EraseCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace GitCredentialManager.Commands 4 | { 5 | /// 6 | /// Erase a previously stored from the OS secure credential store. 7 | /// 8 | public class EraseCommand : GitCommandBase 9 | { 10 | public EraseCommand(ICommandContext context, IHostProviderRegistry hostProviderRegistry) 11 | : base(context, "erase", "[Git] Erase a stored credential", hostProviderRegistry) { } 12 | 13 | protected override Task ExecuteInternalAsync(InputArguments input, IHostProvider provider) 14 | { 15 | return provider.EraseCredentialAsync(input); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/shared/Core/Commands/GetCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace GitCredentialManager.Commands 6 | { 7 | /// 8 | /// Acquire a new from a . 9 | /// 10 | public class GetCommand : GitCommandBase 11 | { 12 | public GetCommand(ICommandContext context, IHostProviderRegistry hostProviderRegistry) 13 | : base(context, "get", "[Git] Return a stored credential", hostProviderRegistry) { } 14 | 15 | protected override async Task ExecuteInternalAsync(InputArguments input, IHostProvider provider) 16 | { 17 | ICredential credential = await provider.GetCredentialAsync(input); 18 | 19 | var output = new Dictionary(); 20 | 21 | // Echo protocol, host, and path back at Git 22 | if (input.Protocol != null) 23 | { 24 | output["protocol"] = input.Protocol; 25 | } 26 | if (input.Host != null) 27 | { 28 | output["host"] = input.Host; 29 | } 30 | if (input.Path != null) 31 | { 32 | output["path"] = input.Path; 33 | } 34 | 35 | // Return the credential to Git 36 | output["username"] = credential.Account; 37 | output["password"] = credential.Password; 38 | 39 | Context.Trace.WriteLine("Writing credentials to output:"); 40 | Context.Trace.WriteDictionarySecrets(output, new []{ "password" }, StringComparer.OrdinalIgnoreCase); 41 | 42 | // Write the values to standard out 43 | Context.Streams.Out.WriteDictionary(output); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/Core/Commands/ProviderCommand.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace GitCredentialManager.Commands 4 | { 5 | public interface ICommandProvider 6 | { 7 | /// 8 | /// Create a custom provider command. 9 | /// 10 | ProviderCommand CreateCommand(); 11 | } 12 | 13 | public class ProviderCommand : Command 14 | { 15 | public ProviderCommand(IHostProvider provider) 16 | : base(provider.Id, $"Commands for interacting with the {provider.Name} host provider") 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/shared/Core/Commands/StoreCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace GitCredentialManager.Commands 5 | { 6 | /// 7 | /// Store a previously created in the OS secure credential store. 8 | /// 9 | public class StoreCommand : GitCommandBase 10 | { 11 | public StoreCommand(ICommandContext context, IHostProviderRegistry hostProviderRegistry) 12 | : base(context, "store", "[Git] Store a credential", hostProviderRegistry) { } 13 | 14 | protected override Task ExecuteInternalAsync(InputArguments input, IHostProvider provider) 15 | { 16 | return provider.StoreCredentialAsync(input); 17 | } 18 | 19 | protected override void EnsureMinimumInputArguments(InputArguments input) 20 | { 21 | base.EnsureMinimumInputArguments(input); 22 | 23 | // An empty string username/password are valid inputs, so only check for `null` (not provided) 24 | if (input.UserName is null) 25 | { 26 | throw new Trace2InvalidOperationException(Context.Trace2, "Missing 'username' input argument"); 27 | } 28 | 29 | if (input.Password is null) 30 | { 31 | throw new Trace2InvalidOperationException(Context.Trace2, "Missing 'password' input argument"); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/Core/ConvertUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GitCredentialManager 4 | { 5 | public static class ConvertUtils 6 | { 7 | public static bool TryToInt32(object value, out int i) 8 | { 9 | return TryConvert(Convert.ToInt32, value, out i); 10 | } 11 | 12 | public static bool TryConvert(Func convert, object value, out T @out) 13 | { 14 | try 15 | { 16 | @out = convert(value); 17 | return true; 18 | } 19 | catch 20 | { 21 | @out = default(T); 22 | return false; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/Core/Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | net7.0;net472 6 | gcmcore 7 | GitCredentialManager 8 | false 9 | latest 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/shared/Core/Credential.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace GitCredentialManager 3 | { 4 | /// 5 | /// Represents a credential. 6 | /// 7 | public interface ICredential 8 | { 9 | /// 10 | /// Account associated with this credential. 11 | /// 12 | string Account { get; } 13 | 14 | /// 15 | /// Password. 16 | /// 17 | string Password { get; } 18 | } 19 | 20 | /// 21 | /// Represents a credential (username/password pair) that Git can use to authenticate to a remote repository. 22 | /// 23 | public class GitCredential : ICredential 24 | { 25 | public GitCredential(string userName, string password) 26 | { 27 | Account = userName; 28 | Password = password; 29 | } 30 | 31 | public string Account { get; } 32 | 33 | public string Password { get; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/Core/Diagnostics/EnvironmentDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Net.Mime; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace GitCredentialManager.Diagnostics 9 | { 10 | public class EnvironmentDiagnostic : Diagnostic 11 | { 12 | public EnvironmentDiagnostic(ICommandContext commandContext) 13 | : base("Environment", commandContext) 14 | { } 15 | 16 | protected override Task RunInternalAsync(StringBuilder log, IList additionalFiles) 17 | { 18 | PlatformInformation platformInfo = PlatformUtils.GetPlatformInformation(CommandContext.Trace2); 19 | log.AppendLine($"OSType: {platformInfo.OperatingSystemType}"); 20 | log.AppendLine($"OSVersion: {platformInfo.OperatingSystemVersion}"); 21 | 22 | log.Append("Reading environment variables..."); 23 | IDictionary envars = Environment.GetEnvironmentVariables(); 24 | log.AppendLine(" OK"); 25 | 26 | log.AppendLine(" Variables:"); 27 | foreach (DictionaryEntry envar in envars) 28 | { 29 | log.AppendFormat("{0}={1}", envar.Key, envar.Value); 30 | log.AppendLine(); 31 | } 32 | log.AppendLine(); 33 | 34 | return Task.FromResult(true); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/shared/Core/Diagnostics/GitDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace GitCredentialManager.Diagnostics 7 | { 8 | public class GitDiagnostic : Diagnostic 9 | { 10 | public GitDiagnostic(ICommandContext commandContext) 11 | : base("Git", commandContext) 12 | { } 13 | 14 | protected override Task RunInternalAsync(StringBuilder log, IList additionalFiles) 15 | { 16 | log.Append("Getting Git version..."); 17 | GitVersion gitVersion = CommandContext.Git.Version; 18 | log.AppendLine(" OK"); 19 | log.AppendLine($"Git version is '{gitVersion.OriginalString}'"); 20 | 21 | log.Append("Locating current repository..."); 22 | string thisRepo =CommandContext.Git.GetCurrentRepository(); 23 | log.AppendLine(" OK"); 24 | log.AppendLine(thisRepo is null ? "Not inside a Git repository." : $"Git repository at '{thisRepo}'"); 25 | 26 | log.Append("Listing all Git configuration..."); 27 | ChildProcess configProc = CommandContext.Git.CreateProcess("config --list --show-origin"); 28 | configProc.Start(Trace2ProcessClass.Git); 29 | // To avoid deadlocks, always read the output stream first and then wait 30 | // TODO: don't read in all the data at once; stream it 31 | string gitConfig = configProc.StandardOutput.ReadToEnd().TrimEnd(); 32 | configProc.WaitForExit(); 33 | log.AppendLine(" OK"); 34 | log.AppendLine("Git configuration:"); 35 | log.AppendLine(gitConfig); 36 | log.AppendLine(); 37 | 38 | return Task.FromResult(true); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/shared/Core/Diagnostics/IDiagnosticProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace GitCredentialManager.Diagnostics 4 | { 5 | public interface IDiagnosticProvider 6 | { 7 | IEnumerable GetDiagnostics(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/shared/Core/EncodingEx.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace GitCredentialManager; 4 | 5 | public static class EncodingEx 6 | { 7 | public static readonly Encoding UTF8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); 8 | } 9 | -------------------------------------------------------------------------------- /src/shared/Core/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace GitCredentialManager 6 | { 7 | public static class EnumerableExtensions 8 | { 9 | /// 10 | /// Concatenates multiple sequences. 11 | /// 12 | /// Initial sequence to concatenate other sequences with. 13 | /// Other sequences to concatenate together with . 14 | /// Type of the sequence elements. 15 | /// Concatenated sequence. 16 | public static IEnumerable ConcatMany(this IEnumerable first, params IEnumerable[] others) 17 | { 18 | IEnumerable result = first; 19 | 20 | foreach (IEnumerable other in others) 21 | { 22 | result = result.Concat(other); 23 | } 24 | 25 | return result; 26 | } 27 | 28 | public static bool TryGetFirst(this IEnumerable collection, Func predicate, out TSource result) 29 | { 30 | result = collection.FirstOrDefault(predicate); 31 | return !(result is null); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/Core/FileCredential.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager 2 | { 3 | public class FileCredential : ICredential 4 | { 5 | public FileCredential(string fullPath, string service, string account, string password) 6 | { 7 | FullPath = fullPath; 8 | Service = service; 9 | Account = account; 10 | Password = password; 11 | } 12 | 13 | public string FullPath { get; } 14 | 15 | public string Service { get; } 16 | 17 | public string Account { get; } 18 | 19 | public string Password { get; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/Core/GitConfigurationEntry.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager 2 | { 3 | public class GitConfigurationEntry 4 | { 5 | public GitConfigurationEntry(string key, string value) 6 | { 7 | Key = key; 8 | Value = value; 9 | } 10 | 11 | public string Key { get; } 12 | public string Value { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/Core/HttpContentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace GitCredentialManager 6 | { 7 | public static class HttpContentExtensions 8 | { 9 | public static async Task> ReadAsFormContentAsync(this HttpContent content) 10 | { 11 | string str = await content.ReadAsStringAsync(); 12 | return UriExtensions.ParseQueryString(str); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/shared/Core/HttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Text; 6 | 7 | namespace GitCredentialManager 8 | { 9 | public static class HttpRequestExtensions 10 | { 11 | /// 12 | /// Add a basic authentication header to the request, with the given username and password. 13 | /// 14 | /// 15 | /// The header value is formed by computing the base64 string from the UTF-8 string "{}:{}". 16 | /// 17 | public static void AddBasicAuthenticationHeader(this HttpRequestMessage request, string userName, string password) 18 | { 19 | string basicAuthValue = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", userName, password); 20 | byte[] authBytes = Encoding.UTF8.GetBytes(basicAuthValue); 21 | string base64String = Convert.ToBase64String(authBytes); 22 | request.Headers.Authorization = new AuthenticationHeaderValue(Constants.Http.WwwAuthenticateBasicScheme, base64String); 23 | } 24 | 25 | /// 26 | /// Add a bearer authentication header to the request, with the given bearer token. 27 | /// 28 | public static void AddBearerAuthenticationHeader(this HttpRequestMessage request, string bearerToken) 29 | { 30 | request.Headers.Authorization = new AuthenticationHeaderValue(Constants.Http.WwwAuthenticateBearerScheme, bearerToken); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/Core/ISessionManager.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager 2 | { 3 | public interface ISessionManager 4 | { 5 | /// 6 | /// Determine if the current session has access to a desktop/can display UI. 7 | /// 8 | /// True if the session can display UI, false otherwise. 9 | bool IsDesktopSession { get; } 10 | 11 | /// 12 | /// Determine if the current session has access to a web browser. 13 | /// 14 | /// True if the session can display a web browser, false otherwise. 15 | bool IsWebBrowserAvailable { get; } 16 | } 17 | 18 | public abstract class SessionManager : ISessionManager 19 | { 20 | protected IEnvironment Environment { get; } 21 | protected IFileSystem FileSystem { get; } 22 | 23 | protected SessionManager(IEnvironment env, IFileSystem fs) 24 | { 25 | EnsureArgument.NotNull(env, nameof(env)); 26 | EnsureArgument.NotNull(fs, nameof(fs)); 27 | 28 | Environment = env; 29 | FileSystem = fs; 30 | } 31 | 32 | public abstract bool IsDesktopSession { get; } 33 | 34 | public virtual bool IsWebBrowserAvailable => IsDesktopSession; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/shared/Core/ISystemPrompts.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace GitCredentialManager 3 | { 4 | /// 5 | /// Represents native system UI prompts. 6 | /// 7 | public interface ISystemPrompts 8 | { 9 | /// 10 | /// The parent window handle or ID. Used for correctly positioning and parenting system dialogs. 11 | /// 12 | /// This value is platform specific. 13 | object ParentWindowId { get; set; } 14 | 15 | /// 16 | /// Show a basic credential prompt using native system UI. 17 | /// 18 | /// The name or URL of the resource to collect credentials for. 19 | /// Optional pre-filled username. 20 | /// The captured basic credential. 21 | /// True if the user completes the dialog, false otherwise. 22 | bool ShowCredentialPrompt(string resource, string userName, out ICredential credential); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/Core/ITerminal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GitCredentialManager.Interop; 3 | 4 | namespace GitCredentialManager 5 | { 6 | /// 7 | /// Represents a terminal (TTY) interface. 8 | /// 9 | public interface ITerminal 10 | { 11 | /// 12 | /// Write a message to the terminal screen. 13 | /// 14 | /// Format message to print to the terminal. 15 | /// Format argument values. 16 | /// Throw if an error occurs interacting with the native terminal device. 17 | void WriteLine(string format, params object[] args); 18 | 19 | /// 20 | /// Prompt for user input. 21 | /// 22 | /// Prompt message. 23 | /// User input. 24 | /// Throw if an error occurs interacting with the native terminal device. 25 | string Prompt(string prompt); 26 | 27 | /// 28 | /// Prompt for secret user input. 29 | /// 30 | /// 31 | /// Typed user input is masked or hidden. 32 | /// 33 | /// Prompt message. 34 | /// Secret user input. 35 | /// Throw if an error occurs interacting with the native terminal device. 36 | string PromptSecret(string prompt); 37 | } 38 | 39 | public static class TerminalExtensions 40 | { 41 | /// 42 | /// Write a blank line to the terminal screen. 43 | /// 44 | public static void WriteLine(this ITerminal terminal) 45 | { 46 | terminal.WriteLine(Environment.NewLine); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/shared/Core/ITrace2Writer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace GitCredentialManager; 5 | 6 | /// 7 | /// The different format targets supported in the TRACE2 tracing 8 | /// system. 9 | /// 10 | public enum Trace2FormatTarget 11 | { 12 | Event, 13 | Normal, 14 | Performance 15 | } 16 | 17 | public interface ITrace2Writer : IDisposable 18 | { 19 | bool Failed { get; } 20 | 21 | void Write(Trace2Message message); 22 | } 23 | 24 | public class Trace2Writer : DisposableObject, ITrace2Writer 25 | { 26 | private readonly Trace2FormatTarget _formatTarget; 27 | 28 | public bool Failed { get; protected set; } 29 | 30 | protected Trace2Writer(Trace2FormatTarget formatTarget) 31 | { 32 | _formatTarget = formatTarget; 33 | } 34 | 35 | protected string Format(Trace2Message message) 36 | { 37 | EnsureArgument.NotNull(message, nameof(message)); 38 | var sb = new StringBuilder(); 39 | 40 | switch (_formatTarget) 41 | { 42 | case Trace2FormatTarget.Event: 43 | sb.Append(message.ToJson()); 44 | break; 45 | case Trace2FormatTarget.Normal: 46 | sb.Append(message.ToNormalString()); 47 | break; 48 | case Trace2FormatTarget.Performance: 49 | sb.Append(message.ToPerformanceString()); 50 | break; 51 | default: 52 | Console.WriteLine($"warning: unrecognized format target '{_formatTarget}', disabling TRACE2 tracing."); 53 | Failed = true; 54 | break; 55 | } 56 | 57 | sb.Append('\n'); 58 | return sb.ToString(); 59 | } 60 | 61 | public virtual void Write(Trace2Message message) 62 | { } 63 | } 64 | -------------------------------------------------------------------------------- /src/shared/Core/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Core.Tests")] 4 | [assembly: InternalsVisibleTo("GitHub.Tests")] 5 | [assembly: InternalsVisibleTo("Atlassian.Bitbucket.Tests")] 6 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/InteropException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | 5 | namespace GitCredentialManager.Interop 6 | { 7 | /// 8 | /// An unexpected error occurred in interop-code. 9 | /// 10 | [DebuggerDisplay("{DebuggerDisplay}")] 11 | public class InteropException : Exception 12 | { 13 | public InteropException() 14 | : base() { } 15 | 16 | public InteropException(string message, int errorCode) 17 | : base(message) 18 | { 19 | ErrorCode = errorCode; 20 | } 21 | 22 | public InteropException(string message, int errorCode, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | ErrorCode = errorCode; 26 | } 27 | 28 | public InteropException(string message, Win32Exception w32Exception) 29 | : base(message, w32Exception) 30 | { 31 | ErrorCode = w32Exception.NativeErrorCode; 32 | } 33 | 34 | /// 35 | /// Native error code. 36 | /// 37 | public int ErrorCode { get; } 38 | 39 | private string DebuggerDisplay => $"{Message} [0x{ErrorCode:x}]"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/InteropUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace GitCredentialManager.Interop 6 | { 7 | internal static class InteropUtils 8 | { 9 | public static byte[] ToByteArray(IntPtr ptr, long count) 10 | { 11 | var destination = new byte[count]; 12 | Marshal.Copy(ptr, destination, 0, destination.Length); 13 | return destination; 14 | } 15 | 16 | public static bool AreEqual(byte[] bytes, IntPtr ptr, uint length) 17 | { 18 | if (bytes.Length == 0 && (ptr == IntPtr.Zero || length == 0)) 19 | { 20 | return true; 21 | } 22 | 23 | if (bytes.Length != length) 24 | { 25 | return false; 26 | } 27 | 28 | byte[] ptrBytes = ToByteArray(ptr, length); 29 | return bytes.SequenceEqual(ptrBytes); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Linux/LinuxFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using GitCredentialManager.Interop.Posix; 4 | 5 | namespace GitCredentialManager.Interop.Linux 6 | { 7 | public class LinuxFileSystem : PosixFileSystem 8 | { 9 | public override bool IsSamePath(string a, string b) 10 | { 11 | if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b)) 12 | { 13 | return false; 14 | } 15 | 16 | // Normalize paths 17 | a = Path.GetFullPath(a); 18 | b = Path.GetFullPath(b); 19 | 20 | // Resolve symbolic links 21 | a = ResolveSymbolicLinks(a); 22 | b = ResolveSymbolicLinks(b); 23 | 24 | return StringComparer.Ordinal.Equals(a, b); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Linux/Native/Gobject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.Linux.Native 5 | { 6 | public static class Gobject 7 | { 8 | private const string LibraryName = "libgobject-2.0.so.0"; 9 | 10 | [DllImport(LibraryName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 11 | public static extern void g_object_ref(IntPtr @object); 12 | 13 | [DllImport(LibraryName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 14 | public static extern void g_object_unref(IntPtr @object); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Linux/Native/termios_Linux.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using GitCredentialManager.Interop.Posix.Native; 3 | 4 | namespace GitCredentialManager.Interop.Linux.Native 5 | { 6 | public static class Termios_Linux 7 | { 8 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 9 | public static extern int tcgetattr(int fd, out termios_Linux termios); 10 | 11 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 12 | public static extern int tcsetattr(int fd, SetActionFlags optActions, ref termios_Linux termios); 13 | } 14 | 15 | [StructLayout(LayoutKind.Explicit)] 16 | public struct termios_Linux 17 | { 18 | // Linux has an array of 32 elements 19 | private const int NCCS = 32; 20 | 21 | // Linux uses unsigned 32-bit sized flags 22 | [FieldOffset(0)] public InputFlags c_iflag; 23 | [FieldOffset(4)] public OutputFlags c_oflag; 24 | [FieldOffset(8)] public ControlFlags c_cflag; 25 | [FieldOffset(12)] public LocalFlags c_lflag; 26 | 27 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = NCCS)] 28 | [FieldOffset(16)] public byte[] c_cc; 29 | 30 | [FieldOffset(16 + NCCS)] public uint c_ispeed; 31 | [FieldOffset(16 + NCCS + 4)] public uint c_ospeed; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Linux/SecretServiceCredential.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace GitCredentialManager.Interop.Linux 4 | { 5 | [DebuggerDisplay("{DebuggerDisplay}")] 6 | public class SecretServiceCredential : ICredential 7 | { 8 | internal SecretServiceCredential(string service, string account, string password) 9 | { 10 | Service = service; 11 | Account = account; 12 | Password = password; 13 | } 14 | 15 | public string Service { get; } 16 | 17 | public string Account { get; } 18 | 19 | public string Password { get; } 20 | 21 | private string DebuggerDisplay => $"[Service: {Service}, Account: {Account}]"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/MacOSEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading; 6 | using GitCredentialManager.Interop.Posix; 7 | 8 | namespace GitCredentialManager.Interop.MacOS 9 | { 10 | public class MacOSEnvironment : PosixEnvironment 11 | { 12 | private ICollection _pathsToIgnore; 13 | 14 | public MacOSEnvironment(IFileSystem fileSystem) 15 | : base(fileSystem) { } 16 | 17 | internal MacOSEnvironment(IFileSystem fileSystem, IReadOnlyDictionary variables) 18 | : base(fileSystem, variables) { } 19 | 20 | public override bool TryLocateExecutable(string program, out string path) 21 | { 22 | if (_pathsToIgnore is null) 23 | { 24 | _pathsToIgnore = new List(); 25 | if (Variables.TryGetValue("HOMEBREW_PREFIX", out string homebrewPrefix)) 26 | { 27 | string homebrewGit = Path.Combine(homebrewPrefix, "Homebrew/Library/Homebrew/shims/shared/git"); 28 | _pathsToIgnore.Add(homebrewGit); 29 | } 30 | } 31 | return TryLocateExecutable(program, _pathsToIgnore, out path); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/MacOSFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using GitCredentialManager.Interop.Posix; 4 | 5 | namespace GitCredentialManager.Interop.MacOS 6 | { 7 | public class MacOSFileSystem : PosixFileSystem 8 | { 9 | public override bool IsSamePath(string a, string b) 10 | { 11 | if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b)) 12 | { 13 | return false; 14 | } 15 | 16 | // Normalize paths 17 | a = Path.GetFullPath(a); 18 | b = Path.GetFullPath(b); 19 | 20 | // Resolve symbolic links 21 | a = ResolveSymbolicLinks(a); 22 | b = ResolveSymbolicLinks(b); 23 | 24 | // TODO: determine if file system is case-sensitive 25 | // By default HFS+/APFS is NOT case-sensitive... 26 | return StringComparer.OrdinalIgnoreCase.Equals(a, b); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/MacOSKeychainCredential.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace GitCredentialManager.Interop.MacOS 4 | { 5 | [DebuggerDisplay("{DebuggerDisplay}")] 6 | public class MacOSKeychainCredential : ICredential 7 | { 8 | internal MacOSKeychainCredential(string service, string account, string password, string label) 9 | { 10 | Service = service; 11 | Account = account; 12 | Password = password; 13 | Label = label; 14 | } 15 | 16 | public string Service { get; } 17 | 18 | public string Account { get; } 19 | 20 | public string Label { get; } 21 | 22 | public string Password { get; } 23 | 24 | private string DebuggerDisplay => $"{Label} [Service: {Service}, Account: {Account}]"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/MacOSSessionManager.cs: -------------------------------------------------------------------------------- 1 | using GitCredentialManager.Interop.MacOS.Native; 2 | using GitCredentialManager.Interop.Posix; 3 | 4 | namespace GitCredentialManager.Interop.MacOS 5 | { 6 | public class MacOSSessionManager : PosixSessionManager 7 | { 8 | public MacOSSessionManager(IEnvironment env, IFileSystem fs) : base(env, fs) 9 | { 10 | PlatformUtils.EnsureMacOS(); 11 | } 12 | 13 | public override bool IsDesktopSession 14 | { 15 | get 16 | { 17 | // Get information about the current session 18 | int error = SecurityFramework.SessionGetInfo(SecurityFramework.CallerSecuritySession, out int id, out var sessionFlags); 19 | 20 | // Check if the session supports Quartz 21 | if (error == 0 && (sessionFlags & SessionAttributeBits.SessionHasGraphicAccess) != 0) 22 | { 23 | return true; 24 | } 25 | 26 | // Fall-through and check if X11 is available on macOS 27 | return base.IsDesktopSession; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/Native/LibC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.MacOS.Native 5 | { 6 | public static class LibC 7 | { 8 | private const string LibCLib = "libc"; 9 | 10 | [DllImport(LibCLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 11 | public static extern int _NSGetExecutablePath(IntPtr buf, out int bufsize); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/Native/LibSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.MacOS.Native 5 | { 6 | public static class LibSystem 7 | { 8 | private const string LibSystemLib = "/System/Library/Frameworks/System.framework/System"; 9 | 10 | [DllImport(LibSystemLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 11 | public static extern IntPtr dlopen(string name, int flags); 12 | 13 | [DllImport(LibSystemLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 14 | public static extern IntPtr dlsym(IntPtr handle, string symbol); 15 | 16 | public static IntPtr GetGlobal(IntPtr handle, string symbol) 17 | { 18 | IntPtr ptr = dlsym(handle, symbol); 19 | return Marshal.PtrToStructure(ptr); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/MacOS/Native/termios_MacOS.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using GitCredentialManager.Interop.Posix.Native; 3 | 4 | namespace GitCredentialManager.Interop.MacOS.Native 5 | { 6 | public static class Termios_MacOS 7 | { 8 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 9 | public static extern int tcgetattr(int fd, out termios_MacOS termios); 10 | 11 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 12 | public static extern int tcsetattr(int fd, SetActionFlags optActions, ref termios_MacOS termios); 13 | } 14 | 15 | [StructLayout(LayoutKind.Explicit)] 16 | public struct termios_MacOS 17 | { 18 | // macOS has an array of 20 elements 19 | private const int NCCS = 20; 20 | 21 | // macOS uses unsigned 64-bit sized flags 22 | [FieldOffset(0)] public InputFlags c_iflag; 23 | [FieldOffset(8)] public OutputFlags c_oflag; 24 | [FieldOffset(16)] public ControlFlags c_cflag; 25 | [FieldOffset(24)] public LocalFlags c_lflag; 26 | 27 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = NCCS)] 28 | [FieldOffset(32)] public byte[] c_cc; 29 | 30 | [FieldOffset(32 + NCCS)] public ulong c_ispeed; 31 | [FieldOffset(32 + NCCS + 8)] public ulong c_ospeed; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/Native/Fcntl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.Posix.Native 5 | { 6 | public static class Fcntl 7 | { 8 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 9 | public static extern int open(string pathname, OpenFlags flags); 10 | } 11 | 12 | [Flags] 13 | public enum OpenFlags 14 | { 15 | O_RDONLY = 0, 16 | O_WRONLY = 1, 17 | O_RDWR = 2, 18 | O_CREAT = 64, 19 | O_EXCL = 128, 20 | O_NOCTTY = 256, 21 | O_TRUNC = 512, 22 | O_APPEND = 1024, 23 | O_NONBLOCK = 2048, 24 | O_SYNC = 4096, 25 | O_NOFOLLOW = 131072, 26 | O_DIRECTORY = 65536, 27 | O_DIRECT = 16384, 28 | O_ASYNC = 8192, 29 | O_LARGEFILE = 32768, 30 | O_CLOEXEC = 524288, 31 | O_PATH = 2097152, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/Native/Signal.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace GitCredentialManager.Interop.Posix.Native 4 | { 5 | public static class Signal 6 | { 7 | /// 8 | /// Interrupt. 9 | /// 10 | public const int SIGINT = 2; 11 | 12 | /// 13 | /// Quit. 14 | /// 15 | public const int SIGQUIT = 3; 16 | 17 | /// 18 | /// Abort. 19 | /// 20 | public const int SIGABRT = 6; 21 | 22 | /// 23 | /// Kill (cannot be caught or ignored). 24 | /// 25 | public const int SIGKILL = 9; 26 | 27 | /// 28 | /// Software termination signal from kill. 29 | /// 30 | public const int SIGTERM = 15; 31 | 32 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 33 | public static extern void kill(int pid, int sig); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/Native/Stat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.Posix.Native 5 | { 6 | public static class Stat 7 | { 8 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 9 | public static extern int chmod(string path, NativeFileMode mode); 10 | } 11 | [Flags] 12 | public enum NativeFileMode 13 | { 14 | NONE = 0, 15 | 16 | // Default permissions (RW for owner, RW for group, RW for other) 17 | DEFAULT = S_IWOTH | S_IROTH | S_IWGRP | S_IRGRP | S_IWUSR | S_IRUSR, 18 | 19 | // All file access permissions (RWX for owner, group, and other) 20 | ACCESSPERMS = S_IRWXO | S_IRWXU | S_IRWXG, 21 | 22 | // Read for owner (0000400) 23 | S_IRUSR = 0x100, 24 | // Write for owner (0000200) 25 | S_IWUSR = 0x080, 26 | // Execute for owner (0000100) 27 | S_IXUSR = 0x040, 28 | // Access permissions for owner 29 | S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR, 30 | 31 | // Read for group (0000040) 32 | S_IRGRP = 0x020, 33 | // Write for group (0000020) 34 | S_IWGRP = 0x010, 35 | // Execute for group (0000010) 36 | S_IXGRP = 0x008, 37 | // Access permissions for group 38 | S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP, 39 | 40 | // Read for other (0000004) 41 | S_IROTH = 0x004, 42 | // Write for other (0000002) 43 | S_IWOTH = 0x002, 44 | // Execute for other (0000001) 45 | S_IXOTH = 0x001, 46 | // Access permissions for other 47 | S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH, 48 | 49 | // Set user ID on execution (0004000) 50 | S_ISUID = 0x800, 51 | // Set group ID on execution (0002000) 52 | S_ISGID = 0x400, 53 | // Sticky bit (0001000) 54 | S_ISVTX = 0x200, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/Native/Stdio.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace GitCredentialManager.Interop.Posix.Native 6 | { 7 | public static class Stdio 8 | { 9 | public const int EOF = -1; 10 | 11 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 12 | public static extern IntPtr fgets(StringBuilder sb, int size, IntPtr stream); 13 | 14 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 15 | public static extern int fputc(int c, IntPtr stream); 16 | 17 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 18 | public static extern int fprintf(IntPtr stream, string format, string message); 19 | 20 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 21 | public static extern int fgetc(IntPtr stream); 22 | 23 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 24 | public static extern int fputs(string str, IntPtr stream); 25 | 26 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 27 | public static extern void setbuf(IntPtr stream, int size); 28 | 29 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 30 | public static extern IntPtr fdopen(int fd, string mode); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/Native/Stdlib.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace GitCredentialManager.Interop.Posix.Native 4 | { 5 | public static class Stdlib 6 | { 7 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 8 | public static extern unsafe byte* realpath(byte* path, byte* resolved_path); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/Native/Unistd.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace GitCredentialManager.Interop.Posix.Native 4 | { 5 | public static class Unistd 6 | { 7 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 8 | public static extern int read(int fd, byte[] buf, int count); 9 | 10 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 11 | public static extern int write(int fd, byte[] buf, int size); 12 | 13 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 14 | public static extern int close(int fd); 15 | 16 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 17 | public static extern int getpid(); 18 | 19 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 20 | public static extern int getppid(); 21 | 22 | [DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 23 | public static extern int geteuid(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/PosixEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace GitCredentialManager.Interop.Posix 6 | { 7 | public class PosixEnvironment : EnvironmentBase 8 | { 9 | public PosixEnvironment(IFileSystem fileSystem) 10 | : base(fileSystem) { } 11 | 12 | internal PosixEnvironment(IFileSystem fileSystem, IReadOnlyDictionary variables) 13 | : base(fileSystem, variables) { } 14 | 15 | #region EnvironmentBase 16 | 17 | public override void AddDirectoryToPath(string directoryPath, EnvironmentVariableTarget target) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public override void RemoveDirectoryFromPath(string directoryPath, EnvironmentVariableTarget target) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | 27 | protected override string[] SplitPathVariable(string value) 28 | { 29 | return value.Split(':'); 30 | } 31 | 32 | #endregion 33 | 34 | protected override IReadOnlyDictionary GetCurrentVariables() 35 | { 36 | var dict = new Dictionary(); 37 | var variables = Environment.GetEnvironmentVariables(); 38 | 39 | foreach (var key in variables.Keys) 40 | { 41 | if (key is string name && variables[key] is string value) 42 | { 43 | dict[name] = value; 44 | } 45 | } 46 | 47 | return dict; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Posix/PosixSessionManager.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager.Interop.Posix 2 | { 3 | public abstract class PosixSessionManager : SessionManager 4 | { 5 | protected PosixSessionManager(IEnvironment env, IFileSystem fs) : base(env, fs) 6 | { 7 | PlatformUtils.EnsurePosix(); 8 | } 9 | 10 | // Check if we have an X11 or Wayland display environment available 11 | public override bool IsDesktopSession => 12 | !string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable("DISPLAY")) || 13 | !string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable("WAYLAND_DISPLAY")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Windows/Native/Ole32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.Windows.Native 5 | { 6 | public static class Ole32 7 | { 8 | private const string LibraryName = "ole32.dll"; 9 | 10 | public const uint RPC_E_TOO_LATE = 0x80010119; 11 | 12 | [DllImport(LibraryName)] 13 | public static extern int CoInitializeSecurity( 14 | IntPtr pVoid, 15 | int cAuthSvc, 16 | IntPtr asAuthSvc, 17 | IntPtr pReserved1, 18 | RpcAuthnLevel level, 19 | RpcImpLevel impers, 20 | IntPtr pAuthList, 21 | EoAuthnCap dwCapabilities, 22 | IntPtr pReserved3); 23 | 24 | public enum RpcAuthnLevel 25 | { 26 | Default = 0, 27 | None = 1, 28 | Connect = 2, 29 | Call = 3, 30 | Pkt = 4, 31 | PktIntegrity = 5, 32 | PktPrivacy = 6 33 | } 34 | 35 | public enum RpcImpLevel 36 | { 37 | Default = 0, 38 | Anonymous = 1, 39 | Identify = 2, 40 | Impersonate = 3, 41 | Delegate = 4 42 | } 43 | 44 | public enum EoAuthnCap 45 | { 46 | None = 0x00, 47 | MutualAuth = 0x01, 48 | StaticCloaking = 0x20, 49 | DynamicCloaking = 0x40, 50 | AnyAuthority = 0x80, 51 | MakeFullSIC = 0x100, 52 | Default = 0x800, 53 | SecureRefs = 0x02, 54 | AccessControl = 0x04, 55 | AppID = 0x08, 56 | Dynamic = 0x10, 57 | RequireFullSIC = 0x200, 58 | AutoImpersonate = 0x400, 59 | NoCustomMarshal = 0x2000, 60 | DisableAAA = 0x1000 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Windows/Native/Shell32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace GitCredentialManager.Interop.Windows.Native 5 | { 6 | public static class Shell32 7 | { 8 | private const string LibraryName = "shell32.dll"; 9 | 10 | /// 11 | /// Parses a Unicode command line string and returns an array of pointers 12 | /// to the command line arguments, along with a count of such arguments, 13 | /// in a way that is similar to the standard C run-time argv and argc values. 14 | /// 15 | /// 16 | /// Pointer to a null-terminated Unicode string that contains the full command line. 17 | /// If this parameter is an empty string the function returns the path to the current executable file. 18 | /// 19 | /// 20 | /// Pointer to an int that receives the number of array elements returned, similar to argc. 21 | /// 22 | /// A pointer to an array of LPWSTR values, similar to argv. 23 | [DllImport("Shell32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 24 | public static extern IntPtr CommandLineToArgvW(IntPtr lpCmdLine, out int pNumArgs); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Windows/WindowsCredential.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace GitCredentialManager.Interop.Windows 3 | { 4 | public class WindowsCredential : ICredential 5 | { 6 | public WindowsCredential(string service, string userName, string password, string targetName) 7 | { 8 | Service = service; 9 | UserName = userName; 10 | Password = password; 11 | TargetName = targetName; 12 | } 13 | 14 | public string Service { get; } 15 | 16 | public string UserName { get; } 17 | 18 | public string Password { get; } 19 | 20 | public string TargetName { get; } 21 | 22 | string ICredential.Account => UserName; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Windows/WindowsFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace GitCredentialManager.Interop.Windows 5 | { 6 | public class WindowsFileSystem : FileSystem 7 | { 8 | public override bool IsSamePath(string a, string b) 9 | { 10 | if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b)) 11 | { 12 | return false; 13 | } 14 | 15 | a = Path.GetFullPath(a); 16 | b = Path.GetFullPath(b); 17 | 18 | // Note: we do not resolve or handle symlinks on Windows 19 | // because they require administrator permissions to even create! 20 | 21 | return StringComparer.OrdinalIgnoreCase.Equals(a, b); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/Core/Interop/Windows/WindowsProcessManager.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager.Interop.Windows; 2 | 3 | public class WindowsProcessManager : ProcessManager 4 | { 5 | public WindowsProcessManager(ITrace2 trace2) : base(trace2) 6 | { 7 | PlatformUtils.EnsureWindows(); 8 | } 9 | 10 | public override ChildProcess CreateProcess(string path, string args, bool useShellExecute, string workingDirectory) 11 | { 12 | // If we're asked to start a WSL executable we must launch via the wsl.exe command tool 13 | if (!useShellExecute && WslUtils.IsWslPath(path)) 14 | { 15 | string wslPath = WslUtils.ConvertToDistroPath(path, out string distro); 16 | return WslUtils.CreateWslProcess(distro, $"{wslPath} {args}", Trace2, workingDirectory); 17 | } 18 | 19 | return base.CreateProcess(path, args, useShellExecute, workingDirectory); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/Core/NameValueCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | 5 | namespace GitCredentialManager 6 | { 7 | public static class NameValueCollectionExtensions 8 | { 9 | public static IDictionary ToDictionary(this NameValueCollection collection, IEqualityComparer comparer = null) 10 | { 11 | var dict = new Dictionary(comparer ?? StringComparer.Ordinal); 12 | 13 | foreach (string key in collection.AllKeys) 14 | { 15 | dict[key] = collection[key]; 16 | } 17 | 18 | return dict; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/Core/Trace2FileWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System; 3 | 4 | namespace GitCredentialManager; 5 | 6 | public class Trace2FileWriter : Trace2Writer 7 | { 8 | private readonly string _path; 9 | 10 | public Trace2FileWriter(Trace2FormatTarget formatTarget, string path) : base(formatTarget) 11 | { 12 | _path = path; 13 | } 14 | 15 | public override void Write(Trace2Message message) 16 | { 17 | try 18 | { 19 | File.AppendAllText(_path, Format(message)); 20 | } 21 | catch (DirectoryNotFoundException) 22 | { 23 | // Do nothing, as this either means we don't have the 24 | // parent directories above the file, or this trace2 25 | // target points to a directory. 26 | } 27 | catch (UnauthorizedAccessException) 28 | { 29 | // Do nothing, as this either means the file is not 30 | // accessible with current permissions, or we are on 31 | // Windows and the file is currently open for writing 32 | // by another process (likely Git itself.) 33 | } 34 | catch (IOException) 35 | { 36 | // Do nothing, as this likely means that the file is currently 37 | // open by another process (on Windows). 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/shared/Core/Trace2StreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace GitCredentialManager; 5 | 6 | public class Trace2StreamWriter : Trace2Writer 7 | { 8 | private readonly TextWriter _writer; 9 | 10 | public Trace2StreamWriter(Trace2FormatTarget formatTarget, TextWriter writer) 11 | : base(formatTarget) 12 | { 13 | _writer = writer; 14 | } 15 | 16 | public override void Write(Trace2Message message) 17 | { 18 | try 19 | { 20 | _writer.Write(Format(message)); 21 | _writer.Flush(); 22 | } 23 | catch 24 | { 25 | Failed = true; 26 | } 27 | } 28 | 29 | protected override void ReleaseManagedResources() 30 | { 31 | _writer.Dispose(); 32 | base.ReleaseManagedResources(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/Core/TraceUtils.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager; 2 | 3 | public static class TraceUtils 4 | { 5 | public static string FormatSource(string source, int sourceColumnMaxWidth) 6 | { 7 | int idx = 0; 8 | int maxlen = sourceColumnMaxWidth - 3; 9 | int srclen = source.Length; 10 | 11 | while (idx >= 0 && (srclen - idx) > maxlen) 12 | { 13 | idx = source.IndexOf('\\', idx + 1); 14 | } 15 | 16 | // If we cannot find a path separator which allows the path to be long enough, just truncate the file name 17 | if (idx < 0) 18 | { 19 | idx = srclen - maxlen; 20 | } 21 | 22 | return "..." + source.Substring(idx); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Assets/Base.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | #F6F6F6 7 | 8 | 9 | 10 | 11 | 12 | 13 | #282828 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Assets/ButtonHyperlink.axaml: -------------------------------------------------------------------------------- 1 | 3 | 10 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Assets/Controls.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 10 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /src/shared/Core/UI/AvaloniaApp.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/shared/Core/UI/AvaloniaApp.axaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.ApplicationLifetimes; 4 | using Avalonia.Markup.Xaml; 5 | using GitCredentialManager.UI.Controls; 6 | 7 | namespace GitCredentialManager.UI 8 | { 9 | public class AvaloniaApp : Avalonia.Application 10 | { 11 | private readonly Func _mainWindowFunc; 12 | 13 | public AvaloniaApp() { } 14 | 15 | public AvaloniaApp(Func mainWindowFunc) 16 | { 17 | _mainWindowFunc = mainWindowFunc; 18 | } 19 | 20 | public override void Initialize() 21 | { 22 | AvaloniaXamlLoader.Load(this); 23 | } 24 | 25 | public override void OnFrameworkInitializationCompleted() 26 | { 27 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && _mainWindowFunc != null) 28 | { 29 | desktop.MainWindow = _mainWindowFunc(); 30 | } 31 | 32 | base.OnFrameworkInitializationCompleted(); 33 | } 34 | 35 | private void About(object sender, EventArgs e) 36 | { 37 | var window = new AboutWindow(); 38 | window.Show(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Commands/DeviceCodeCommand.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GitCredentialManager.UI.ViewModels; 5 | 6 | namespace GitCredentialManager.UI.Commands 7 | { 8 | public abstract class DeviceCodeCommand : HelperCommand 9 | { 10 | protected DeviceCodeCommand(ICommandContext context) 11 | : base(context, "device", "Show device code prompt.") 12 | { 13 | var code = new Option("--code", "User code."); 14 | AddOption(code); 15 | 16 | var url =new Option("--url", "Verification URL."); 17 | AddOption(url); 18 | 19 | var noLogo = new Option("--no-logo", "Hide the Git Credential Manager logo and logotype."); 20 | AddOption(noLogo); 21 | 22 | this.SetHandler(ExecuteAsync, code, url, noLogo); 23 | } 24 | 25 | private async Task ExecuteAsync(string code, string url, bool noLogo) 26 | { 27 | var viewModel = new DeviceCodeViewModel(Context.Environment) 28 | { 29 | UserCode = code, 30 | VerificationUrl = url, 31 | }; 32 | 33 | viewModel.ShowProductHeader = !noLogo; 34 | 35 | await ShowAsync(viewModel, CancellationToken.None); 36 | 37 | if (!viewModel.WindowResult) 38 | { 39 | throw new Trace2Exception(Context.Trace2, "User cancelled dialog."); 40 | } 41 | 42 | return 0; 43 | } 44 | 45 | protected abstract Task ShowAsync(DeviceCodeViewModel viewModel, CancellationToken ct); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Controls/AboutWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Avalonia; 3 | using Avalonia.Controls; 4 | using Avalonia.Interactivity; 5 | using Avalonia.Markup.Xaml; 6 | 7 | namespace GitCredentialManager.UI.Controls 8 | { 9 | public partial class AboutWindow : Window 10 | { 11 | public string VersionString => $"Version {Constants.GcmVersion}"; 12 | public string ProjectUrl => Constants.HelpUrls.GcmProjectUrl; 13 | 14 | public AboutWindow() 15 | { 16 | InitializeComponent(); 17 | } 18 | 19 | private void ProjectButton_Click(object sender, RoutedEventArgs e) 20 | { 21 | var psi = new ProcessStartInfo(ProjectUrl) 22 | { 23 | UseShellExecute = true 24 | }; 25 | Process.Start(psi); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Controls/IFocusable.cs: -------------------------------------------------------------------------------- 1 | namespace GitCredentialManager.UI.Controls 2 | { 3 | public interface IFocusable 4 | { 5 | void SetFocus(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Controls/ProgressWindow.axaml: -------------------------------------------------------------------------------- 1 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Controls/ProgressWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Avalonia; 5 | using Avalonia.Controls; 6 | using Avalonia.Markup.Xaml; 7 | 8 | namespace GitCredentialManager.UI.Controls; 9 | 10 | public partial class ProgressWindow : Window 11 | { 12 | public ProgressWindow() 13 | { 14 | InitializeComponent(); 15 | } 16 | 17 | public static IntPtr ShowAndGetHandle(CancellationToken ct) 18 | { 19 | var tsc = new TaskCompletionSource(); 20 | 21 | Window CreateWindow() 22 | { 23 | var window = new ProgressWindow(); 24 | window.Loaded += (s, e) => tsc.SetResult(window.TryGetPlatformHandle()?.Handle ?? IntPtr.Zero); 25 | return window; 26 | } 27 | 28 | Task _ = AvaloniaUi.ShowWindowAsync(CreateWindow, IntPtr.Zero, ct); 29 | 30 | return tsc.Task.GetAwaiter().GetResult(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Converters/BoolConvertersEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using Avalonia; 5 | using Avalonia.Data.Converters; 6 | 7 | namespace GitCredentialManager.UI.Converters 8 | { 9 | public static class BoolConvertersEx 10 | { 11 | public static readonly IValueConverter ToThickness = new BoolToThicknessConverter(); 12 | 13 | public static readonly IMultiValueConverter Or = 14 | new FuncMultiValueConverter(x => x.Aggregate(false, (a, b) => a || b)); 15 | 16 | public static readonly IMultiValueConverter And = 17 | new FuncMultiValueConverter(x => x.Aggregate(true, (a, b) => a && b)); 18 | 19 | private class BoolToThicknessConverter : IValueConverter 20 | { 21 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | if (value is not bool b) 24 | { 25 | return null; 26 | } 27 | 28 | if (parameter is int i) 29 | { 30 | return b ? new Thickness(i) : new Thickness(0); 31 | } 32 | 33 | return b ? new Thickness(1) : new Thickness(0); 34 | } 35 | 36 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 37 | { 38 | return null; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Converters/WindowClientAreaConverters.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | using Avalonia.Platform; 3 | 4 | namespace GitCredentialManager.UI.Converters 5 | { 6 | public static class WindowClientAreaConverters 7 | { 8 | public static readonly IValueConverter BoolToChromeHints = 9 | new FuncValueConverter( 10 | x => x 11 | ? ExtendClientAreaChromeHints.NoChrome 12 | : ExtendClientAreaChromeHints.PreferSystemChrome); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/Core/UI/HelperApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.CommandLine; 4 | using System.CommandLine.Builder; 5 | using System.CommandLine.Invocation; 6 | using System.CommandLine.Parsing; 7 | using System.Threading.Tasks; 8 | 9 | namespace GitCredentialManager.UI 10 | { 11 | public class HelperApplication : ApplicationBase 12 | { 13 | private readonly IList _commands = new List(); 14 | 15 | public HelperApplication(ICommandContext context) : base(context) 16 | { 17 | } 18 | 19 | protected override async Task RunInternalAsync(string[] args) 20 | { 21 | var rootCommand = new RootCommand(); 22 | 23 | foreach (Command command in _commands) 24 | { 25 | rootCommand.AddCommand(command); 26 | } 27 | 28 | var parser = new CommandLineBuilder(rootCommand) 29 | .UseDefaults() 30 | .UseExceptionHandler(OnException) 31 | .Build(); 32 | 33 | return await parser.InvokeAsync(args); 34 | } 35 | 36 | public void RegisterCommand(Command command) 37 | { 38 | _commands.Add(command); 39 | } 40 | 41 | private void OnException(Exception ex, InvocationContext invocationContext) 42 | { 43 | if (ex is AggregateException aex) 44 | { 45 | aex.Handle(WriteException); 46 | } 47 | else 48 | { 49 | WriteException(ex); 50 | } 51 | 52 | invocationContext.ExitCode = -1; 53 | } 54 | 55 | private bool WriteException(Exception ex) 56 | { 57 | Context.Streams.Out.WriteDictionary(new Dictionary 58 | { 59 | ["error"] = ex.Message 60 | }); 61 | 62 | return true; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/shared/Core/UI/HelperCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.CommandLine; 4 | 5 | namespace GitCredentialManager.UI 6 | { 7 | public abstract class HelperCommand : Command 8 | { 9 | protected ICommandContext Context { get; } 10 | 11 | public HelperCommand(ICommandContext context, string name, string description) 12 | : base(name, description) 13 | { 14 | Context = context; 15 | } 16 | 17 | protected IntPtr GetParentHandle() 18 | { 19 | if (int.TryParse(Context.Settings.ParentWindowId, out int id)) 20 | { 21 | return new IntPtr(id); 22 | } 23 | 24 | return IntPtr.Zero; 25 | } 26 | 27 | protected void WriteResult(IDictionary result) 28 | { 29 | Context.Streams.Out.WriteDictionary(result); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Core/UI/ViewModels/DeviceCodeViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace GitCredentialManager.UI.ViewModels 4 | { 5 | public class DeviceCodeViewModel : WindowViewModel 6 | { 7 | private readonly IEnvironment _environment; 8 | 9 | private ICommand _verificationUrlCommand; 10 | private string _verificationUrl; 11 | private string _userCode; 12 | private bool _showProductHeader = true; 13 | 14 | public DeviceCodeViewModel() 15 | { 16 | // Constructor the XAML designer 17 | } 18 | 19 | public DeviceCodeViewModel(IEnvironment environment) 20 | { 21 | EnsureArgument.NotNull(environment, nameof(environment)); 22 | 23 | _environment = environment; 24 | 25 | Title = "Device code authentication"; 26 | VerificationUrlCommand = new RelayCommand(OpenVerificationUrl); 27 | } 28 | 29 | private void OpenVerificationUrl() 30 | { 31 | BrowserUtils.OpenDefaultBrowser(_environment, VerificationUrl); 32 | } 33 | 34 | public string UserCode 35 | { 36 | get => _userCode; 37 | set => SetAndRaisePropertyChanged(ref _userCode, value); 38 | } 39 | 40 | public string VerificationUrl 41 | { 42 | get => _verificationUrl; 43 | set => SetAndRaisePropertyChanged(ref _verificationUrl, value); 44 | } 45 | 46 | public ICommand VerificationUrlCommand 47 | { 48 | get => _verificationUrlCommand; 49 | set => SetAndRaisePropertyChanged(ref _verificationUrlCommand, value); 50 | } 51 | 52 | public bool ShowProductHeader 53 | { 54 | get => _showProductHeader; 55 | set => SetAndRaisePropertyChanged(ref _showProductHeader, value); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/shared/Core/UI/ViewModels/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace GitCredentialManager.UI.ViewModels 5 | { 6 | public abstract class ViewModel : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | 15 | protected void SetAndRaisePropertyChanged( 16 | ref T field, T value, [CallerMemberName] string propertyName = null) 17 | { 18 | if (!Equals(field, value)) 19 | { 20 | field = value; 21 | RaisePropertyChanged(propertyName); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Views/CredentialsView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | using GitCredentialManager.UI.Controls; 4 | using GitCredentialManager.UI.ViewModels; 5 | 6 | namespace GitCredentialManager.UI.Views 7 | { 8 | public partial class CredentialsView : UserControl, IFocusable 9 | { 10 | public CredentialsView() 11 | { 12 | InitializeComponent(); 13 | } 14 | 15 | public void SetFocus() 16 | { 17 | if (!(DataContext is CredentialsViewModel vm)) 18 | { 19 | return; 20 | } 21 | 22 | if (string.IsNullOrWhiteSpace(vm.UserName)) 23 | { 24 | // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293 25 | if (!PlatformUtils.IsMacOS()) 26 | _userNameTextBox.Focus(); 27 | } 28 | else 29 | { 30 | // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293 31 | if (!PlatformUtils.IsMacOS()) 32 | _passwordTextBox.Focus(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Views/DefaultAccountView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace GitCredentialManager.UI.Views 5 | { 6 | public partial class DefaultAccountView : UserControl 7 | { 8 | public DefaultAccountView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Views/DeviceCodeView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace GitCredentialManager.UI.Views 5 | { 6 | public partial class DeviceCodeView : UserControl 7 | { 8 | public DeviceCodeView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/Core/UI/Views/OAuthView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace GitCredentialManager.UI.Views 5 | { 6 | public partial class OAuthView : UserControl 7 | { 8 | public OAuthView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/Core/X509Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace GitCredentialManager; 4 | 5 | public static class X509Utils 6 | { 7 | public static X509Certificate2 GetCertificateByThumbprint(string thumbprint) 8 | { 9 | foreach (var location in new[]{StoreLocation.CurrentUser, StoreLocation.LocalMachine}) 10 | { 11 | using var store = new X509Store(StoreName.My, location); 12 | store.Open(OpenFlags.ReadOnly); 13 | 14 | X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); 15 | if (certs.Count > 0) 16 | { 17 | return certs[0]; 18 | } 19 | } 20 | 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | $(RepoOutPath)shared\ 9 | $(PlatformOutPath)$(MSBuildProjectName)\ 10 | $(ProjectOutPath)bin\ 11 | $(ProjectOutPath)obj\ 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/shared/DotnetTool/DotnetTool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | true 5 | dotnet-tool.nuspec 6 | 7 | 8 | version=$(PackageVersion); 9 | publishDir=$(PublishDir); 10 | 11 | true 12 | $(ProjectOutPath)nupkg\$(Configuration)\ 13 | false 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/shared/DotnetTool/DotnetToolSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/shared/DotnetTool/dotnet-tool.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | git-credential-manager 5 | $version$ 6 | Secure, cross-platform Git credential storage with authentication to Azure Repos, GitHub, and other popular Git hosting services. 7 | git-credential-manager 8 | images\icon.png 9 | https://raw.githubusercontent.com/git-ecosystem/git-credential-manager/main/assets/gcm-transparent.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/shared/DotnetTool/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldennington/git-credential-manager/5f9bede1faf9fdab70b533723f91ec91552d0d02/src/shared/DotnetTool/icon.png -------------------------------------------------------------------------------- /src/shared/DotnetTool/pack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | die () { 3 | echo "$*" >&2 4 | exit 1 5 | } 6 | 7 | # Parse script arguments 8 | for i in "$@" 9 | do 10 | case "$i" in 11 | --configuration=*) 12 | CONFIGURATION="${i#*=}" 13 | shift # past argument=value 14 | ;; 15 | --version=*) 16 | VERSION="${i#*=}" 17 | shift # past argument=value 18 | ;; 19 | --publish-dir=*) 20 | PUBLISH_DIR="${i#*=}" 21 | shift # past argument=value 22 | ;; 23 | *) 24 | # unknown option 25 | ;; 26 | esac 27 | done 28 | 29 | CONFIGURATION="${CONFIGURATION:=Debug}" 30 | if [ -z "$VERSION" ]; then 31 | die "--version was not set" 32 | fi 33 | 34 | # Directories 35 | THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" 36 | ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" 37 | SRC="$ROOT/src" 38 | OUT="$ROOT/out" 39 | DOTNET_TOOL="shared/DotnetTool" 40 | 41 | if [ -z "$PUBLISH_DIR" ]; then 42 | PUBLISH_DIR="$OUT/$DOTNET_TOOL/nupkg/$CONFIGURATION" 43 | fi 44 | 45 | echo "Creating dotnet tool package..." 46 | 47 | dotnet pack "$SRC/$DOTNET_TOOL/DotnetTool.csproj" \ 48 | /p:Configuration="$CONFIGURATION" \ 49 | /p:PackageVersion="$VERSION" \ 50 | /p:PublishDir="$PUBLISH_DIR/" 51 | 52 | echo "Dotnet tool pack complete." 53 | -------------------------------------------------------------------------------- /src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net7.0 6 | net472;net7.0 7 | win-x86;osx-x64;linux-x64;osx-arm64 8 | x86 9 | git-credential-manager 10 | GitCredentialManager 11 | $(RepoAssetsPath)gcmicon.ico 12 | false 13 | latest 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/shared/GitHub.Tests/GitHub.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | false 6 | true 7 | latest 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/shared/GitHub.UI.Avalonia/Commands/SelectAccountCommandImpl.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using GitHub.UI.ViewModels; 4 | using GitHub.UI.Views; 5 | using GitCredentialManager; 6 | using GitCredentialManager.UI; 7 | 8 | namespace GitHub.UI.Commands 9 | { 10 | public class SelectAccountCommandImpl : SelectAccountCommand 11 | { 12 | public SelectAccountCommandImpl(ICommandContext context) : base(context) { } 13 | 14 | protected override Task ShowAsync(SelectAccountViewModel viewModel, CancellationToken ct) 15 | { 16 | return AvaloniaUi.ShowViewAsync(viewModel, GetParentHandle(), ct); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/shared/GitHub/AuthenticationResult.cs: -------------------------------------------------------------------------------- 1 | using GitCredentialManager; 2 | 3 | namespace GitHub 4 | { 5 | public struct AuthenticationResult 6 | { 7 | public AuthenticationResult(GitHubAuthenticationResultType type) 8 | { 9 | Type = type; 10 | Token = null; 11 | } 12 | 13 | public AuthenticationResult(GitHubAuthenticationResultType type, string token) 14 | { 15 | Type = type; 16 | Token = token; 17 | } 18 | 19 | public GitHubAuthenticationResultType Type { get; } 20 | 21 | public string Token { get; } 22 | } 23 | 24 | public enum GitHubAuthenticationResultType 25 | { 26 | Success, 27 | Failure, 28 | TwoFactorApp, 29 | TwoFactorSms, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/GitHub/Diagnostics/GitHubApiDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.CommandLine; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using GitCredentialManager; 7 | using GitCredentialManager.Diagnostics; 8 | 9 | namespace GitHub.Diagnostics 10 | { 11 | public class GitHubApiDiagnostic : Diagnostic 12 | { 13 | private readonly IGitHubRestApi _api; 14 | 15 | public GitHubApiDiagnostic(IGitHubRestApi api, ICommandContext commandContext) 16 | : base("GitHub API", commandContext) 17 | { 18 | _api = api; 19 | } 20 | 21 | protected override async Task RunInternalAsync(StringBuilder log, IList additionalFiles) 22 | { 23 | var targetUri = new Uri("https://github.com"); 24 | log.AppendLine($"Using '{targetUri}' as API target."); 25 | 26 | log.Append("Querying '/meta' endpoint..."); 27 | GitHubMetaInfo metaInfo = await _api.GetMetaInfoAsync(targetUri); 28 | log.AppendLine(" OK"); 29 | 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/GitHub/GitHub.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | net7.0;net472 6 | GitHub 7 | GitHub 8 | false 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/shared/GitHub/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("GitHub.Tests")] 4 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Commands/DeviceCommand.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using GitHub.UI.ViewModels; 5 | using GitCredentialManager; 6 | using GitCredentialManager.UI; 7 | 8 | namespace GitHub.UI.Commands 9 | { 10 | public abstract class DeviceCodeCommand : HelperCommand 11 | { 12 | protected DeviceCodeCommand(ICommandContext context) 13 | : base(context, "device", "Show device code prompt.") 14 | { 15 | var code = new Option("--code", "User code."); 16 | AddOption(code); 17 | 18 | var url = new Option("--url", "Verification URL."); 19 | AddOption(url); 20 | 21 | this.SetHandler(ExecuteAsync, code, url); 22 | } 23 | 24 | private async Task ExecuteAsync(string code, string url) 25 | { 26 | var viewModel = new DeviceCodeViewModel(Context.Environment) 27 | { 28 | UserCode = code, 29 | VerificationUrl = url, 30 | }; 31 | 32 | await ShowAsync(viewModel, CancellationToken.None); 33 | 34 | if (!viewModel.WindowResult) 35 | { 36 | throw new Trace2Exception(Context.Trace2, "User cancelled dialog."); 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | protected abstract Task ShowAsync(DeviceCodeViewModel viewModel, CancellationToken ct); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Commands/TwoFactorCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.CommandLine; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using GitHub.UI.ViewModels; 6 | using GitCredentialManager; 7 | using GitCredentialManager.UI; 8 | 9 | namespace GitHub.UI.Commands 10 | { 11 | public abstract class TwoFactorCommand : HelperCommand 12 | { 13 | protected TwoFactorCommand(ICommandContext context) 14 | : base(context, "2fa", "Show two-factor prompt.") 15 | { 16 | var sms = new Option("--sms", "Two-factor code was sent via SMS."); 17 | AddOption(sms); 18 | 19 | this.SetHandler(ExecuteAsync, sms); 20 | } 21 | 22 | private async Task ExecuteAsync(bool sms) 23 | { 24 | var viewModel = new TwoFactorViewModel(Context.Environment, Context.ProcessManager) 25 | { 26 | IsSms = sms 27 | }; 28 | 29 | await ShowAsync(viewModel, CancellationToken.None); 30 | 31 | if (!viewModel.WindowResult) 32 | { 33 | throw new Trace2Exception(Context.Trace2, "User cancelled dialog."); 34 | } 35 | 36 | WriteResult(new Dictionary 37 | { 38 | ["code"] = viewModel.Code 39 | }); 40 | 41 | return 0; 42 | } 43 | 44 | protected abstract Task ShowAsync(TwoFactorViewModel viewModel, CancellationToken ct); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Controls/HorizontalShadowDivider.axaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Controls/HorizontalShadowDivider.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace GitHub.UI.Controls 5 | { 6 | public partial class HorizontalShadowDivider : UserControl 7 | { 8 | public HorizontalShadowDivider() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Controls/SixDigitInput.axaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/ViewModels/DeviceCodeViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | using GitCredentialManager; 3 | using GitCredentialManager.UI; 4 | using GitCredentialManager.UI.ViewModels; 5 | 6 | namespace GitHub.UI.ViewModels 7 | { 8 | public class DeviceCodeViewModel : WindowViewModel 9 | { 10 | private readonly IEnvironment _environment; 11 | 12 | private ICommand _verificationUrlCommand; 13 | private string _verificationUrl; 14 | private string _userCode; 15 | 16 | public DeviceCodeViewModel() 17 | { 18 | // Constructor the XAML designer 19 | } 20 | 21 | public DeviceCodeViewModel(IEnvironment environment) 22 | { 23 | EnsureArgument.NotNull(environment, nameof(environment)); 24 | 25 | _environment = environment; 26 | 27 | Title = "Device code authentication"; 28 | VerificationUrlCommand = new RelayCommand(OpenVerificationUrl); 29 | } 30 | 31 | private void OpenVerificationUrl() 32 | { 33 | BrowserUtils.OpenDefaultBrowser(_environment, VerificationUrl); 34 | } 35 | 36 | public string UserCode 37 | { 38 | get => _userCode; 39 | set => SetAndRaisePropertyChanged(ref _userCode, value); 40 | } 41 | 42 | public string VerificationUrl 43 | { 44 | get => _verificationUrl; 45 | set => SetAndRaisePropertyChanged(ref _verificationUrl, value); 46 | } 47 | 48 | public ICommand VerificationUrlCommand 49 | { 50 | get => _verificationUrlCommand; 51 | set => SetAndRaisePropertyChanged(ref _verificationUrlCommand, value); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Views/DeviceCodeView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace GitHub.UI.Views 5 | { 6 | public partial class DeviceCodeView : UserControl 7 | { 8 | public DeviceCodeView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Views/SelectAccountView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Input; 4 | using Avalonia.Markup.Xaml; 5 | using GitHub.UI.ViewModels; 6 | 7 | namespace GitHub.UI.Views; 8 | 9 | public partial class SelectAccountView : UserControl 10 | { 11 | public SelectAccountView() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private void ListBox_OnDoubleTapped(object sender, TappedEventArgs e) 17 | { 18 | if (DataContext is SelectAccountViewModel { SelectedAccount: not null } vm) 19 | { 20 | vm.ContinueCommand.Execute(null); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/GitHub/UI/Views/TwoFactorView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | using GitCredentialManager; 4 | using GitHub.UI.Controls; 5 | using GitCredentialManager.UI.Controls; 6 | 7 | namespace GitHub.UI.Views 8 | { 9 | public partial class TwoFactorView : UserControl, IFocusable 10 | { 11 | public TwoFactorView() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | public void SetFocus() 17 | { 18 | // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293 19 | if (!PlatformUtils.IsMacOS()) 20 | _codeInput.SetFocus(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/GitLab.Tests/GitLab.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | false 6 | true 7 | latest 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/shared/GitLab/GitLab.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | net7.0;net472 6 | GitLab 7 | GitLab 8 | false 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/shared/GitLab/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("GitLab.Tests")] 4 | -------------------------------------------------------------------------------- /src/shared/Microsoft.AzureRepos.Tests/Microsoft.AzureRepos.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | false 6 | true 7 | latest 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/shared/Microsoft.AzureRepos/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("Microsoft.AzureRepos.Tests")] 4 | -------------------------------------------------------------------------------- /src/shared/Microsoft.AzureRepos/Microsoft.AzureRepos.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | net7.0;net472 6 | Microsoft.AzureRepos 7 | Microsoft.AzureRepos 8 | false 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/AssertEx.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace GitCredentialManager.Tests 4 | { 5 | public static class AssertEx 6 | { 7 | /// 8 | /// Requires the fact or theory be marked with the 9 | /// or . 10 | /// 11 | /// Reason the test has been skipped. 12 | public static void Skip(string reason) 13 | { 14 | Xunit.Skip.If(true, reason); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestGpg.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GitCredentialManager.Interop.Posix; 5 | 6 | namespace GitCredentialManager.Tests.Objects 7 | { 8 | public class TestGpg : IGpg 9 | { 10 | private readonly TestFileSystem _fs; 11 | private readonly ISet _keys = new HashSet(StringComparer.OrdinalIgnoreCase); 12 | 13 | public TestGpg(TestFileSystem fs) 14 | { 15 | _fs = fs; 16 | } 17 | 18 | public string DecryptFile(string path) 19 | { 20 | // No encryption 21 | return Encoding.UTF8.GetString(_fs.Files[path]); 22 | } 23 | 24 | public void EncryptFile(string path, string gpgId, string contents) 25 | { 26 | if (!_keys.Contains(gpgId)) 27 | { 28 | throw new Exception($"No GPG key found for '{gpgId}'."); 29 | } 30 | 31 | // No encryption 32 | _fs.Files[path] = Encoding.UTF8.GetBytes(contents); 33 | } 34 | 35 | public void GenerateKeys(string userId) 36 | { 37 | _keys.Add(userId); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestHostProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace GitCredentialManager.Tests.Objects 5 | { 6 | public class TestHostProvider : HostProvider 7 | { 8 | public TestHostProvider(ICommandContext context) 9 | : base(context) { } 10 | 11 | public Func IsSupportedFunc { get; set; } 12 | 13 | public string LegacyAuthorityIdValue { get; set; } 14 | 15 | public Func GenerateCredentialFunc { get; set; } 16 | 17 | #region HostProvider 18 | 19 | public override string Id { get; } = "test-provider"; 20 | 21 | public override string Name { get; } = "TestHostProvider"; 22 | 23 | public string LegacyAuthorityId => LegacyAuthorityIdValue; 24 | 25 | public override bool IsSupported(InputArguments input) => IsSupportedFunc(input); 26 | 27 | public override Task GenerateCredentialAsync(InputArguments input) 28 | { 29 | return Task.FromResult(GenerateCredentialFunc(input)); 30 | } 31 | 32 | #endregion 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestHostProviderRegistry.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace GitCredentialManager.Tests.Objects 4 | { 5 | public class TestHostProviderRegistry : IHostProviderRegistry 6 | { 7 | public IHostProvider Provider { get; set; } 8 | 9 | #region IHostProviderRegistry 10 | 11 | void IHostProviderRegistry.Register(IHostProvider hostProvider, HostProviderPriority priority) 12 | { 13 | } 14 | 15 | Task IHostProviderRegistry.GetProviderAsync(InputArguments input) 16 | { 17 | return Task.FromResult(Provider); 18 | } 19 | 20 | #endregion 21 | 22 | public void Dispose() 23 | { 24 | Provider?.Dispose(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestHttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | 4 | namespace GitCredentialManager.Tests.Objects 5 | { 6 | public class TestHttpClientFactory : IHttpClientFactory 7 | { 8 | public HttpMessageHandler MessageHandler { get; set; } = new TestHttpMessageHandler(); 9 | 10 | #region IHttpClientFactory 11 | 12 | HttpClient IHttpClientFactory.CreateClient() 13 | { 14 | return new HttpClient(MessageHandler); 15 | } 16 | 17 | #endregion 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestOAuth2WebBrowser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using GitCredentialManager.Authentication.OAuth; 6 | 7 | namespace GitCredentialManager.Tests.Objects 8 | { 9 | public class TestOAuth2WebBrowser : IOAuth2WebBrowser 10 | { 11 | private readonly HttpClient _httpClient; 12 | 13 | public TestOAuth2WebBrowser(HttpMessageHandler httpHandler) 14 | { 15 | _httpClient = new HttpClient(httpHandler); 16 | } 17 | 18 | public Uri UpdateRedirectUri(Uri uri) 19 | { 20 | return uri; 21 | } 22 | 23 | public async Task GetAuthenticationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken ct) 24 | { 25 | using (var response = await _httpClient.SendAsync(HttpMethod.Get, authorizationUri)) 26 | { 27 | response.EnsureSuccessStatusCode(); 28 | return response.Headers.Location; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestSessionManager.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace GitCredentialManager.Tests.Objects 3 | { 4 | public class TestSessionManager : ISessionManager 5 | { 6 | public bool? IsWebBrowserAvailableOverride { get; set; } 7 | 8 | public bool IsDesktopSession { get; set; } 9 | 10 | bool ISessionManager.IsWebBrowserAvailable => IsWebBrowserAvailableOverride ?? IsDesktopSession; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestStandardStreams.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace GitCredentialManager.Tests.Objects 5 | { 6 | public class TestStandardStreams : IStandardStreams 7 | { 8 | public string NewLine { get; set; } = "\n"; 9 | public string In { get; set; } = string.Empty; 10 | public StringBuilder Out { get; set; } = new StringBuilder(); 11 | public StringBuilder Error { get; set; } = new StringBuilder(); 12 | 13 | #region IStandardStreams 14 | 15 | TextReader IStandardStreams.In => new StringReader(In); 16 | 17 | TextWriter IStandardStreams.Out => new StringWriter(Out){NewLine = NewLine}; 18 | 19 | TextWriter IStandardStreams.Error => new StringWriter(Error){NewLine = NewLine}; 20 | 21 | #endregion 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/Objects/TestTerminal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace GitCredentialManager.Tests.Objects 5 | { 6 | public class TestTerminal : ITerminal 7 | { 8 | public IDictionary Prompts = new Dictionary(); 9 | public IDictionary SecretPrompts = new Dictionary(); 10 | public IList<(string, object[])> Messages = new List<(string, object[])>(); 11 | 12 | #region ITerminal 13 | 14 | public void WriteLine(string format, params object[] args) 15 | { 16 | Messages.Add((format, args)); 17 | } 18 | 19 | string ITerminal.Prompt(string prompt) 20 | { 21 | if (!Prompts.TryGetValue(prompt, out string result)) 22 | { 23 | throw new Exception($"No result has been configured for prompt text '{prompt}'"); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | string ITerminal.PromptSecret(string prompt) 30 | { 31 | if (!SecretPrompts.TryGetValue(prompt, out string result)) 32 | { 33 | throw new Exception($"No result has been configured for secret prompt text '{prompt}'"); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | #endregion 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/RestTestUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace GitCredentialManager.Tests 8 | { 9 | public static class RestTestUtilities 10 | { 11 | public static void AssertBasicAuth(HttpRequestMessage request, string userName, string password) 12 | { 13 | string expectedBasicValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}")); 14 | 15 | AuthenticationHeaderValue authHeader = request.Headers.Authorization; 16 | Assert.NotNull(authHeader); 17 | Assert.Equal("Basic", authHeader.Scheme); 18 | Assert.Equal(expectedBasicValue, authHeader.Parameter); 19 | } 20 | 21 | public static void AssertBearerAuth(HttpRequestMessage request, string token) 22 | { 23 | AuthenticationHeaderValue authHeader = request.Headers.Authorization; 24 | Assert.NotNull(authHeader); 25 | Assert.Equal("Bearer", authHeader.Scheme); 26 | Assert.Equal(token, authHeader.Parameter); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/shared/TestInfrastructure/TestInfrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | GitCredentialManager.Tests 6 | false 7 | false 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/windows/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | $(RepoOutPath)windows\ 9 | $(PlatformOutPath)$(MSBuildProjectName)\ 10 | $(ProjectOutPath)bin\ 11 | $(ProjectOutPath)obj\ 12 | 13 | 14 | --------------------------------------------------------------------------------