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	conf[confKeyDefaultLogin] = login
102
103	err = j.ValidateConfig(conf)
104	if err != nil {
105		return nil, err
106	}
107
108	fmt.Printf("Attempting to login with credentials...\n")
109	client, err := buildClient(context.TODO(), baseURL, credType, cred)
110	if err != nil {
111		return nil, err
112	}
113
114	// verify access to the project with credentials
115	fmt.Printf("Checking project ...\n")
116	_, err = client.GetProject(project)
117	if err != nil {
118		return nil, fmt.Errorf(
119			"Project %s doesn't exist on %s, or authentication credentials for (%s)"+
120				" are invalid",
121			project, baseURL, login)
122	}
123
124	// don't forget to store the now known valid token
125	if !auth.IdExist(repo, cred.ID()) {
126		err = auth.Store(repo, cred)
127		if err != nil {
128			return nil, err
129		}
130	}
131
132	err = core.FinishConfig(repo, metaKeyJiraLogin, login)
133	if err != nil {
134		return nil, err
135	}
136
137	fmt.Print(moreConfigText)
138	return conf, nil
139}
140
141// ValidateConfig returns true if all required keys are present
142func (*Jira) ValidateConfig(conf core.Configuration) error {
143	if v, ok := conf[core.ConfigKeyTarget]; !ok {
144		return fmt.Errorf("missing %s key", core.ConfigKeyTarget)
145	} else if v != target {
146		return fmt.Errorf("unexpected target name: %v", v)
147	}
148	if _, ok := conf[confKeyBaseUrl]; !ok {
149		return fmt.Errorf("missing %s key", confKeyBaseUrl)
150	}
151	if _, ok := conf[confKeyProject]; !ok {
152		return fmt.Errorf("missing %s key", confKeyProject)
153	}
154	if _, ok := conf[confKeyCredentialType]; !ok {
155		return fmt.Errorf("missing %s key", confKeyCredentialType)
156	}
157	if _, ok := conf[confKeyDefaultLogin]; !ok {
158		return fmt.Errorf("missing %s key", confKeyDefaultLogin)
159	}
160
161	return nil
162}
163
164func promptCredOptions(repo repository.RepoConfig, login, baseUrl string) (auth.Credential, error) {
165	creds, err := auth.List(repo,
166		auth.WithTarget(target),
167		auth.WithKind(auth.KindToken),
168		auth.WithMeta(auth.MetaKeyLogin, login),
169		auth.WithMeta(auth.MetaKeyBaseURL, baseUrl),
170	)
171	if err != nil {
172		return nil, err
173	}
174
175	cred, index, err := input.PromptCredential(target, "password", creds, []string{
176		"enter my password",
177		"ask my password each time",
178	})
179	switch {
180	case err != nil:
181		return nil, err
182	case cred != nil:
183		return cred, nil
184	case index == 0:
185		password, err := input.PromptPassword("Password", "password", input.Required)
186		if err != nil {
187			return nil, err
188		}
189		lp := auth.NewLoginPassword(target, login, password)
190		lp.SetMetadata(auth.MetaKeyLogin, login)
191		lp.SetMetadata(auth.MetaKeyBaseURL, baseUrl)
192		return lp, nil
193	case index == 1:
194		l := auth.NewLogin(target, login)
195		l.SetMetadata(auth.MetaKeyLogin, login)
196		l.SetMetadata(auth.MetaKeyBaseURL, baseUrl)
197		return l, nil
198	default:
199		panic("missed case")
200	}
201}