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

Stop using PATs for Azure Repos (Attempt 2: Electric Boogaloo) #294

Merged
merged 17 commits into from
Mar 10, 2021
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
215 changes: 215 additions & 0 deletions docs/azrepos-users-and-tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Azure Repos: Access tokens and Accounts

## Different credential types

The Azure Repos host provider supports creating multiple types of credential:

- Azure DevOps personal access tokens
- Microsoft identity OAuth tokens (experimental)

### Azure DevOps personal access tokens

Historically, the only option supported by the Azure Repos host provider was
Azure DevOps Personal Access Tokens (PATs).

These PATs are only used by Azure DevOps, and must be [managed through the Azure
DevOps user settings page](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page).

PATs have a limited lifetime and new tokens must be created once they expire. In
Git Credential Manager, when a PAT expired (or was manually revoked) this
resulted in a new authentication prompt.

### Microsoft identity OAuth tokens (experimental)

"Microsoft identity OAuth token" is the generic term for OAuth-based access
tokens issued by Azure Active Directory for either Work and School Accounts
(AAD tokens) or Personal Accounts (Microsoft Account/MSA tokens).

Azure DevOps supports Git authentication using Microsoft identity OAuth tokens
as well as PATs. Microsoft identity OAuth tokens created by Git Credential
Manager are scoped to Azure DevOps only.

Unlike PATs, Microsoft identity OAuth tokens get automatically refreshed and
renewed as long as you are actively using them to perform Git operations.

These tokens are also securely shared with other Microsoft developer tools
including the Visual Studio IDE and Azure CLI. This means that as long as you're
using Git or one of these tools with the same account, you'll never need to
re-authenticate due to expired tokens!

#### User accounts

In versions of Git Credential Manager that support Microsoft identity OAuth
tokens, the user account used to authenticate for a particular Azure DevOps
organization will now be remembered.

The first time you clone, fetch or push from/to an Azure DevOps organization you
will be prompted to sign-in and select a user account. Git Credential Manager
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know on the Azure DevOps side, they say "identity" for the thing that represents a user. Genuine question, is that the same thing as "account" here (and if so, should we use their terminology)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can say "identity" if we want to be consistent with Azure DevOps.

In AAD there are users and accounts (an account != user.. one can span tenants and clouds, the other is in a particular cloud+tenant).

In Git there is only a "username".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess technically, you're selecting an Azure account when you sign-in, and not an Azure DevOps identity.. Just happens that Azure accounts are mapped to an AzDO identity.

Aside: there's also a FUN thing that if you've never signed-in to the web front-end of a particular Azure DevOps organisation, you cannot call any AzDevOps APIs because the "identity" doesn't exist yet - it's only materialised on first sign-in on the web. 🙄

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you've never signed-in to the web front-end of a particular Azure DevOps organisation, you cannot call any AzDevOps APIs

Let me know if you are seeing this - it means that someone forgot to turn on a feature flag.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was the case back when GCM Core was first out. I remember hitting this exact issue and having to include support for surfacing human readable error messages (telling you do go log in via the web first) from Azure DevOps' REST API endpoints.

will remember which account you used and continue to use that for all future
remote Git operations (clone/fetch/push). An account is said to be "bound" to
an Azure DevOps organization.

---

**Note:** If GCM is set to use PAT credentials, this account will **NOT** be
used and you will continue to be prompted to select a user account to renew the
credential. This may change in the future.

---

Normally you won't need to worry about managing which user accounts Git
Credential Manager is using as this is configured automatically when you first
authenticate for a particular Azure DevOps organziation.

In advanced scenarios (such as using multiple accounts) you can interact with
and manage remembered user accounts using the 'azure-repos' provider command:

```shell
git-credential-manager-core azure-repos [ list | bind | unbind | ... ] <options>
```

##### Listing remembered accounts

You can list all bound user accounts by Git Credential Manager for each Azure
DevOps organization using the `list` command:

```shell
$ git-credential-manager-core azure-repos list
contoso:
(global) -> [email protected]
fabrikam:
(global) -> [email protected]
```

In the above example, the `contoso` Azure DevOps organization is associated with
the `[email protected]` user account, while the `fabrikam` organization is
associated to the `[email protected]` user account.

Global "bindings" apply to all remote Git operations for the current computer
user profile and are stored in `~/.gitconfig` or `%USERPROFILE%\.gitconfig`.

##### Using different accounts within a repository

If you generally use one account for an Azure DevOps organization, the default
global bindings will be sufficient. However, if you wish to use a different
user account for an organization in a particular repository you can use a local
binding.

Local account bindings only apply within a single repository and are stored in
the `.git/config` file. If there are local bindings in a repository you can show
them with the `list` command:

```shell
~/myrepo$ git-credential-manager-core azure-repos list
contoso:
(global) -> [email protected]
(local) -> [email protected]
```

Within the `~/myrepo` repository, the `[email protected]` account will be
used by Git and GCM for the `contoso` Azure DevOps organization.

To create a local binding, use the `bind` command with the `--local` option when
inside a repository:

```shell
~/myrepo$ git-credential-manager-core azure-repos bind --local contoso [email protected]
```

```diff
contoso:
(global) -> [email protected]
+ (local) -> [email protected]
```

##### Forget an account

To have Git Credential Manager forget a user account, use the `unbind` command:

```shell
git-credential-manager-core azure-repos unbind fabrikam
```

