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}