Skip to content

Commit df22473

Browse files
authored
Merge pull request #137 from mjcheetham/devguide
Add Architecture overview document to help developers
2 parents f8ac4e4 + e5889e3 commit df22473

File tree

1 file changed

+279
-0
lines changed

1 file changed

+279
-0
lines changed

docs/architecture.md

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

0 commit comments

Comments
 (0)