```diff
contoso:
(global) -> [email protected]
- fabrikam:
- (global) -> [email protected]
```

In the above example, and global account binding for the `fabrikam` organization
will be forgotten. The next time you need to renew a PAT (if using PATs) or
perform any remote Git operation (is using Azure tokens) you will be prompted
to authenticate again.

To forget or remove a local binding, within the repository run the `unbind`
command with the `--local` option:

```shell
~/myrepo$ git-credential-manager-core azure-repos unbind --local contoso
```

```diff
contoso:
(global) -> [email protected]
- (local) -> [email protected]
```

##### Using different accounts for specific Git remotes

As well as global and local user account bindings, you can instruct Git
Credential Manager to use a specific user account for an individual Git remotes
within the same local repository.

To show which accounts are being used for each Git remote in a repository use
the `list` command with the `--show-remotes` option:

```shell
~/myrepo$ git-credential-manager-core azure-repos list --show-remotes
contoso:
(global) -> [email protected]
origin:
(fetch) -> (inherit)
(push) -> (inherit)
fabrikam:
(global) -> [email protected]
```

In the above example, the `~/myrepo` repository has a single Git remote named
`origin` that points to the `contoso` Azure DevOps organziation. There is no
user account specifically associated with the `origin` remote, so the global
user account binding for `contoso` will be used (the global binding is
inherited).

To associate a user account with a particular Git remote you must manually edit
the remote URL using `git config` commands to include the username in the
[user information](https://tools.ietf.org/html/rfc3986#section-3.2.1) part of
the URL.

```shell
git config --local remote.origin.url https://alice-alt%[email protected]/project/_git/repo
```

In the above example the `[email protected]` account is being set as the
account to use for the `origin` Git remote.

---

**Note:** All special characters must be URL encoded/escaped, for example `@`
becomes `%40`.

---

The `list --show-remotes` command will show the user account specified in the
remote URL:

```shell
~/myrepo$ git-credential-manager-core azure-repos list --show-remotes
contoso:
(global) -> [email protected]
origin:
(fetch) -> [email protected]
(push) -> [email protected]
fabrikam:
(global) -> [email protected]
```
23 changes: 23 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,26 @@ Credential: "git:https://[email protected]/example/myrepo" (user = bob)

https://[email protected]/example/myrepo
```

---

### credential.azreposCredentialType _(experimental)_
mjcheetham marked this conversation as resolved.
Show resolved Hide resolved

Specify the type of credential the Azure Repos host provider should return.

Defaults to the value `pat`.

Value|Description
-|-
`pat` _(default)_|Azure DevOps personal access tokens
`oauth`|Microsoft identity OAuth tokens (AAD or MSA tokens)

More information about Azure Access tokens can be found [here](azrepos-azuretokens.md).

#### Example

```shell
git config --global credential.azreposCredentialType oauth
```

**Also see: [GCM_AZREPOS_CREDENTIALTYPE](environment.md#GCM_AZREPOS_CREDENTIALTYPE-experimental)**
29 changes: 29 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,32 @@ export GCM_MSAUTH_FLOW="devicecode"
```

**Also see: [credential.msauthFlow](configuration.md#credentialmsauthflow)**

---

### GCM_AZREPOS_CREDENTIALTYPE _(experimental)_

Specify the type of credential the Azure Repos host provider should return.

Defaults to the value `pat`.

Value|Description
-|-
`pat` _(default)_|Azure DevOps personal access tokens
`oauth`|Microsoft identity OAuth tokens (AAD or MSA tokens)

More information about Azure Access tokens can be found [here](azrepos-azuretokens.md).

##### Windows

```batch
SET GCM_AZREPOS_CREDENTIALTYPE="oauth"
```

##### macOS/Linux

```bash
export GCM_AZREPOS_CREDENTIALTYPE="oauth"
```

**Also see: [credential.azreposCredentialType](configuration.md#azreposcredentialtype-experimental)**
7 changes: 7 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ Read the [Git manual](https://git-scm.com/docs/gitcredentials#_custom_helpers) a
Set your user-level Git configuration (`~/.gitconfig`) to use GCM Core. If you pass
`--system` to these commands, they act on the system-level Git configuration
(`/etc/gitconfig`) instead.

### azure-repos (experimental)

Interact with the Azure Repos host provider to bind/unbind user accounts to Azure DevOps
organizations or specific remote URLs, and manage the authentication authority cache.

For more information about managing user account bindings see [here](azrepos-users-and-tokens.md#useraccounts).
5 changes: 4 additions & 1 deletion src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public bool IsSupported(InputArguments input)
}

// Split port number and hostname from host input argument
input.TryGetHostAndPort(out string hostName, out _);
if (!input.TryGetHostAndPort(out string hostName, out _))
{
return false;
}

// We do not support unencrypted HTTP communications to Bitbucket,
// but we report `true` here for HTTP so that we can show a helpful
Expand Down
5 changes: 4 additions & 1 deletion src/shared/GitHub/GitHubHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ public override bool IsSupported(InputArguments input)
}

// Split port number and hostname from host input argument
input.TryGetHostAndPort(out string hostName, out _);
if (!input.TryGetHostAndPort(out string hostName, out _))
{
return false;
}

if (StringComparer.OrdinalIgnoreCase.Equals(hostName, GitHubConstants.GitHubBaseUrlHost) ||
StringComparer.OrdinalIgnoreCase.Equals(hostName, GitHubConstants.GistBaseUrlHost))
Expand Down
Loading