Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp credential storage system to allow for smarter searching #162

Merged
merged 16 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,25 @@ directly implement the interface they can also derive from the `HostProvider`
abstract class (which itself implements the `IHostProvider` interface).

The `HostProvider` abstract class implements the
`Get|Store|EraseCredentialAsync` methods and instead has a
`GenerateCredentialAsync` and `GetCredentialKey` abstract methods. Calls to
`get`, `store`, or `erase` result in first a call to `GetCredentialKey` which
should return a stable and 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 for an existing credential with the computed key.
`Get|Store|EraseCredentialAsync` methods and instead has the
`GenerateCredentialAsync` abstract method, and the `GetServiceName` virtual
method. Calls to `get`, `store`, or `erase` result in first a call to
`GetServiceName` which should return a stable and unique value for the provider
and request. This value forms part of the attributes associated with any stored
credential in the credential store. During a `get` operation the
credential store is queried for an existing credential with such service name.
If a credential is found it is returned immediately. Similarly, calls to `store`
and `erase` are handles automatically to store credentials against, and erase
credentials matching the computed key. Methods are implemented as `virtual`
credentials matching the service name. Methods are implemented as `virtual`
meaning you can always override this behaviour, for example to clear other
custom caches on an `erase` request, without having to reimplement the
lookup/store credential logic.

The default implementation of `GetServiceName` is usually sufficient for most
providers. It returns the computed remote URL (without a trailing slash) from
the input arguments from Git - `<protocol>://<host>[/<path>]` - no username is
included even if present.

Host providers are queried in turn (registration order) via the
`IHostProvider.IsSupported` method and passed the input received from Git. If
the provider recognises the request, for example by a matching known host name,
Expand Down
17 changes: 17 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,20 @@ git config --global credential.gitHubAuthModes "oauth basic"
```

**Also see: [GCM_GITHUB_AUTHMODES](environment.md#GCM_GITHUB_AUTHMODES)**

---

### credential.namespace
derrickstolee marked this conversation as resolved.
Show resolved Hide resolved

Use a custom namespace prefix for credentials read and written in the OS credential store.
Credentials will be stored in the format `{namespace}:{service}`.

Defaults to the value `git`.

#### Example

```shell
git config --global credential.namespace "my-namespace"
```

**Also see: [GCM_NAMESPACE](environment.md#GCM_NAMESPACE)**
23 changes: 23 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,26 @@ export GCM_GITHUB_AUTHMODES="oauth basic"
```

**Also see: [credential.gitHubAuthModes](configuration.md#credentialgitHubAuthModes)**

---

### GCM_NAMESPACE

Use a custom namespace prefix for credentials read and written in the OS credential store.
Credentials will be stored in the format `{namespace}:{service}`.

Defaults to the value `git`.

##### Windows

```batch
SET GCM_NAMESPACE="my-namespace"
```

##### macOS/Linux

```bash
export GCM_NAMESPACE="my-namespace"
```

**Also see: [credential.namespace](configuration.md#credentialnamespace)**
40 changes: 24 additions & 16 deletions docs/hostprovider.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
Property|Value
-|-
Author(s)|Matthew John Cheetham ([@mjcheetham](https://github.com/mjcheetham))
Revision|1.0
Last updated|2020-06-22
Revision|1.1
Last updated|2020-09-04

## Revision Summary

- 1.0. Initial revision.
- 1.1. Replaced `GetCredentialKey` with `GetServiceName`.

## Abstract

Expand All @@ -28,7 +33,7 @@ authentication to secured Git repositories by implementing and registering a
- [2.4. Storing Credentials](#24-storing-credentials)
- [2.5. Erasing Credentials](#25-erasing-credentials)
- [2.6 `HostProvider` base class](#26-hostprovider-base-class)
- [2.6.1 `GetCredentialKey`](#261-getcredentialkey)
- [2.6.1 `GetServiceName`](#261-getservicename)
- [2.6.2 `GenerateCredentialAsync`](#262-generatecredentialasync)
- [2.7. External Metadata](#27-external-metadata)
- [3. Helpers](#3-helpers)
Expand Down Expand Up @@ -222,7 +227,7 @@ Host providers MAY store multiple credentials or tokens in the same request if
it is required. One example where multiple credential storage is needed is with
OAuth2 access tokens (AT) and refresh tokens (RT). Both the AT and RT SHOULD be
stored in the same location using the credential store with complementary
credential keys.
credential service names.

### 2.5. Erasing Credentials

Expand Down Expand Up @@ -256,26 +261,29 @@ most of the methods as implemented - implementors SHOULD implement the
`IHostProvider` interface directly instead.

Implementors that choose to derive from this base class MUST implement all
abstract methods and properties. The two primary abstract methods to implement
are `GetCredentialKey` and `GenerateCredentialAsync`.
abstract methods and properties. The primary abstract method to implement
is `GenerateCredentialAsync`.

#### 2.6.1 `GetCredentialKey`
There is also an additional `virtual` method named `GetServiceName` that is used
by the default implementations of the `Get|Store|EraseCredentialAsync` methods
to locate and store credentials.

The `GetCredentialKey` method MUST return a string that forms a key for storing
credentials for this provider and request. The key returned MUST be stable -
i.e, it MUST return the same value given the same or equivalent input arguments.
#### 2.6.1 `GetServiceName`

This key is used by the `GetCredentialAsync` method to first check for any
existing credential stored in the credential store, returning it if found.
The `GetServiceName` virtual method, if overriden, MUST return a string that
identifies the service/provider for this request, and is used for storing
credentials. The value returned MUST be stable - i.e, it MUST return the same
value given the same or equivalent input arguments.

The key is also similarly used by the `StoreCredentialAsync` and
`EraseCredentialAsync` methods to store and erase, respectively, credentials
passed as arguments by Git.
By default this method returns the full remote URI, without a trailing slash,
including protocol/scheme, hostname, and path if present in the input arguments.
Any username in the input arguments is never included in the URI.

#### 2.6.2 `GenerateCredentialAsync`

The `GenerateCredentialAsync` method will be called if an existing credential
with a matching credential key is not found in the credential store.
with a matching service (from `GetServiceName`) and account is not found in the
credential store.

This method MUST return a freshly created/generated credential and not any
existing or stored one. It MAY use existing or stored ancillary data or tokens,
Expand Down
Loading