1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
// Package auth is a set of functions for retrieving authentication tokens
// and authenticated hosts.
package auth
import (
"os"
"os/exec"
"strconv"
"strings"
"github.com/cli/go-gh/internal/set"
"github.com/cli/go-gh/pkg/config"
"github.com/cli/safeexec"
)
const (
codespaces = "CODESPACES"
defaultSource = "default"
ghEnterpriseToken = "GH_ENTERPRISE_TOKEN"
ghHost = "GH_HOST"
ghToken = "GH_TOKEN"
github = "github.com"
githubEnterpriseToken = "GITHUB_ENTERPRISE_TOKEN"
githubToken = "GITHUB_TOKEN"
hostsKey = "hosts"
localhost = "github.localhost"
oauthToken = "oauth_token"
)
// TokenForHost retrieves an authentication token and the source of that token for the specified
// host. The source can be either an environment variable, configuration file, or the system
// keyring. In the latter case, this shells out to "gh auth token" to obtain the token.
//
// Returns "", "default" if no applicable token is found.
func TokenForHost(host string) (string, string) {
if token, source := TokenFromEnvOrConfig(host); token != "" {
return token, source
}
if ghExe, err := safeexec.LookPath("gh"); err == nil {
if token, source := tokenFromGh(ghExe, host); token != "" {
return token, source
}
}
return "", defaultSource
}
// TokenFromEnvOrConfig retrieves an authentication token from environment variables or the config
// file as fallback, but does not support reading the token from system keyring. Most consumers
// should use TokenForHost.
func TokenFromEnvOrConfig(host string) (string, string) {
cfg, _ := config.Read()
return tokenForHost(cfg, host)
}
func tokenForHost(cfg *config.Config, host string) (string, string) {
host = normalizeHostname(host)
if isEnterprise(host) {
if token := os.Getenv(ghEnterpriseToken); token != "" {
return token, ghEnterpriseToken
}
if token := os.Getenv(githubEnterpriseToken); token != "" {
return token, githubEnterpriseToken
}
if isCodespaces, _ := strconv.ParseBool(os.Getenv(codespaces)); isCodespaces {
if token := os.Getenv(githubToken); token != "" {
return token, githubToken
}
}
if cfg != nil {
token, _ := cfg.Get([]string{hostsKey, host, oauthToken})
return token, oauthToken
}
}
if token := os.Getenv(ghToken); token != "" {
return token, ghToken
}
if token := os.Getenv(githubToken); token != "" {
return token, githubToken
}
if cfg != nil {
token, _ := cfg.Get([]string{hostsKey, host, oauthToken})
return token, oauthToken
}
return "", defaultSource
}
func tokenFromGh(path string, host string) (string, string) {
cmd := exec.Command(path, "auth", "token", "--secure-storage", "--hostname", host)
result, err := cmd.Output()
if err != nil {
return "", "gh"
}
return strings.TrimSpace(string(result)), "gh"
}
// KnownHosts retrieves a list of hosts that have corresponding
// authentication tokens, either from environment variables
// or from the configuration file.
// Returns an empty string slice if no hosts are found.
func KnownHosts() []string {
cfg, _ := config.Read()
return knownHosts(cfg)
}
func knownHosts(cfg *config.Config) []string {
hosts := set.NewStringSet()
if host := os.Getenv(ghHost); host != "" {
hosts.Add(host)
}
if token, _ := tokenForHost(cfg, github); token != "" {
hosts.Add(github)
}
if cfg != nil {
keys, err := cfg.Keys([]string{hostsKey})
if err == nil {
hosts.AddValues(keys)
}
}
return hosts.ToSlice()
}
// DefaultHost retrieves an authenticated host and the source of host.
// The source can be either an environment variable or from the
// configuration file.
// Returns "github.com", "default" if no viable host is found.
func DefaultHost() (string, string) {
cfg, _ := config.Read()
return defaultHost(cfg)
}
func defaultHost(cfg *config.Config) (string, string) {
if host := os.Getenv(ghHost); host != "" {
return host, ghHost
}
if cfg != nil {
keys, err := cfg.Keys([]string{hostsKey})
if err == nil && len(keys) == 1 {
return keys[0], hostsKey
}
}
return github, defaultSource
}
func isEnterprise(host string) bool {
return host != github && host != localhost
}
func normalizeHostname(host string) string {
hostname := strings.ToLower(host)
if strings.HasSuffix(hostname, "."+github) {
return github
}
if strings.HasSuffix(hostname, "."+localhost) {
return localhost
}
return hostname
}
|