diff --git a/.vscode/launch.json b/.vscode/launch.json index 152dd35b32..3da6339024 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,26 +5,35 @@ "version": "0.2.0", "configurations": [ { - "name": "Git Credential Manager (get)", + "name": "gcm get", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/net10.0/git-credential-manager.dll", + "program": "${workspaceFolder}/out/bin/git-credential-manager/debug/git-credential-manager.dll", "args": ["get"], - "cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager", + "cwd": "${workspaceFolder}", "console": "integratedTerminal", "stopAtEntry": false, }, { - "name": "Git Credential Manager (store)", + "name": "gcm store", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/out/shared/Git-Credential-Manager/bin/Debug/net10.0/git-credential-manager.dll", + "program": "${workspaceFolder}/out/bin/git-credential-manager/debug/git-credential-manager.dll", "args": ["store"], - "cwd": "${workspaceFolder}/out/shared/Git-Credential-Manager", + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + }, + { + "name": "gcm erase", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/out/bin/git-credential-manager/debug/git-credential-manager.dll", + "args": ["erase"], + "cwd": "${workspaceFolder}", "console": "integratedTerminal", "stopAtEntry": false, }, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8dd1d37379..098c118dac 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,21 +11,21 @@ }, "args": [ "build", - "${workspaceFolder}/Git-Credential-Manager.sln", + "${workspaceFolder}/git-credential-manager.slnx", ], "problemMatcher": "$msCompile" }, { "label": "test", "command": "dotnet", - "type": "shell", + "type": "process", "group":{ "kind": "test", "isDefault": true }, "args": [ "test", - "${workspaceFolder}/Git-Credential-Manager.sln" + "${workspaceFolder}/git-credential-manager.slnx" ], "presentation": { "reveal": "always", @@ -35,13 +35,13 @@ { "label": "test with coverage", "command": "dotnet", - "type": "shell", + "type": "process", "group": "test", "args": [ "test", - "${workspaceFolder}/Git-Credential-Manager.sln", + "${workspaceFolder}/git-credential-manager.slnx", "--collect", - "'XPlat Code Coverage'", + "XPlat Code Coverage", "--settings", "${workspaceFolder}/.code-coverage/coverlet.settings.xml" ], @@ -53,10 +53,13 @@ { "label": "report coverage - nix", "command": "dotnet", - "type": "shell", + "type": "process", "group": "test", "args": [ - "~/.nuget/packages/reportgenerator/*/*/net10.0/ReportGenerator.dll", + "exec", + "--roll-forward", + "Major", + "${env:HOME}/.nuget/packages/reportgenerator/5.3.10/tools/net8.0/ReportGenerator.dll", "-reports:${workspaceFolder}/**/TestResults/**/coverage.cobertura.xml", "-targetdir:${workspaceFolder}/out/code-coverage" ], @@ -68,10 +71,13 @@ { "label": "report coverage - win", "command": "dotnet", - "type": "shell", + "type": "process", "group": "test", "args": [ - "${env:USERROFILE}/.nuget/packages/reportgenerator/*/*/net10.0/ReportGenerator.dll", + "exec", + "--roll-forward", + "Major", + "${env:USERPROFILE}/.nuget/packages/reportgenerator/5.3.10/tools/net8.0/ReportGenerator.dll", "-reports:${workspaceFolder}/**/TestResults/**/coverage.cobertura.xml", "-targetdir:${workspaceFolder}/out/code-coverage" ], diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..68f5525085 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,88 @@ +# Agent instructions + +Follow [CONTRIBUTING.md](CONTRIBUTING.md) when contributing to Git Credential +Manager. Keep changes focused, match existing code style, and include tests and +documentation for behaviour changes. + +## Ethos + +Git Credential Manager is an open source project that values collaboration, +code quality, and adherence to best practices, whilst still being pragmatic +about trade-offs and exceptions to the rules. Don't be afraid to make pragmatic +decisions when necessary, but have a good and well argued rationale. + +We try to not favor any particular platform, technology, or approach without +good reason, and aim to avoid hacks and workarounds where possible. + +Be kind, be honest, and respectful. + +## Project guidance + +Read the guidance relevant to your change: + +- [Development and debugging](docs/development.md): setup, build commands, + debugging, testing, coverage, and documentation checks. +- [Architecture](docs/architecture.md): project boundaries, command execution, + platform abstractions, credential storage, and error handling. +- [Host provider specification](docs/hostprovider.md): provider selection, + credential operations, protocol responses, and authentication interaction. +- [Main thread dispatcher](docs/dispatcher.md): UI and broker threading, + lifecycle rules, and testing seams. +- [Documentation index](docs/README.md): user-facing configuration and + provider-specific guidance. +- [SECURITY.md](SECURITY.md): private reporting of security vulnerabilities. + +## Making changes + +- Inspect the affected implementation and tests before editing. Reuse existing + helpers and patterns rather than introducing parallel implementations. +- Preserve Windows, macOS, and Linux behaviour. Use `ICommandContext` services + and existing platform abstractions; reuse mocks from `src/TestInfrastructure` + instead of accessing live services in unit tests. +- Keep Git's credential stdin/stdout separate from user interaction. + Use `ICommandContext.Console` for messages and prompts, and secret-aware + tracing for sensitive values. Respect interaction and prompt settings. +- Follow the dispatcher rules for main-thread work. Do not eagerly start the + UI framework or block its main loop on asynchronous work. +- Keep dependencies, reflection, and serialization compatible with Native AOT + and trimming. Use the existing source-generated JSON contexts and central + package versions in `Directory.Packages.props`. +- Update user documentation when changing configuration, environment variables, + commands, or authentication behaviour. Update developer documentation when + changing architecture, APIs, build tooling, or test workflows. +- Do not hand-edit generated artifacts under `out/`. Do not use real secrets + in source, tests, examples, or ordinary trace messages. +- Avoid making drive-by changes to other pieces of code; focus on the area + relevant to your change. +- If making changes directly outside the scope of the request would make things + clearer/cleaning and more maintainable, make the change or refactor as a + standalone change in a commit first, then a second commit to introduce your + main change. +- Stop and ask when requirements or repository rules are unclear. + +## Validation + +For every code change, build and run the full solution test suite: + +```shell +dotnet build git-credential-manager.slnx +dotnet test git-credential-manager.slnx +``` + +Targeted tests can help during development but do not replace the full run. +Platform-specific tests skip on other operating systems; CI provides the +cross-platform matrix. + +Exercise changed UI flows through the main executable. Validate publishing or +packaging changes with the applicable distribution build on its host OS; an +ordinary build does not validate Native AOT publishing. See the development +guide for commands. + +For documentation-only changes, follow best practices for Markdown and use +common linters to check for spelling mistakes and layout issues. + +## Commits + +Use logical commits and follow the +[commit guidance](CONTRIBUTING.md#commits) for area-prefixed imperative subjects, +explanatory bodies, sign-offs, and AI-assistance trailers. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87314ca39a..e0f6777a01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,16 +19,15 @@ you agree to abide by its terms. ## Submitting a pull request 1. [Fork][fork] and clone the repository -1. Configure and install the dependencies: `dotnet restore` -1. Make sure the tests pass on your machine: `dotnet test` +1. Follow [development setup][development] and restore dependencies: + `dotnet restore` +1. Make sure the full solution test suite passes on your machine: + `dotnet test git-credential-manager.slnx` 1. Create a new branch: `git switch -c my-branch-name` -1. Make your change, add tests, and make sure the tests still pass -1. For UI updates, test your changes by executing a `dotnet run` in applicable - UI-related project directories: - - `Atlassian.Bitbucket.UI.Avalonia` - - `GitHub.UI.Avalonia` - - `Atlassian.Bitbucket.UI.Windows` - - `GitHub.UI.Windows` +1. Make your change, add tests, and make sure all tests pass +1. For UI updates, exercise the relevant flow through the main executable: + `dotnet run --project src/git-credential-manager -- get`. + See [debugging instructions][debugging] for stdin and startup arguments. 1. Organize your changes into one or more [logical, descriptive commits][commits]. 1. Push to your fork and [submit a pull request][pr] 1. Pat your self on the back and wait for your pull request to be reviewed and @@ -40,22 +39,56 @@ request being accepted: - Match existing code style. - Write tests. - Keep your change as focused as possible. If there are multiple changes you - would like to make that are not dependent upon each other, consider - submitting them as separate pull requests. + would like to make that are not dependent upon each other, consider + submitting them as separate pull requests. + +Run the full solution test suite for every code change. Targeted tests are useful +while iterating, but do not replace the full run. Platform-specific tests skip +on other operating systems; CI covers the Windows, macOS, and Linux matrix. +See [testing][testing] for test infrastructure and coverage instructions. + +## Commits + +Make each commit one logical change. Include the tests and documentation for a +behaviour change in the same commit; keep unrelated refactors and formatting +separate. + +Use `area: short imperative summary` for the subject, with a concrete area, +lowercase text after the colon, no trailing period, and at most 72 characters. +Explain why the change is needed in the body, wrapping prose at about 72 +characters. + +Credit meaningful AI assistance with `Assisted-by: `. +We don't require the `Signed-off-by` trailer (`git commit -s`) on commits, but +it is recommended. + +Please watch [this very helpful video][commits-video] that outlines the commit +message conventions and best practices for open source projects. + +## Development resources + +The [developer documentation index][developer-docs] links to build, debugging, +architecture, threading, and provider guidance. ## Resources - [How to Contribute to Open Source][how-to-contribute] - [Using Pull Requests][prs] - [GitHub Help][github-help] +- [Writing Commits For You, Your Friends, And Your Future Self][commits-video] [code-of-conduct]: CODE_OF_CONDUCT.md -[commits]: https://www.youtube.com/watch?v=4qLtKx9S9a8 +[commits]: #commits +[commits-video]: https://www.youtube.com/watch?v=4qLtKx9S9a8 [contribute-under-repo-license]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license [fork]: https://github.com/git-ecosystem/git-credential-manager/fork [github-help]: https://help.github.com [how-to-contribute]: https://opensource.guide/how-to-contribute/ [issue]: https://github.com/git-ecosystem/git-credential-manager/issues/new/choose [license]: LICENSE +[debugging]: docs/development.md#debugging +[developer-docs]: docs/README.md#developer-documentation +[development]: docs/development.md [pr]: https://github.com/git-ecosystem/git-credential-manager/compare [prs]: https://help.github.com/articles/about-pull-requests/ +[testing]: docs/development.md#testing diff --git a/docs/README.md b/docs/README.md index 2128aa88ad..25847cfe6f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ -# User documentation +# Documentation -The following are links to GCM user support documentation: +## User documentation + +The following are links to user support documentation: - [Frequently asked questions][gcm-faq] - [Command-line usage][gcm-usage] @@ -9,17 +11,34 @@ The following are links to GCM user support documentation: - [Enterprise configuration][gcm-enterprise-config] - [Network and HTTP configuration][gcm-net-config] - [Credential stores][gcm-credstores] -- [Host provider specification][gcm-host-provider] - [Azure Repos OAuth tokens][gcm-azure-tokens] - [Azure Managed Identities and Service Principals][gcm-misp] - [Azure Workload Identity Federation Workflows][gcm-azure-wif] - [GitLab support][gcm-gitlab] +- [Bitbucket authentication][gcm-bitbucket] - [Generic OAuth support][gcm-oauth] - [NTLM and Kerberos authentication][gcm-ntlm-kerberos] +## Developer documentation + +These are links helpful for developers of Git Credential Manager: + +- [Contributing][gcm-contributing] +- [Development, debugging, and testing][gcm-development] +- [Architecture][gcm-architecture] +- [Main thread dispatcher (UI thread)][gcm-dispatcher] +- [Host provider specification][gcm-host-provider] +- [Bitbucket development][gcm-bitbucket-dev] + +[gcm-architecture]: architecture.md [gcm-azure-tokens]: azrepos-users-and-tokens.md +[gcm-bitbucket]: bitbucket-authentication.md +[gcm-bitbucket-dev]: bitbucket-development.md [gcm-config]: configuration.md +[gcm-contributing]: ../CONTRIBUTING.md [gcm-credstores]: credstores.md +[gcm-development]: development.md +[gcm-dispatcher]: dispatcher.md [gcm-enterprise-config]: enterprise-config.md [gcm-env]: environment.md [gcm-faq]: faq.md diff --git a/docs/architecture.md b/docs/architecture.md index 3bd8d66684..179f49fa69 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,86 +2,67 @@ ## Overview -```text -+------------------------------------------------------------------------------+ -| | -| Git-Credential-Manager | -| | -+-+-------------+--------------+-----+---------------------+-----------------+-+ - | | | | | | - | | | | Windows | Windows | - | | | | | | - | +-----------v-----------+ | | +----------------v---------------+ | - | | | | | | | | - | | GitHub <-------------+ GitHub.UI.Windows | | - | | | | | | | | - | +-+---------------------+ | | +-+------------------------------+ | - | | | | | | - | | +---------------------v-+ | | +------------------------------v-+ - | | | | | | | | - | | | Atlassian.Bitbucket <------------+ Atlassian.Bitbucket.UI.Windows | - | | | | | | | | - | | +-+---------------------+ | | +---------------+----------------+ - | | | | | | - | | | +----------------------v-+ | | - | | | | | | | - | | | | Microsoft.AzureRepos | | | - | | | | | | | - | | | +-----------+------------+ | | - | | | | | | -+-v---v----v--------------v------------+ +-v-----------------v----------------+ -| | | | -| Core <--+ Core.UI | -| | | | -+--------------------------------------+ +------------------------------------+ +```mermaid +flowchart TD + App["git-credential-manager"] --> GitHub + App --> Bitbucket["Atlassian.Bitbucket"] + App --> AzureRepos["Microsoft.AzureRepos"] + App --> GitLab + App --> Core + GitHub --> Core + Bitbucket --> Core + AzureRepos --> Core + GitLab --> Core ``` Git Credential Manager (GCM) is built to be Git host and platform/OS -agnostic. Most of the shared logic (command execution, the abstract platform -subsystems, etc) can be found in the `Core` class -library (C#). The library targets .NET Standard as well as .NET Framework. - -> **Note** -> -> The reason for also targeting .NET Framework directly is that the -> `Microsoft.Identity.Client` ([MSAL.NET][msal]) -> library requires a .NET Framework target to be able to show the embedded web -> browser auth pop-up on Windows platforms. -> -> There are extension points that now exist in MSAL.NET meaning we can plug-in -> our own browser pop-up handling code on .NET meaning both Windows and -> Mac. We haven't yet gotten around to exploring this. -> -> See [GCM issue 113][issue-113] for more information. - -The entry-point for GCM can be found in the `Git-Credential-Manager` -project, a console application that targets both .NET and .NET Framework. -This project emits the `git-credential-manager(.exe)` executable, and -contains very little code - registration of all supported host providers and -running the `Application` object found in `Core`. - -Providers have their own projects/assemblies that take dependencies on the -`Core` core assembly, and are dependents of the main -entry point application `Git-Credential-Manager`. Code in these binaries is -expected to run on all supported platforms and typically (see MSAL.NET note -above) does not include any graphical user interface; they use terminal prompts -only. - -Where a provider needs some platform-specific interaction or graphical user -interface, the recommended model is to have a separate 'helper' executable that -the shared, core binaries shell out to. Currently the Bitbucket and GitHub -providers each have a WPF (Windows only) helper executable that shows -authentication prompts and messages. - -The `Core.UI` project is a WPF (Windows only) assembly -that contains common WPF components and styles that are shared between provider -helpers on Windows. +agnostic. Shared commands, the Git credential protocol, settings, credential +storage, authentication, console services, and platform abstractions live in +[`src/Core`][core-project]. The product projects target the latest .NET LTS; +they do not target .NET Standard or .NET Framework. The current project graph +is declared in [`git-credential-manager.slnx`][solution]. + +The entry point is [`src/git-credential-manager/Program.cs`][core-program]. +It initializes the main-thread dispatcher, starts an `AppMain` thread, registers +the host providers, and runs the `Application` object from `Core`. This project +emits the `git-credential-manager(.exe)` executable. + +GitHub, Bitbucket, Azure Repos, and GitLab have their own projects, each +referencing `Core`. The executable references all four. `GenericHostProvider` +lives in `Core` and provides the fallback for other hosts. ### Cross-platform UI -We hope to be able to migrate the WPF/Windows only helpers to [Avalonia][avalonia] -in order to gain cross-platform graphical user interface support. See -[GCM issue 136][issue-136] for up-to-date progress on this effort. +Graphical prompts use [Avalonia][avalonia] **in process**. Shared controls, +views, view models, and the application main loop live under `src/Core/UI`; +provider-specific UI lives in the corresponding provider project. + +The main loop starts lazily, when work is first posted to GCM's dispatcher. +Returning a stored credential does not require starting the UI framework. +See the [main thread dispatcher][gcm-dispatcher] documentation before changing +UI initialization, main-thread work, or shutdown. + +Authentication components also retain support for configured out-of-process UI +helpers. These are optional overrides, not a requirement for new providers or +separate UI projects built by this solution. See the +[host provider specification][host-provider-helpers]. + +### Build and deployment + +Shared build properties and artifact paths are declared in +[`Directory.Build.props`][build-props]. Package versions are managed centrally +in [`Directory.Packages.props`][packages-props]. + +An ordinary solution build produces managed binaries under `out/bin/`. +Platform distribution projects under `build/windows`, `build/macos`, and +`build/linux` publish Native AOT binaries by default. Their publish scripts also +support a trimmed, self-contained non-AOT build. The .NET tool distribution is +different: it publishes portable, framework-dependent IL without AOT or trimming. +See [development and debugging][gcm-development] for commands and prerequisites. + +Publishing constraints matter when adding dependencies, reflection, or +serialization. Existing JSON serializers use source-generated contexts so +their metadata is available to the Native AOT and trimming toolchains. ### Microsoft Entra authentication @@ -105,80 +86,59 @@ how that works and the rules for posting to it. ## Command execution -```text - +---------------+ - | | - | Git | - | | - +---+-------^---+ - | | - +---v---+---+---+ - | stdin | stdout| - +---+---+---^---+ - | | - (2) | | (7) - Select | | Serialize - Command | | Result - | | - (3) | | - Select | | -+---------------+ Provider +---v-------+---+ -| Host Provider | | | -| Registry <------------+ Command | -| | | | -+-------^-------+ +----+------^---+ - | | | - | (4) | | (6) - | Execute | | Return - | Operation | | Result - | (1) | | - | Register +----v------+---+ - | | | - +--------------------+ Host Provider | - | | - +-------^-------+ - | - (5) Use services | - | - +-------v-------+ - | Command | - | Context | - +---------------+ +```mermaid +flowchart TB + git["Git"] -.- io("stdin/stdout") + io -- select command (2) --> cmd["Command"] + cmd -- serialize result (7) --> io + cmd <-. select provider (3) .-> registry["Host Provider Registry"] + cmd -- execute operation (4) --> provider["Host Provider"] + provider -- return result (6) --> cmd + provider <-. use services (5) .-> ctx["Command Context"] + provider -- register (1) --> registry ``` -Git Credential Manager maintains a set of known commands including -`Get|Store|EraseCommand`, as well as commands for install and help/usage. +`Application` uses `System.CommandLine` to register the main command called by +Git: `get`, `store`, `erase`, `capability`, along with help/version options, +GCM setup, and provider-specific commands. -GCM also maintains a set of known, registered host providers that implement -the `IHostProvider` interface. Providers register themselves by adding an +GCM maintains a set of known, registered host providers that implement the +`IHostProvider` interface. Providers register themselves by adding an instance of the provider to the `Application` object via the `RegisterProvider` -method in [`Core.Program`][core-program]. +method in [`Program`][core-program]. The `GenericHostProvider` is registered last so that it can handle all other -HTTP-based remotes as a catch-all, and provide basic username/password auth and -detect the presence of Windows Integrated Authentication (Kerberos, NTLM, -Negotiate) support (1). +supported remotes as a low-priority catch-all. It supports basic authentication, +[generic OAuth][generic-oauth], and detection of Windows Integrated +Authentication (Kerberos, NTLM, Negotiate) support (1). For each invocation of GCM, the first argument on the command-line is matched against the known commands and if there is a successful match, the input from Git (over standard input) is deserialized and the command is executed (2). -The `Get|Store|EraseCommand`s consult the host provider registry for the most -appropriate host provider. The default registry implementation select the a host -provider by asking each registered provider in turn if they understand the -request. The provider selection can be overridden by the user via the +The `get|store|erase` commands parse stdin into a `GitRequest` and scope +settings to its remote URI. They consult the host provider registry for the most +appropriate provider. At each priority, the registry first checks +`IsSupported(GitRequest)` in registration order. If no provider matches, it can +probe an HTTP(S) remote with a HEAD request and check +`IsSupported(HttpResponseMessage)`. Probing is bounded by the configured timeout +and can be disabled; a successful probe is reused across priority levels. +The provider selection can be overridden by the user via the [`credential.provider`][credential-provider] or [`GCM_PROVIDER`][gcm-provider] configuration and environment variable respectively (3). -The `Get|Store|EraseCommand`s call the corresponding +The `get|store|erase` commands call the corresponding `Get|Store|EraseCredentialAsync` methods on the `IHostProvider`, passing the -request from Git together with an instance of the `ICommandContext` (4). The -host provider can then make use of various services available on the command -context to complete the requested operation (5). +`GitRequest` (4). Providers receive `ICommandContext` at construction, rather +than as an argument to each credential operation, and use its services to +complete the request (5). Once a credential has been created, retrieved, stored or erased, the host -provider returns the credential (for `get` operations only) to the calling -command (6). The credential is then serialized and returned to Git over standard -output (7) and GCM terminates with a successful exit code. +provider returns a `GitResponse` (for `get` operations only) to the calling +command (6). The `get` command serializes the response over stdout, including +capability-gated fields where negotiated with Git (7). A response can also +cancel the credential acquisition pipeline (`quit=1`), yield to another helper +(an empty response), or request another authentication round (`continue=1`). +See the [provider response contract][host-provider-responses]. ## Host provider @@ -194,11 +154,11 @@ method. Calls to `get`, `store`, or `erase` result in first a call to and request. This value forms part of the attributes associated with any stored credential in the credential store. During a `get` operation the credential store is queried for an existing credential with such service name. -If a credential is found it is returned immediately. Similarly, calls to `store` -and `erase` are handles automatically to store credentials against, and erase -credentials matching the service name. Methods are implemented as `virtual` -meaning you can always override this behaviour, for example to clear other -custom caches on an `erase` request, without having to reimplement the +If a credential is found it is wrapped in a `GitResponse` and returned +immediately. Similarly, calls to `store` and `erase` automatically store or +remove credentials matching the service name. Methods are implemented as +`virtual` meaning you can always override this behaviour, for example to clear +other custom caches on an `erase` request, without having to reimplement the lookup/store credential logic. The default implementation of `GetServiceName` is usually sufficient for most @@ -226,23 +186,24 @@ requests), `StoreCredentialAsync` (for `store` requests) or `GitRequest` contains the request information passed over standard input from Git/the caller; the same as was passed to `IsSupported`. -The return value for the `get` operation must be an `ICredential` that Git can -use to complete authentication. +`GetCredentialAsync` returns `Task`, not `Task`. +A successful response contains an `ICredential` that Git can use to complete +authentication. The base class's `GenerateCredentialAsync` method still returns +`Task`. -> **Note:** -> +> [!NOTE] > The credential can also be an instance where both username and password are > the empty string, to signal to Git it should let cURL use "any auth" > detection - typically to use Windows Integrated Authentication. There are no return values for the `store` and `erase` operations as Git ignores any output or exit codes for these commands. Failures for these operations are -best communicated via writing to the Standard Error stream via -`ICommandContext.Streams.Error`. +communicated through `ICommandContext.Console`, which writes user-facing +messages to stderr. ## Command context -The `ICommandContext` which contains numerous services which are useful for +`ICommandContext` contains numerous services which are useful for interacting with various platform subsystems, such as the file system or environment variables. All services on the command context are exposed as interfaces for ease of testing and portability between different operating @@ -250,17 +211,22 @@ systems and platforms. Component|Description -|- -CredentialStore|A secure operating system controlled location for storing and retrieving `ICredential` objects. +CredentialStore|Stores and retrieves `ICredential` objects through the configured backend, including native OS stores and file-backed implementations. See [credential stores][credential-stores]. Settings|Abstraction over all GCM settings. -Streams|Abstraction over standard input, output and error streams connected to the parent process (typically Git). -Terminal|Provides interactions with an attached terminal, if it exists. +Streams|Standard input, output, and error streams connected to the parent process (typically Git). During credential operations, stdin/stdout are reserved for the Git protocol. +Console|User-facing messages on stderr and interactive Spectre.Console prompts on the controlling terminal. Only interactive prompts require a TTY. SessionManager|Provides information about the current user session. -Trace|Provides tracing information that may be useful for debugging issues in the wild. Secret information MUST be filtered out completely or via the `Write___Secret` method(s). +Trace|Diagnostic tracing. Use secret-aware methods such as `WriteLineSecrets` and `WriteDictionarySecrets` to mask secrets unless secret tracing was explicitly enabled. FileSystem|Abstraction over file system operations. HttpClientFactory|Factory for creating `HttpClient` instances that are configured with the correct user agent, headers, and proxy settings. Git|Provides interactions with Git and Git configuration. Environment|Abstraction over the current system/user environment variables. -SystemPrompts|Provides services for showing system/OS native credential prompts. +ProcessManager|Creates child processes with the appropriate standard streams, working directory, and Trace2 attribution. + +Prefer these abstractions and the shared mocks in `src/TestInfrastructure` +when writing tests. Do not use Git's protocol streams for user-facing prompts +or diagnostics. Authentication components check the interaction, GUI, and +terminal-prompt settings before requesting user input. ## Error handling and tracing @@ -273,20 +239,31 @@ should be human readable. When there is a known or user-fixable issue, instructions on how to self-remedy the issue, or links to relevant documentation should be given. -Warnings can be emitted over the standard error stream -(`ICommandContext.Streams.Error`) when you want to alert the user to a potential -issue with their configuration that does not necessarily stop the -operation/authentication. +Warnings can be emitted with `ICommandContext.Console.WriteWarning` when you +want to alert the user to a potential configuration issue that does not +necessarily stop authentication. The `ITrace` component can be found on the `ICommandContext` object or passed in -directly to some constructors. Verbose and diagnostic information is be written +directly to some constructors. Verbose and diagnostic information is written to the trace object in most places of GCM. +Cancellation is distinct from an unrecoverable error. During `get`, +`OperationCanceledException` and terminal interrupts are translated to +`GitResponse.Cancel()`, preventing Git from prompting again. Terminal interrupts +in other commands are handled by `Application` with exit code 130 and no +`fatal:` banner. + [avalonia]: https://avaloniaui.net/ -[core-program]: ../src/shared/Git-Credential-Manager/Program.cs +[build-props]: ../Directory.Build.props +[core-program]: ../src/git-credential-manager/Program.cs +[core-project]: ../src/Core [credential-provider]: configuration.md#credentialprovider -[issue-113]: https://github.com/git-ecosystem/git-credential-manager/issues/113 -[issue-136]: https://github.com/git-ecosystem/git-credential-manager/issues/136 +[credential-stores]: credstores.md +[gcm-development]: development.md [gcm-dispatcher]: dispatcher.md -[gcm-provider]: environment.md#GCM_PROVIDER -[msal]: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet +[gcm-provider]: environment.md#gcm_provider +[generic-oauth]: generic-oauth.md +[host-provider-helpers]: hostprovider.md#3-helpers +[host-provider-responses]: hostprovider.md#23-retrieving-credentials +[packages-props]: ../Directory.Packages.props +[solution]: ../git-credential-manager.slnx diff --git a/docs/development.md b/docs/development.md index 5ca0de8f2b..fa0b6c1731 100644 --- a/docs/development.md +++ b/docs/development.md @@ -6,14 +6,49 @@ Start by cloning this repository: git clone https://github.com/git-ecosystem/git-credential-manager ``` -You also need the latest version of the .NET SDK which can be downloaded and -installed from the [.NET website][dotnet-web]. +Install a .NET SDK compatible with [`global.json`][global-json] from the +[.NET website][dotnet-web]. The product typically targets the latest LTS release +of .NET. Build defaults and dependency versions are maintained in +[`Directory.Build.props`][build-props] and +[`Directory.Packages.props`][packages-props]. ## Building The `git-credential-manager.slnx` solution can be opened and built in Visual Studio, Visual Studio Code, or JetBrains Rider. +For an ordinary development build, run these commands from the repository root: + +```shell +dotnet restore +dotnet build git-credential-manager.slnx +``` + +Managed binaries are written to `out/bin///`, for +example `out/bin/git-credential-manager/debug/git-credential-manager.dll`. +Intermediate files are under `out/obj/`. These paths use the SDK's artifacts +output layout, not per-project `bin` and `obj` directories. + +### Distribution prerequisites + +An ordinary build is not a distribution publish. The platform distribution +scripts publish Native AOT binaries by default and require native build tools +in addition to the .NET SDK: + +- **Windows:** Visual Studio's Desktop development with C++ workload and + PowerShell 7 (`pwsh`). The distribution project restores its Inno Setup + compiler through NuGet. +- **macOS:** Xcode Command Line Tools. For Native AOT builds, also read the + Homebrew SDK warning below. +- **Linux:** a C/C++ compiler toolchain and development libraries required by + .NET, including Clang and zlib development headers on Ubuntu. ARM32 + cross-compilation also needs the ARMHF toolchain used in CI. + +See the [.NET Native AOT prerequisites][native-aot] and the +[CI workflow][ci-workflow] for platform details. Build a platform's distribution +on that platform: its distribution project skips packaging on other operating +systems. + Each platform's distributables (installers, packages, and archives) are produced by building the matching project under `build/`, as shown below. These commands mirror what CI runs. @@ -23,7 +58,7 @@ mirror what CI runs. To build the macOS distribution from the command line, run: ```shell -dotnet build build/macos --configuration=Debug --runtime=osx-arm64 +dotnet build build/macos --configuration=debug --runtime=osx-arm64 ``` Use `osx-x64` or `osx-arm64` for the `--runtime`, or omit it to build for the @@ -78,7 +113,7 @@ The flat binaries can also be found in To build the Windows distribution from the command line, run: ```powershell -dotnet build build\windows --configuration=Debug --runtime=win-x64 +dotnet build build\windows --configuration=debug --runtime=win-x64 ``` Use `win-x64`, `win-x86`, or `win-arm64` for the `--runtime`, or omit it to @@ -96,7 +131,7 @@ The flat binaries can also be found in To build the Linux distribution from the command line, run: ```shell -dotnet build build/linux --configuration=Debug --runtime=linux-x64 +dotnet build build/linux --configuration=debug --runtime=linux-x64 ``` Use `linux-x64`, `linux-arm64`, or `linux-arm` for the `--runtime`, or omit it @@ -261,6 +296,9 @@ method 0. `exit`: contains current executable's exit code 0. `child_start`: describes a child process that is about to be spawned 0. `child_exit`: describes a child process at exit +0. `thread_start`: identifies a thread starting a separate Trace2 context +0. `thread_exit`: records that thread's completion and elapsed time +0. `error`: records an error encountered by the application 0. `cmd_name`: identifies the canonical command and inherited command hierarchy 0. `region_enter`: describes a region (e.g. a timer for a section of code that is interesting) on entry @@ -274,13 +312,37 @@ section][trace2-events] of Git's Trace2 API documentation. Want to see more events? Consider contributing! We'd :love: to see your awesome work in support of building out this API. +## Testing + +Run the full solution test suite from the repository root: + +```shell +dotnet test git-credential-manager.slnx +``` + +For faster iteration, you can run a project or filter specific tests: + +```shell +dotnet test src/Core.Tests/Core.Tests.csproj --filter FullyQualifiedName~WslUtilsTests +``` + +These focused runs do not replace the full suite required by the +[contributing guide][contributing]. Tests use xUnit, Moq, and mocks of commonly +used types can be found in [`src/TestInfrastructure`][test-infrastructure]. +Prefer these mocks to live network, credential-store, or Git-configuration +changes. + +The [platform-specific attributes][platform-attributes] skip tests on other +operating systems. A passing local run does not exercise all platform code; +CI runs the suite across Windows, macOS, and Linux. + ### Code coverage metrics If you want code coverage metrics these can be generated either from the command line: ```shell -dotnet test --collect:"XPlat Code Coverage" --settings=./.code-coverage/coverlet.settings.xml +dotnet test git-credential-manager.slnx --collect:"XPlat Code Coverage" --settings=.code-coverage/coverlet.settings.xml ``` Or via the VSCode Terminal/Run Task: @@ -289,28 +351,27 @@ Or via the VSCode Terminal/Run Task: test with coverage ``` -HTML reports can be generated using ReportGenerator, this should be installed -during the build process, from the command line: +ReportGenerator is restored with the test projects. The current package version +is declared in `Directory.Packages.props`; the examples below use that version +and the default NuGet package cache. Adjust the path if either changes. ```shell dotnet ~/.nuget/packages/reportgenerator/*/*/net10.0/ReportGenerator.dll -reports:./**/TestResults/**/coverage.cobertura.xml -targetdir:./out/code-coverage ``` -or +..or in PowerShell on Windows: -```shell +```powershell dotnet {$env:USERPROFILE}/.nuget/packages/reportgenerator/*/*/net10.0/ReportGenerator.dll -reports:./**/TestResults/**/coverage.cobertura.xml -targetdir:./out/code-coverage ``` -Or via VSCode Terminal/Run Task: +..or via VSCode Terminal/Run Task: ```console +# Unix report coverage - nix -``` -or - -```console +# Windows report coverage - win ``` @@ -322,18 +383,40 @@ the [documentation on GitHub][markdownlint]. The configuration used for markdownlint is in [.markdownlint.jsonc][markdownlint-config]. Documents are checked for link validity using [lychee][lychee]. Lychee can be -installed in a variety of ways depending on your platform, see the [docs on GitHub][lychee-docs]. +installed in a variety of ways depending on your platform, see the +[docs on GitHub][lychee-docs]. Some URLs are ignored by lychee, per the [lycheeignore][lycheeignore]. +With the tools installed, run from the repository root: + +```shell +markdownlint-cli2 "**/*.md" "!.github/ISSUE_TEMPLATE" +lychee --no-progress . +``` + +You can pass only changed Markdown files while iterating. +`lychee --offline --include-fragments ` checks local paths and anchors +without requesting external links. CI checks external links too; see the +[documentation workflow][lint-workflow]. + +[build-props]: ../Directory.Build.props +[ci-workflow]: ../.github/workflows/continuous-integration.yml +[contributing]: ../CONTRIBUTING.md [dotnet-runtime-120440]: https://github.com/dotnet/runtime/issues/120440 [dotnet-web]: https://dotnet.microsoft.com/ [custom-helpers]: https://git-scm.com/docs/gitcredentials#_custom_helpers [ioformat]: https://git-scm.com/docs/git-credential#IOFMT +[global-json]: ../global.json +[lint-workflow]: ../.github/workflows/lint-docs.yml [lychee]: https://lychee.cli.rs/ [lychee-docs]: https://github.com/lycheeverse/lychee [lycheeignore]: ../.lycheeignore [markdownlint]: https://github.com/DavidAnson/markdownlint-cli2 [markdownlint-config]: ../.markdownlint.jsonc +[native-aot]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/ +[packages-props]: ../Directory.Packages.props +[platform-attributes]: ../src/TestInfrastructure/PlatformAttributes.cs +[test-infrastructure]: ../src/TestInfrastructure [trace2]: https://git-scm.com/docs/api-trace2 [trace2-events]: https://git-scm.com/docs/api-trace2#_event_specific_keyvalue_pairs [trace2-targets]: https://git-scm.com/docs/api-trace2#_trace2_targets diff --git a/docs/hostprovider.md b/docs/hostprovider.md index 5d0c9df446..4fb56df00e 100644 --- a/docs/hostprovider.md +++ b/docs/hostprovider.md @@ -2,7 +2,7 @@ ## Abstract -Git Credential Manger, the cross-platform and cross-host Git credential +Git Credential Manager, the cross-platform and cross-host Git credential helper, can be extended to support any Git hosting service allowing seamless authentication to secured Git repositories by implementing and registering a "host provider". @@ -55,11 +55,11 @@ Implementors MUST implement all interface properties and abstract methods. The `Id` and `Name` properties MUST be implemented and MUST NOT return default or empty values. -The `Id` field MUST be unique over the set of all providers, or -else an error will be thrown at registration time. The `Id` field MAY be a -unique random string of characters and digits such as a UUID, but it is -RECOMMENDED to use a human-readable value consisting of letter characters in the -range \[a-z\] only. +The `Id` field MUST be unique over the set of all providers. The registry +rejects the reserved ID `auto` or any attempt to register a duplicate ID. +The `Id` field MAY be a unique random string of characters and digits such as a +UUID, but it is RECOMMENDED to use a human-readable value consisting of letter +characters in the range \[a-z\] only. The `Name` property MUST be a human readable string and MUST identify the Git hosting service this provider supports. @@ -113,6 +113,12 @@ to check for recognised on-premises instances (for example, by inspecting response headers) and return `true` if it wishes to be called upon to handle the credential request, otherwise it MUST return `false`. +The registry probes only HTTP(S) remotes, only when the auto-detection timeout +is positive, and reuses the probe response across priority levels. Network +failures or a disabled probe do not prevent selection by `GitRequest`. The user +may disable the auto-detection probe if desired so do not rely on this mechanism +exclusively. + Host providers SHOULD NOT make further network calls if possible during any of the `IsSupported` method overloads to avoid degrading the performance of the overall application. @@ -129,10 +135,16 @@ method by throwing an `Exception`. Implementors MUST provide detailed information regarding the reason why the authentication cannot continue, for example "HTTP is not secure, please use HTTPS". +Rejecting an unsafe or unsupported operation is different from user +cancellation. A provider can return `GitResponse.Cancel()` when the user +cancels, so Git stops rather than falling back to another prompt. + ### 2.3. Retrieving Credentials The `GetCredentialAsync` method will be called when a `get` request is made. -The method MUST return an instance of an `ICredential` capable of fulfilling the +The method MUST return a `GitResponse` instance. + +A successful response MUST contain an `ICredential` capable of fulfilling the specific access request. The argument passed to `GetCredentialAsync` contains properties indicating the required `protocol` and `host` for this request. The `username` and `path` properties are OPTIONAL, however if they are present, they @@ -151,11 +163,25 @@ If a provider chooses to make a validation web request and that request fails or is inconclusive, it SHOULD assume the credential is still valid and return it anyway, letting Git (the caller) attempt to use it and validate it itself. -The returned `ICredential` MAY leave both the username and password values as -the empty string or `null`. This signals to Git (or rather cURL) that it should -negotiate the authentication mechanism with the remote itself. This is typically +The credential inside a successful response MAY leave both username and password +values as the empty string or `null`. This signals to Git (or rather cURL) that +it should negotiate the authentication mechanism with the remote itself. +This is typically used for Windows Integrated Authentication. +The response shapes defined by `GitResponse` are: + +| Response | Meaning | Output from `GetCommand` | +| --- | --- | --- | +| `GitResponse.Ok(credential)` or `new GitResponse(credential)` | Return a credential. | Credential fields and a terminating blank line. | +| `GitResponse.Continue(credential)` | Return a credential and expect another authentication round. | Credential fields and `continue=1` when the `state` capability is negotiated. | +| `GitResponse.Cancel()` | Stop credential acquisition without another helper or prompt. | `quit=1` and a terminating blank line. | +| `GitResponse.Yield()` | Let Git try another helper or its own prompt. | Only a blank line. | + +Cancel and yield responses do not carry credentials. `GetCommand` also +translates `OperationCanceledException` and terminal `InterruptedException` +from a provider into a cancellation response. + #### 2.3.1 Authentication Prompts When it is not possible to locate an existing credential suitable for the @@ -180,13 +206,38 @@ check if interaction has been disabled (`ISettings.IsInteractionAllowed`), and an exception MUST be thrown if interaction has been disallowed. Authentication prompts that display a graphical user interface such as a window -are MUST be preferred when an interactive "desktop" session is available. +are MUST be preferred when an interactive "desktop" session is available and +the user has not disabled GUI prompts. Disabling GUI prompts MUST be respected; +use a supported terminal flow instead where one exists. If an authentication prompt is required when an interactive session is not available and a terminal/TTY is attached then a provider MUST first check if terminal prompts are enabled (`ISettings.IsTerminalPromptsEnabled`), and an exception MUST be thrown if interaction has been disallowed. +User-facing output and prompts SHOULD use `ICommandContext.Console`. +Output-only messages go to stderr; interactive prompts use the controlling +terminal. Providers MUST NOT consume Git's stdin or write user messages to its +stdout, which are reserved for the credential protocol. + +#### 2.3.2 Protocol capabilities and state + +`GitRequest` exposes advertised capabilities, authentication challenges +(`WwwAuth`), and replayed per-helper state, as well as the basic protocol, host, +path, username, and password fields. + +The command layer negotiates capabilities by intersecting Git's advertised +capabilities with GCM's supported set. It emits `authtype`, `credential`, and +`ephemeral` only when `authtype` is negotiated, and `continue` and `state[]` +only when `state` is negotiated. Providers SHOULD use the typed response +properties instead of writing protocol fields themselves. + +Use `SetState` or `WithState` to attach validated per-helper state. The command +layer adds GCM's namespace prefix on output; `GitRequest.State` strips it on +input. Providers MUST NOT add the prefix themselves or treat other helpers' +state as their own. State and additional properties are not emitted for +cancelled or yielded responses. + ### 2.4. Storing Credentials Host providers MAY store credentials at various stages of a typical @@ -285,26 +336,23 @@ of, or the entire cache, when a call to `EraseCredentialAsync` is made. ## 3. Helpers -Host providers MAY wish to make use of platform or operating system specific -features such as native APIs and native graphical user interfaces, in order to -offer a better authentication experience. +GCM's built-in graphical prompts use in-process Avalonia UI. Shared UI lives +under the Core project, and provider-specific views and view models live in +their provider projects. -Host providers MUST function without the presence of a helper, even if that -function is to fail gracefully with a user-friendly error message, including -a remedy to correct their installation. Host providers SHOULD always offer a -terminal/TTY or text-based authentication mechanism alongside any graphical -interface provided by a helper. +Providers MUST use the existing `AvaloniaUi` helpers and follow the +[main thread dispatcher][dispatcher] rules for UI and broker work. -In order to achieve this host providers MUST introduce an out-of-process -"helper" executable that can be invoked from the main GCM process. This -allows the "helper" executable full implementation freedom of runtime, language, -etc. +Authentication components MAY support optional out-of-process helper overrides. +Host providers MUST handle an unavailable helper gracefully; they SHOULD offer +an available in-process or terminal flow, or fail with a user-friendly error and +remedy when no supported flow remains. Interaction settings still apply. -Communications between the main and helper processes MAY use any IPC mechanism -available. It is RECOMMENDED implementors use standard input/output streams or -file descriptors to send and receive data as this is consistent with how Git and -GCM communicate. UNIX sockets or Windows Named Pipes MAY also be used when -an ongoing back-and-forth communication is required. +Communications between the main process and an optional helper MAY use any IPC +mechanism available. It is RECOMMENDED implementors use standard input/output +streams or file descriptors to send and receive data as this is consistent with +how Git and GCM communicate. UNIX sockets or Windows Named Pipes MAY also be +used when an ongoing back-and-forth communication is required. ### 3.1. Discovery @@ -319,9 +367,9 @@ MUST include detailed failure information in the error message. If the reason for failure can be fixed by the user the error message MUST include instructions to fix the problem, or a link to online documentation. -In the case of a recoverable error, host providers SHOULD print a warning -message to the standard error stream, and MUST include the error information and -the recovery steps take in the trace log. +In the case of a recoverable error, host providers SHOULD use +`ICommandContext.Console.WriteWarning` to print a message to stderr, and MUST +include the error information and the recovery steps take in the trace log. In the case of an authentication error, providers SHOULD attempt to prompt the user again with a message indicating the incorrect authentication details have @@ -329,7 +377,7 @@ been entered. ## 5. Custom Commands -If a host provider wishes to surface custom commands the SHOULD implement the +If a host provider wishes to surface custom commands it SHOULD implement the `ICommandProvider` interface. Each provider is given the opportunity to create a single `ProviderCommand` @@ -344,6 +392,7 @@ take, but implementors SHOULD attempt to follow existing practices and styles. 1. [`System.CommandLine` API][github-dotnet-cli] [gcm]: https://github.com/git-ecosystem/git-credential-manager +[dispatcher]: dispatcher.md [github-dotnet-cli]: https://github.com/dotnet/command-line-api [hostprovider-base-class]: #26-hostprovider-base-class [references]: #references