config.go

  1package jira
  2
  3import (
  4	"bufio"
  5	"encoding/json"
  6	"fmt"
  7	"io/ioutil"
  8	"os"
  9	"strconv"
 10	"strings"
 11	"time"
 12
 13	"github.com/pkg/errors"
 14
 15	"github.com/MichaelMure/git-bug/bridge/core"
 16	"github.com/MichaelMure/git-bug/input"
 17	"github.com/MichaelMure/git-bug/repository"
 18)
 19
 20const (
 21	target             = "jira"
 22	keyServer          = "server"
 23	keyProject         = "project"
 24	keyCredentialsFile = "credentials-file"
 25	keyUsername        = "username"
 26	keyPassword        = "password"
 27	keyIDMap           = "bug-id-map"
 28	keyCreateDefaults  = "create-issue-defaults"
 29	keyCreateGitBug    = "create-issue-gitbug-id"
 30
 31	defaultTimeout = 60 * time.Second
 32)
 33
 34const moreConfigText = `
 35NOTE: There are a few optional configuration values that you can additionally
 36set in your git configuration to influence the behavior of the bridge. Please
 37see the notes at:
 38https://github.com/MichaelMure/git-bug/blob/master/doc/jira_bridge.md
 39`
 40
 41// Configure sets up the bridge configuration
 42func (g *Jira) Configure(
 43	repo repository.RepoCommon, params core.BridgeParams) (
 44	core.Configuration, error) {
 45	conf := make(core.Configuration)
 46	var err error
 47	var url string
 48	var project string
 49	var credentialsFile string
 50	var username string
 51	var password string
 52	var serverURL string
 53
 54	if params.Token != "" || params.TokenStdin {
 55		return nil, fmt.Errorf(
 56			"JIRA session tokens are extremely short lived. We don't store them " +
 57				"in the configuration, so they are not valid for this bridge.")
 58	}
 59
 60	if params.Owner != "" {
 61		return nil, fmt.Errorf("owner doesn't make sense for jira")
 62	}
 63
 64	serverURL = params.URL
 65	if url == "" {
 66		// terminal prompt
 67		serverURL, err = prompt("JIRA server URL", "URL")
 68		if err != nil {
 69			return nil, err
 70		}
 71	}
 72
 73	project = params.Project
 74	if project == "" {
 75		project, err = prompt("JIRA project key", "project")
 76		if err != nil {
 77			return nil, err
 78		}
 79	}
 80
 81	choice, err := promptCredentialOptions(serverURL)
 82	if err != nil {
 83		return nil, err
 84	}
 85
 86	if choice == 1 {
 87		credentialsFile, err = prompt("Credentials file path", "path")
 88		if err != nil {
 89			return nil, err
 90		}
 91	}
 92
 93	username, err = prompt("JIRA username", "username")
 94	if err != nil {
 95		return nil, err
 96	}
 97
 98	password, err = input.PromptPassword()
 99	if err != nil {
100		return nil, err
101	}
102
103	jsonData, err := json.Marshal(
104		&SessionQuery{Username: username, Password: password})
105	if err != nil {
106		return nil, err
107	}
108
109	fmt.Printf("Attempting to login with credentials...\n")
110	client := NewClient(serverURL, nil)
111	err = client.RefreshTokenRaw(jsonData)
112	if err != nil {
113		return nil, err
114	}
115
116	// verify access to the project with credentials
117	_, err = client.GetProject(project)
118	if err != nil {
119		return nil, fmt.Errorf(
120			"Project %s doesn't exist on %s, or authentication credentials for (%s)"+
121				" are invalid",
122			project, serverURL, username)
123	}
124
125	conf[core.KeyTarget] = target
126	conf[keyServer] = serverURL
127	conf[keyProject] = project
128	switch choice {
129	case 1:
130		conf[keyCredentialsFile] = credentialsFile
131		err = ioutil.WriteFile(credentialsFile, jsonData, 0644)
132		if err != nil {
133			return nil, errors.Wrap(
134				err, fmt.Sprintf("Unable to write credentials to %s", credentialsFile))
135		}
136	case 2:
137		conf[keyUsername] = username
138		conf[keyPassword] = password
139	case 3:
140		conf[keyUsername] = username
141	}
142
143	err = g.ValidateConfig(conf)
144	if err != nil {
145		return nil, err
146	}
147
148	fmt.Print(moreConfigText)
149	return conf, nil
150}
151
152// ValidateConfig returns true if all required keys are present
153func (*Jira) ValidateConfig(conf core.Configuration) error {
154	if v, ok := conf[core.KeyTarget]; !ok {
155		return fmt.Errorf("missing %s key", core.KeyTarget)
156	} else if v != target {
157		return fmt.Errorf("unexpected target name: %v", v)
158	}
159
160	if _, ok := conf[keyProject]; !ok {
161		return fmt.Errorf("missing %s key", keyProject)
162	}
163
164	return nil
165}
166
167const credentialsText = `
168How would you like to store your JIRA login credentials?
169[1]: sidecar JSON file: Your credentials will be stored in a JSON sidecar next
170     to your git config. Note that it will contain your JIRA password in clear
171     text.
172[2]: git-config: Your credentials will be stored in the git config. Note that
173     it will contain your JIRA password in clear text.
174[3]: username in config, askpass: Your username will be stored in the git
175     config. We will ask you for your password each time you execute the bridge.
176`
177
178func promptCredentialOptions(serverURL string) (int, error) {
179	fmt.Print(credentialsText)
180	for {
181		fmt.Print("Select option: ")
182
183		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
184		fmt.Println()
185		if err != nil {
186			return -1, err
187		}
188
189		line = strings.TrimRight(line, "\n")
190
191		index, err := strconv.Atoi(line)
192		if err != nil || (index != 1 && index != 2 && index != 3) {
193			fmt.Println("invalid input")
194			continue
195		}
196
197		return index, nil
198	}
199}
200
201func prompt(description, name string) (string, error) {
202	for {
203		fmt.Printf("%s: ", description)
204
205		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
206		if err != nil {
207			return "", err
208		}
209
210		line = strings.TrimRight(line, "\n")
211		if line == "" {
212			fmt.Printf("%s is empty\n", name)
213			continue
214		}
215
216		return line, nil
217	}
218}