config.go

  1package jira
  2
  3import (
  4	"context"
  5	"fmt"
  6
  7	"github.com/MichaelMure/git-bug/bridge/core"
  8	"github.com/MichaelMure/git-bug/bridge/core/auth"
  9	"github.com/MichaelMure/git-bug/cache"
 10	"github.com/MichaelMure/git-bug/input"
 11	"github.com/MichaelMure/git-bug/repository"
 12)
 13
 14const moreConfigText = `
 15NOTE: There are a few optional configuration values that you can additionally
 16set in your git configuration to influence the behavior of the bridge. Please
 17see the notes at:
 18https://github.com/MichaelMure/git-bug/blob/master/doc/jira_bridge.md
 19`
 20
 21const credTypeText = `
 22JIRA has recently altered it's authentication strategies. Servers deployed
 23prior to October 1st 2019 must use "SESSION" authentication, whereby the REST
 24client logs in with an actual username and password, is assigned a session, and
 25passes the session cookie with each request. JIRA Cloud and servers deployed
 26after October 1st 2019 must use "TOKEN" authentication. You must create a user
 27API token and the client will provide this along with your username with each
 28request.`
 29
 30func (*Jira) ValidParams() map[string]interface{} {
 31	return map[string]interface{}{
 32		"BaseURL":    nil,
 33		"Login":      nil,
 34		"CredPrefix": nil,
 35		"Project":    nil,
 36	}
 37}
 38
 39// Configure sets up the bridge configuration
 40func (j *Jira) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.Configuration, error) {
 41	var err error
 42
 43	baseURL := params.BaseURL
 44	if baseURL == "" {
 45		// terminal prompt
 46		baseURL, err = input.Prompt("JIRA server URL", "URL", input.Required, input.IsURL)
 47		if err != nil {
 48			return nil, err
 49		}
 50	}
 51
 52	project := params.Project
 53	if project == "" {
 54		project, err = input.Prompt("JIRA project key", "project", input.Required)
 55		if err != nil {
 56			return nil, err
 57		}
 58	}
 59
 60	fmt.Println(credTypeText)
 61	credTypeInput, err := input.PromptChoice("Authentication mechanism", []string{"SESSION", "TOKEN"})
 62	if err != nil {
 63		return nil, err
 64	}
 65	credType := []string{"SESSION", "TOKEN"}[credTypeInput]
 66
 67	var login string
 68	var cred auth.Credential
 69
 70	switch {
 71	case params.CredPrefix != "":
 72		cred, err = auth.LoadWithPrefix(repo, params.CredPrefix)
 73		if err != nil {
 74			return nil, err
 75		}
 76		l, ok := cred.GetMetadata(auth.MetaKeyLogin)
 77		if !ok {
 78			return nil, fmt.Errorf("credential doesn't have a login")
 79		}
 80		login = l
 81	default:
 82		login := params.Login
 83		if login == "" {
 84			// TODO: validate username
 85			login, err = input.Prompt("JIRA login", "login", input.Required)
 86			if err != nil {
 87				return nil, err
 88			}
 89		}
 90		cred, err = promptCredOptions(repo, login, baseURL)
 91		if err != nil {
 92			return nil, err
 93		}
 94	}
 95
 96	conf := make(core.Configuration)
 97	conf[core.ConfigKeyTarget] = target
 98	conf[confKeyBaseUrl] = baseURL
 99	conf[confKeyProject] = project
100	conf[confKeyCredentialType] = credType
101
102	err = j.ValidateConfig(conf)
103	if err != nil {
104		return nil, err
105	}
106
107	fmt.Printf("Attempting to login with credentials...\n")
108	client, err := buildClient(context.TODO(), baseURL, credType, cred)
109	if err != nil {
110		return nil, err
111	}
112
113	// verify access to the project with credentials
114	fmt.Printf("Checking project ...\n")
115	_, err = client.GetProject(project)
116	if err != nil {
117		return nil, fmt.Errorf(
118			"Project %s doesn't exist on %s, or authentication credentials for (%s)"+
119				" are invalid",
120			project, baseURL, login)
121	}
122
123	err = core.FinishConfig(repo, metaKeyJiraLogin, login)
124	if err != nil {
125		return nil, err
126	}
127
128	fmt.Print(moreConfigText)
129	return conf, nil
130}
131
132// ValidateConfig returns true if all required keys are present
133func (*Jira) ValidateConfig(conf core.Configuration) error {
134	if v, ok := conf[core.ConfigKeyTarget]; !ok {
135		return fmt.Errorf("missing %s key", core.ConfigKeyTarget)
136	} else if v != target {
137		return fmt.Errorf("unexpected target name: %v", v)
138	}
139
140	if _, ok := conf[confKeyProject]; !ok {
141		return fmt.Errorf("missing %s key", confKeyProject)
142	}
143
144	return nil
145}
146
147func promptCredOptions(repo repository.RepoConfig, login, baseUrl string) (auth.Credential, error) {
148	creds, err := auth.List(repo,
149		auth.WithTarget(target),
150		auth.WithKind(auth.KindToken),
151		auth.WithMeta(auth.MetaKeyLogin, login),
152		auth.WithMeta(auth.MetaKeyBaseURL, baseUrl),
153	)
154	if err != nil {
155		return nil, err
156	}
157
158	cred, index, err := input.PromptCredential(target, "password", creds, []string{
159		"enter my password",
160		"ask my password each time",
161	})
162	switch {
163	case err != nil:
164		return nil, err
165	case cred != nil:
166		return cred, nil
167	case index == 0:
168		password, err := input.PromptPassword("Password", "password", input.Required)
169		if err != nil {
170			return nil, err
171		}
172		lp := auth.NewLoginPassword(target, login, password)
173		lp.SetMetadata(auth.MetaKeyLogin, login)
174		return lp, nil
175	case index == 1:
176		l := auth.NewLogin(target, login)
177		l.SetMetadata(auth.MetaKeyLogin, login)
178		return l, nil
179	default:
180		panic("missed case")
181	}
182}