github.com/echohead/hub@v2.2.1+incompatible/github/config.go (about) 1 package github 2 3 import ( 4 "bufio" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/user" 9 "path/filepath" 10 "strconv" 11 12 "github.com/github/hub/Godeps/_workspace/src/github.com/howeyc/gopass" 13 "github.com/github/hub/ui" 14 "github.com/github/hub/utils" 15 ) 16 17 var defaultConfigsFile string 18 19 func init() { 20 homeDir := os.Getenv("HOME") 21 22 if homeDir == "" { 23 if u, err := user.Current(); err == nil { 24 homeDir = u.HomeDir 25 } 26 } 27 28 if homeDir == "" { 29 utils.Check(fmt.Errorf("Can't get current user's home dir")) 30 } 31 32 defaultConfigsFile = filepath.Join(homeDir, ".config", "hub") 33 } 34 35 type yamlHost struct { 36 User string `yaml:"user"` 37 OAuthToken string `yaml:"oauth_token"` 38 Protocol string `yaml:"protocol"` 39 } 40 41 type yamlConfig map[string][]yamlHost 42 43 type Host struct { 44 Host string `toml:"host"` 45 User string `toml:"user"` 46 AccessToken string `toml:"access_token"` 47 Protocol string `toml:"protocol"` 48 } 49 50 type Config struct { 51 Hosts []Host `toml:"hosts"` 52 } 53 54 func (c *Config) PromptForHost(host string) (h *Host, err error) { 55 h = c.Find(host) 56 if h != nil { 57 return 58 } 59 60 user := c.PromptForUser(host) 61 pass := c.PromptForPassword(host, user) 62 63 client := NewClient(host) 64 var code, token string 65 for { 66 token, err = client.FindOrCreateToken(user, pass, code) 67 if err == nil { 68 break 69 } 70 71 if ae, ok := err.(*AuthError); ok && ae.IsRequired2FACodeError() { 72 if code != "" { 73 ui.Errorln("warning: invalid two-factor code") 74 } 75 code = c.PromptForOTP() 76 } else { 77 break 78 } 79 } 80 81 if err != nil { 82 return 83 } 84 85 client.Host.AccessToken = token 86 currentUser, err := client.CurrentUser() 87 if err != nil { 88 return 89 } 90 91 h = &Host{ 92 Host: host, 93 User: currentUser.Login, 94 AccessToken: token, 95 Protocol: "https", 96 } 97 c.Hosts = append(c.Hosts, *h) 98 err = newConfigService().Save(configsFile(), c) 99 100 return 101 } 102 103 func (c *Config) PromptForUser(host string) (user string) { 104 user = os.Getenv("GITHUB_USER") 105 if user != "" { 106 return 107 } 108 109 ui.Printf("%s username: ", host) 110 user = c.scanLine() 111 112 return 113 } 114 115 func (c *Config) PromptForPassword(host, user string) (pass string) { 116 pass = os.Getenv("GITHUB_PASSWORD") 117 if pass != "" { 118 return 119 } 120 121 ui.Printf("%s password for %s (never stored): ", host, user) 122 if isTerminal(os.Stdout.Fd()) { 123 pass = string(gopass.GetPasswd()) 124 } else { 125 pass = c.scanLine() 126 } 127 128 return 129 } 130 131 func (c *Config) PromptForOTP() string { 132 fmt.Print("two-factor authentication code: ") 133 return c.scanLine() 134 } 135 136 func (c *Config) scanLine() string { 137 var line string 138 scanner := bufio.NewScanner(os.Stdin) 139 if scanner.Scan() { 140 line = scanner.Text() 141 } 142 utils.Check(scanner.Err()) 143 144 return line 145 } 146 147 func (c *Config) Find(host string) *Host { 148 for _, h := range c.Hosts { 149 if h.Host == host { 150 return &h 151 } 152 } 153 154 return nil 155 } 156 157 func (c *Config) selectHost() *Host { 158 options := len(c.Hosts) 159 160 if options == 1 { 161 return &c.Hosts[0] 162 } 163 164 prompt := "Select host:\n" 165 for idx, host := range c.Hosts { 166 prompt += fmt.Sprintf(" %d. %s\n", idx+1, host.Host) 167 } 168 prompt += fmt.Sprint("> ") 169 170 ui.Printf(prompt) 171 index := c.scanLine() 172 i, err := strconv.Atoi(index) 173 if err != nil || i < 1 || i > options { 174 utils.Check(fmt.Errorf("Error: must enter a number [1-%d]", options)) 175 } 176 177 return &c.Hosts[i-1] 178 } 179 180 func configsFile() string { 181 configsFile := os.Getenv("HUB_CONFIG") 182 if configsFile == "" { 183 configsFile = defaultConfigsFile 184 } 185 186 return configsFile 187 } 188 189 func CurrentConfig() *Config { 190 c := &Config{} 191 newConfigService().Load(configsFile(), c) 192 193 return c 194 } 195 196 func (c *Config) DefaultHost() (host *Host, err error) { 197 if GitHubHostEnv != "" { 198 host, err = c.PromptForHost(GitHubHostEnv) 199 } else if len(c.Hosts) > 0 { 200 host = c.selectHost() 201 } else { 202 host, err = c.PromptForHost(DefaultGitHubHost()) 203 } 204 205 return 206 } 207 208 // Public for testing purpose 209 func CreateTestConfigs(user, token string) *Config { 210 f, _ := ioutil.TempFile("", "test-config") 211 defaultConfigsFile = f.Name() 212 213 host := Host{ 214 User: "jingweno", 215 AccessToken: "123", 216 Host: GitHubHost, 217 } 218 219 c := &Config{Hosts: []Host{host}} 220 err := newConfigService().Save(f.Name(), c) 221 if err != nil { 222 panic(err) 223 } 224 225 return c 226 }