1package jira
2
3import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7
8 "github.com/pkg/errors"
9
10 "github.com/MichaelMure/git-bug/bridge/core"
11 "github.com/MichaelMure/git-bug/cache"
12 "github.com/MichaelMure/git-bug/input"
13)
14
15const moreConfigText = `
16NOTE: There are a few optional configuration values that you can additionally
17set in your git configuration to influence the behavior of the bridge. Please
18see the notes at:
19https://github.com/MichaelMure/git-bug/blob/master/doc/jira_bridge.md
20`
21
22const credTypeText = `
23JIRA has recently altered it's authentication strategies. Servers deployed
24prior to October 1st 2019 must use "SESSION" authentication, whereby the REST
25client logs in with an actual username and password, is assigned a session, and
26passes the session cookie with each request. JIRA Cloud and servers deployed
27after October 1st 2019 must use "TOKEN" authentication. You must create a user
28API token and the client will provide this along with your username with each
29request.`
30
31// Configure sets up the bridge configuration
32func (g *Jira) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.Configuration, error) {
33 conf := make(core.Configuration)
34 conf[core.ConfigKeyTarget] = target
35
36 var err error
37
38 // if params.Token != "" || params.TokenStdin {
39 // return nil, fmt.Errorf(
40 // "JIRA session tokens are extremely short lived. We don't store them " +
41 // "in the configuration, so they are not valid for this bridge.")
42 // }
43
44 if params.Owner != "" {
45 fmt.Println("warning: --owner is ineffective for a Jira bridge")
46 }
47
48 serverURL := params.URL
49 if serverURL == "" {
50 // terminal prompt
51 serverURL, err = input.Prompt("JIRA server URL", "URL", input.Required)
52 if err != nil {
53 return nil, err
54 }
55 }
56 conf[keyServer] = serverURL
57
58 project := params.Project
59 if project == "" {
60 project, err = input.Prompt("JIRA project key", "project", input.Required)
61 if err != nil {
62 return nil, err
63 }
64 }
65 conf[keyProject] = project
66
67 fmt.Println(credTypeText)
68 credType, err := input.PromptChoice("Authentication mechanism", []string{"SESSION", "TOKEN"})
69 if err != nil {
70 return nil, err
71 }
72
73 switch credType {
74 case 1:
75 conf[keyCredentialsType] = "SESSION"
76 case 2:
77 conf[keyCredentialsType] = "TOKEN"
78 }
79
80 fmt.Println("How would you like to store your JIRA login credentials?")
81 credTargetChoice, err := input.PromptChoice("Credential storage", []string{
82 "sidecar JSON file: Your credentials will be stored in a JSON sidecar next" +
83 "to your git config. Note that it will contain your JIRA password in clear" +
84 "text.",
85 "git-config: Your credentials will be stored in the git config. Note that" +
86 "it will contain your JIRA password in clear text.",
87 "username in config, askpass: Your username will be stored in the git" +
88 "config. We will ask you for your password each time you execute the bridge.",
89 })
90 if err != nil {
91 return nil, err
92 }
93
94 username, err := input.Prompt("JIRA username", "username", input.Required)
95 if err != nil {
96 return nil, err
97 }
98
99 password, err := input.PromptPassword("Password", "password", input.Required)
100 if err != nil {
101 return nil, err
102 }
103
104 switch credTargetChoice {
105 case 1:
106 // TODO: a validator to see if the path is writable ?
107 credentialsFile, err := input.Prompt("Credentials file path", "path", input.Required)
108 if err != nil {
109 return nil, err
110 }
111 conf[keyCredentialsFile] = credentialsFile
112 jsonData, err := json.Marshal(&SessionQuery{Username: username, Password: password})
113 if err != nil {
114 return nil, err
115 }
116 err = ioutil.WriteFile(credentialsFile, jsonData, 0644)
117 if err != nil {
118 return nil, errors.Wrap(
119 err, fmt.Sprintf("Unable to write credentials to %s", credentialsFile))
120 }
121 case 2:
122 conf[keyUsername] = username
123 conf[keyPassword] = password
124 case 3:
125 conf[keyUsername] = username
126 }
127
128 err = g.ValidateConfig(conf)
129 if err != nil {
130 return nil, err
131 }
132
133 fmt.Printf("Attempting to login with credentials...\n")
134 client := NewClient(serverURL, nil)
135 err = client.Login(conf)
136 if err != nil {
137 return nil, err
138 }
139
140 // verify access to the project with credentials
141 fmt.Printf("Checking project ...\n")
142 _, err = client.GetProject(project)
143 if err != nil {
144 return nil, fmt.Errorf(
145 "Project %s doesn't exist on %s, or authentication credentials for (%s)"+
146 " are invalid",
147 project, serverURL, username)
148 }
149
150 fmt.Print(moreConfigText)
151 return conf, nil
152}
153
154// ValidateConfig returns true if all required keys are present
155func (*Jira) ValidateConfig(conf core.Configuration) error {
156 if v, ok := conf[core.ConfigKeyTarget]; !ok {
157 return fmt.Errorf("missing %s key", core.ConfigKeyTarget)
158 } else if v != target {
159 return fmt.Errorf("unexpected target name: %v", v)
160 }
161
162 if _, ok := conf[keyProject]; !ok {
163 return fmt.Errorf("missing %s key", keyProject)
164 }
165
166 return nil
167}