|
| 1 | +# Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +```text |
| 6 | ++---------------------------------------------------------------------------------------------------------------------+ |
| 7 | +| | |
| 8 | +| Git-Credential-Manager | |
| 9 | +| | |
| 10 | ++----------------+--------------------+--------------+--------------+-----+-----------------------+-----------------+-+ |
| 11 | + | | | | | | | |
| 12 | + | Mac | | | | Windows | Windows | |
| 13 | + | | | | | | | |
| 14 | + | | +-----------v-----------+ | | +----------------v---------------+ | |
| 15 | + | | | | | Windows | | | |
| 16 | + | | | GitHub <--------+------+ GitHub.UI.Windows | | |
| 17 | + | | | | | | | | | |
| 18 | + | | +-+---------------------+ | | +-+------------------------------+ | |
| 19 | + | | | | | | | |
| 20 | + | | | +---------------------v-+ | | +------------------------------v-+ |
| 21 | + | | | | | |Windows | | | |
| 22 | + | | | | Atlassian.Bitbucket <--------------+ Atlassian.Bitbucket.UI.Windows | |
| 23 | + | | | | | | | | | |
| 24 | + | | | +-+---------------------+ | | +---------------+----------------+ |
| 25 | + | | | | | | | |
| 26 | ++----------------v----------------+ | | | +----------------------v-+ | | |
| 27 | +| | | Mac | | | | | |
| 28 | +| Microsoft.Authentication.Helper <----------------+ Microsoft.AzureRepos | | | |
| 29 | +| | | | | | | | | |
| 30 | ++---------------------------------+ | | | +-----------+------------+ | | |
| 31 | + | | | | | | |
| 32 | + | | | | | | |
| 33 | + | | | | | | |
| 34 | + | | | | | | |
| 35 | + +-v----v----v--------------v------------+ +-v-----------------v----------------+ |
| 36 | + | | | | |
| 37 | + | Microsoft.Git.CredentialManager <----+ Microsoft.Git.CredentialManager.UI | |
| 38 | + | | | | |
| 39 | + +---------------------------------------+ +------------------------------------+ |
| 40 | +``` |
| 41 | + |
| 42 | +Git Credential Manager Core (GCM Core) is built to be Git host and platform/OS agonstic. |
| 43 | +Most of the shared logic (command execution, the abstract platform subsystems, etc) can be found in the |
| 44 | +`Microsoft.Git.CredentialManager` class library (C#). The library targets .NET Standard as well as .NET Framework. |
| 45 | + |
| 46 | +> **Note** |
| 47 | +> |
| 48 | +> The reason for also targeting .NET Framework directly is that the `Microsoft.Identity.Client` |
| 49 | +> ([MSAL.NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet)) library requires a .NET Framework |
| 50 | +> target to be able to show the embedded web browser auth pop-up on Windows platforms. |
| 51 | +> |
| 52 | +> There are extension points that now exist in MSAL.NET meaning we can plug-in our own browser pop-up handling code |
| 53 | +> on .NET Core meaning both Windows and Mac. We haven't yet gotten around to exploring this. |
| 54 | +
|
| 55 | +The entry-point for GCM Core can be found in the `Git-Credential-Manager` project, a console application that targets both |
| 56 | +.NET Core and .NET Framework. This project emits the `git-credential-manager-core(.exe)` executable, and contains very little |
| 57 | +code - registration of all supported host providers and running the `Application` object found in `Microsoft.Git.CredentialManager`. |
| 58 | + |
| 59 | +Providers have their own projects/assemblies that take dependencies on the `Microsoft.Git.CredentialManager` core assembly, |
| 60 | +and are dependents of the main entry point application `Git-Credential-Manager`. Code in these binaries is expected to run |
| 61 | +on all supported platforms and typically (see MSAL.NET note above) does not include any graphical user interface; they use |
| 62 | +terminal prompts only. |
| 63 | + |
| 64 | +Where a provider needs some platform-specific interaction or graphical user interface, the recommended model is to have a |
| 65 | +separate 'helper' executable that the shared, core binaries shell out to. Currently the Bitbucket and GitHub providers each |
| 66 | +have a WPF (Windows only) helper executable that shows authentication prompts and messages. |
| 67 | + |
| 68 | +The `Microsoft.Git.CredentialHelper.UI` project is a WPF (Windows only) assembly that contains common WPF components and styles |
| 69 | +that are shared between provider helpers on Windows. |
| 70 | + |
| 71 | +### Cross-platform UI |
| 72 | + |
| 73 | +We hope to be able to migrate the WPF/Windows only helpers to [Avalonia](https://avaloniaui.net/) in order to gain cross-platform |
| 74 | +graphical user interface support. See [this](https://github.com/microsoft/Git-Credential-Manager-Core/issues/136) issue for |
| 75 | +up-to-date progress on this effort. |
| 76 | + |
| 77 | +### Microsoft authentication |
| 78 | + |
| 79 | +For authentication using Microsoft Accounts or Azure Active Directory, things are a little different. The `MicrosoftAuthentication` |
| 80 | +component is present in the core `Microsoft.Git.CredentialManager` assembly, rather than bundled with a specific host provider. |
| 81 | +This was done to allow any service that may wish to in the future integrate with Microsoft Accounts or Azure Active Directory can |
| 82 | +make use of this reusable authentication component. |
| 83 | + |
| 84 | +Since MSAL.NET includes embedded GUI on Windows (when targeting .NET Frameonly only - see note above) we have no helper executable |
| 85 | +on Windows. However, on macOS the `MicrosoftAuthentication` component shells out to a native macOS helper that completely takes over |
| 86 | +all authentication flows using the older ADAL Objective-C libary. This was done because MSAL.NET does not offer the same level of |
| 87 | +integration for [MDM](https://en.wikipedia.org/wiki/Mobile_device_management) purposes, as well as lacking an embedded UI on non-Windows |
| 88 | +platforms. As MSAL.NET continues to evolve we hope to replace the ADAL/macOS helper altogether. |
| 89 | + |
| 90 | +## Asynchronous programming |
| 91 | + |
| 92 | +GCM Core makes use of the `async`/`await` model of .NET and C# in almost all parts of the codebase where appropriate |
| 93 | +as usually requests end up going to the network at some point. |
| 94 | + |
| 95 | +## Command execution |
| 96 | + |
| 97 | +```text |
| 98 | + +---------------+ |
| 99 | + | | |
| 100 | + | Git | |
| 101 | + | | |
| 102 | + +---+-------^---+ |
| 103 | + | | |
| 104 | + +---v---+---+---+ |
| 105 | + | stdin | stdout| |
| 106 | + +---+---+---^---+ |
| 107 | + | | |
| 108 | + (2) | | (7) |
| 109 | + Select | | Serialize |
| 110 | + Command | | Result |
| 111 | + | | |
| 112 | + (3) | | |
| 113 | + Select | | |
| 114 | ++---------------+ Provider +---v-------+---+ |
| 115 | +| Host Provider | | | |
| 116 | +| Registry <------------+ Command | |
| 117 | +| | | | |
| 118 | ++-------^-------+ +----+------^---+ |
| 119 | + | | | |
| 120 | + | (4) | | (6) |
| 121 | + | Execute | | Return |
| 122 | + | Operation | | Result |
| 123 | + | (1) | | |
| 124 | + | Register +----v------+---+ |
| 125 | + | | | |
| 126 | + +--------------------+ Host Provider | |
| 127 | + | | |
| 128 | + +-------^-------+ |
| 129 | + | |
| 130 | + (5) Use services | |
| 131 | + | |
| 132 | + +-------v-------+ |
| 133 | + | Command | |
| 134 | + | Context | |
| 135 | + +---------------+ |
| 136 | +``` |
| 137 | + |
| 138 | +Git Credential Manager Core maintains a set of known commands including `Get|Store|EraseCommand`, as well as commands for install and help/usage. |
| 139 | + |
| 140 | +GCM Core also maintains a set of known, registered host providers that implement the `IHostProvider` interface. Providers register themselves |
| 141 | +by adding an instance of the provider to the `Application` object via the `RegisterProvider` method [here](../src/shared/Git-Credential-Manager/Program.cs). |
| 142 | +The `GenericHostProvider` is registered last so that it can handle all other HTTP-based remotes as a catch-all, and provide basic username/password |
| 143 | +auth and detect the presense of Windows Integrated Authentication (Kerberos, NTLM, Negotiate) support (1). |
| 144 | + |
| 145 | +For each invocation of GCM Core, the first argument on the command-line is matched against the known commands and if there is a successful match, |
| 146 | +the input from Git (over standard input) is deserialized and the command is executed (2). |
| 147 | + |
| 148 | +The `Get|Store|EraseCommand`s consult the host provider registry for the most appropriate host provider. The default registry implementation select |
| 149 | +the a host provider by asking each registered provider in turn if they understand the request. The provider selection can be overriden by the user |
| 150 | +via the [`credential.provider`](configuration.md#credentialprovider) or [`GCM_PROVIDER`](environment.md#GCM_PROVIDER) configuration and environment |
| 151 | +variable respectively (3)). |
| 152 | + |
| 153 | +The `Get|Store|EraseCommand`s call the corresponding `Get|Store|EraseCredentialAsync` methods on the `IHostProvider`, passing the request from Git |
| 154 | +together with an instance of the `ICommandContext` (4). The host provider can then make use of various services available on the command context to |
| 155 | +complete the requested operation (5). |
| 156 | + |
| 157 | +Once a credential has been created, retrieved, stored or erased, the host provider returns the credential (for `get` operations only) to the calling |
| 158 | +command (6). The credential is then serialized and returned to Git over standard output (7) and GCM Core terminates with a successful exit code. |
| 159 | + |
| 160 | +## Host provider |
| 161 | + |
| 162 | +Host providers implement the `IHostProvider` interface. They can choose to directly implement the interface they can also derive from the `HostProvider` |
| 163 | +abstract class (which itself implements the `IHostProvider` interface). |
| 164 | + |
| 165 | +The `HostProvider` abstract class implements the `Get|Store|EraseCredentialAsync` methods and instead has a `GenerateCredentialAsync` and |
| 166 | +`GetCredentialKey` abstract methods. Calls to `get`, `store`, or `erase` result in first a call to `GetCredentialKey` which should return a stable and |
| 167 | +unique "key" for the request. This forms the key for any stored credential in the credential store. During a `get` operation the credential store is queried |
| 168 | +for an existing credential with the computed key. If a credential is found it is returned immediately. Similarly, calls to `store` and `erase` are handles |
| 169 | +automatically to store credentials against, and erase credentials matching the computed key. Methods are implemented as `virtual` meaning you can always |
| 170 | +override this behaviour, for example to clear other custom caches on an `erase` request, without having to reimplement the lookup/store credential logic. |
| 171 | + |
| 172 | +Host providers are queried in turn (registration order) via the `IHostProvider.IsSupported` method and passed the input recieved from Git. |
| 173 | +If the provider recognises the request, for example by a matching known host name, they can return `true`. If the provider wants to cancel and abort |
| 174 | +an authentication request, for example if this is a HTTP (not HTTPS) request for a known host, they should still return `true` and later cancel the request. |
| 175 | + |
| 176 | +Depending on the request from Git, one of `GetCredentialAsync` (for `get` requests), `StoreCredentialAsync` (for `store` requests) or |
| 177 | +`EraseCredentialAsync` (for `erase` requests) will be called. The argument `InputArguments` contains the request information passed over standard input |
| 178 | +from Git/the caller; the same as was passed to `IsSupported`. |
| 179 | + |
| 180 | +The return value for the `get` operation must be an `ICredential` that Git can use to complete authentication. The credential can also be an instance where |
| 181 | +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. |
| 182 | + |
| 183 | +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 |
| 184 | +are best communicated via writing to the Standard Error stream via `ICommandContext.Streams.Error`. |
| 185 | + |
| 186 | +## Command context |
| 187 | + |
| 188 | +The `ICommandContext` which contains numerous services which are useful for interacting with various platform subsystems, such as the file system or environment |
| 189 | +variables. All services on the command context are exposed as interfaces for ease of testing and portability between different operating systems and platforms. |
| 190 | + |
| 191 | +Component|Description |
| 192 | +-|- |
| 193 | +CredentialStore|A secure operating system controlled location for storing and retrieving `ICredential` objects. |
| 194 | +Settings|Abstraction over all GCM Core settings. |
| 195 | +Streams|Abstraction over standard input, output and error streams connected to the parent process (typically Git). |
| 196 | +Terminal|Provides interactions with an attached terminal, if it exists. |
| 197 | +SessionManager|Provides information about the current user session. |
| 198 | +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). |
| 199 | +FileSystem|Abstraction over file system operations. |
| 200 | +HttpClientFactory|Factory for creating `HttpClient` instances that are configured with the correct user agent, headers, and proxy settings. |
| 201 | +Git|Provides interactions with Git and Git configuration. |
| 202 | +Environment|Abstraction over the current system/user environment variables. |
| 203 | +SystemPrompts|Provides services for showing system/OS native credential prompts. |
| 204 | + |
| 205 | +## Error handling and tracing |
| 206 | + |
| 207 | +GCM Core operates a 'fail fast' approach to unrecoverable errors. This usually means throwing an `Exception` which will propagate up to the entry-point and |
| 208 | +be caught, a non-zero exit code returned, and the error message printed with the "fatal:" prefix. For errors originating from interop/native code, you should |
| 209 | +throw an exception of the `InteropException` type. Error messages in exceptions should be human readable. When there is a known or user-fixable issue, instructions |
| 210 | +on how to self-rememdy the issue, or links to relevant documentation should be given. |
| 211 | + |
| 212 | +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 |
| 213 | +configuration that does not necessarily stop the operation/authentication. |
| 214 | + |
| 215 | +The `ITrace` component can be found on the `ICommandContext` object or passed in directly to some constructors. Verbose and diagnostic information is be written |
| 216 | +to the trace object in most places of GCM Core. |
0 commit comments