Skip to content

Commit

Permalink
Merge pull request #162 from mjcheetham/newcred2
Browse files Browse the repository at this point in the history
Revamp credential storage system to allow for smarter searching
  • Loading branch information
mjcheetham committed Sep 17, 2020
2 parents f93dee0 + fd6df7c commit c883429
Show file tree
Hide file tree
Showing 50 changed files with 2,071 additions and 873 deletions.
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

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

0 comments on commit c883429

Please sign in to comment.