aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/jira/config.go
diff options
context:
space:
mode:
Diffstat (limited to 'bridge/jira/config.go')
-rw-r--r--bridge/jira/config.go239
1 files changed, 239 insertions, 0 deletions
diff --git a/bridge/jira/config.go b/bridge/jira/config.go
new file mode 100644
index 00000000..e33b8f28
--- /dev/null
+++ b/bridge/jira/config.go
@@ -0,0 +1,239 @@
+package jira
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/pkg/errors"
+ "golang.org/x/crypto/ssh/terminal"
+
+ "github.com/MichaelMure/git-bug/bridge/core"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util/interrupt"
+)
+
+const (
+ target = "jira"
+ keyServer = "server"
+ keyProject = "project"
+ keyCredentialsFile = "credentials-file"
+ keyUsername = "username"
+ keyPassword = "password"
+ keyMapOpenID = "bug-open-id"
+ keyMapCloseID = "bug-closed-id"
+ keyCreateDefaults = "create-issue-defaults"
+ keyCreateGitBug = "create-issue-gitbug-id"
+
+ defaultTimeout = 60 * time.Second
+)
+
+// Configure sets up the bridge configuration
+func (g *Jira) Configure(
+ repo repository.RepoCommon, params core.BridgeParams) (
+ core.Configuration, error) {
+ conf := make(core.Configuration)
+ var err error
+ var url string
+ var project string
+ var credentialsFile string
+ var username string
+ var password string
+ var serverURL string
+
+ if params.Token != "" || params.TokenStdin {
+ return nil, fmt.Errorf(
+ "JIRA session tokens are extremely short lived. We don't store them " +
+ "in the configuration, so they are not valid for this bridge.")
+ }
+
+ if params.Owner != "" {
+ return nil, fmt.Errorf("owner doesn't make sense for jira")
+ }
+
+ serverURL = params.URL
+ if url == "" {
+ // terminal prompt
+ serverURL, err = prompt("JIRA server URL", "URL")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ project = params.Project
+ if project == "" {
+ project, err = prompt("JIRA project key", "project")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ choice, err := promptCredentialOptions(serverURL)
+ if err != nil {
+ return nil, err
+ }
+
+ if choice == 1 {
+ credentialsFile, err = prompt("Credentials file path", "path")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ username, err = prompt("JIRA username", "username")
+ if err != nil {
+ return nil, err
+ }
+
+ password, err = PromptPassword()
+ if err != nil {
+ return nil, err
+ }
+
+ jsonData, err := json.Marshal(
+ &SessionQuery{Username: username, Password: password})
+ if err != nil {
+ return nil, err
+ }
+
+ fmt.Printf("Attempting to login with credentials...\n")
+ client := NewClient(serverURL, nil)
+ err = client.RefreshTokenRaw(jsonData)
+
+ // verify access to the project with credentials
+ _, err = client.GetProject(project)
+ if err != nil {
+ return nil, fmt.Errorf(
+ "Project %s doesn't exist on %s, or authentication credentials for (%s)"+
+ " are invalid",
+ project, serverURL, username)
+ }
+
+ conf[core.KeyTarget] = target
+ conf[keyServer] = serverURL
+ conf[keyProject] = project
+ if choice == 1 {
+ conf[keyCredentialsFile] = credentialsFile
+ err = ioutil.WriteFile(credentialsFile, jsonData, 0644)
+ if err != nil {
+ return nil, errors.Wrap(
+ err, fmt.Sprintf("Unable to write credentials to %s", credentialsFile))
+ }
+ } else if choice == 2 {
+ conf[keyUsername] = username
+ conf[keyPassword] = password
+ } else if choice == 3 {
+ conf[keyUsername] = username
+ }
+ err = g.ValidateConfig(conf)
+ if err != nil {
+ return nil, err
+ }
+
+ return conf, nil
+}
+
+// ValidateConfig returns true if all required keys are present
+func (*Jira) ValidateConfig(conf core.Configuration) error {
+ if v, ok := conf[core.KeyTarget]; !ok {
+ return fmt.Errorf("missing %s key", core.KeyTarget)
+ } else if v != target {
+ return fmt.Errorf("unexpected target name: %v", v)
+ }
+
+ if _, ok := conf[keyProject]; !ok {
+ return fmt.Errorf("missing %s key", keyProject)
+ }
+
+ return nil
+}
+
+const credentialsText = `
+How would you like to store your JIRA login credentials?
+[1]: sidecar JSON file: Your credentials will be stored in a JSON sidecar next
+ to your git config. Note that it will contain your JIRA password in clear
+ text.
+[2]: git-config: Your credentials will be stored in the git config. Note that
+ it will contain your JIRA password in clear text.
+[3]: username in config, askpass: Your username will be stored in the git
+ config. We will ask you for your password each time you execute the bridge.
+`
+
+func promptCredentialOptions(serverURL string) (int, error) {
+ fmt.Print(credentialsText)
+ for {
+ fmt.Print("Select option: ")
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ fmt.Println()
+ if err != nil {
+ return -1, err
+ }
+
+ line = strings.TrimRight(line, "\n")
+
+ index, err := strconv.Atoi(line)
+ if err != nil || (index != 1 && index != 2 && index != 3) {
+ fmt.Println("invalid input")
+ continue
+ }
+
+ return index, nil
+ }
+}
+
+func prompt(description, name string) (string, error) {
+ for {
+ fmt.Printf("%s: ", description)
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+
+ line = strings.TrimRight(line, "\n")
+ if line == "" {
+ fmt.Printf("%s is empty\n", name)
+ continue
+ }
+
+ return line, nil
+ }
+}
+
+// PromptPassword performs interactive input collection to get the user password
+func PromptPassword() (string, error) {
+ termState, err := terminal.GetState(int(syscall.Stdin))
+ if err != nil {
+ return "", err
+ }
+
+ cancel := interrupt.RegisterCleaner(func() error {
+ return terminal.Restore(int(syscall.Stdin), termState)
+ })
+ defer cancel()
+
+ for {
+ fmt.Print("password: ")
+ bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
+ // new line for coherent formatting, ReadPassword clip the normal new line
+ // entered by the user
+ fmt.Println()
+
+ if err != nil {
+ return "", err
+ }
+
+ if len(bytePassword) > 0 {
+ return string(bytePassword), nil
+ }
+
+ fmt.Println("password is empty")
+ }
+}