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	// don't forget to store the now known valid token
124	if !auth.IdExist(repo, cred.ID()) {
125		err = auth.Store(repo, cred)
126		if err != nil {
127			return nil, err
128		}
129	}
130
131	err = core.FinishConfig(repo, metaKeyJiraLogin, login)
132	if err != nil {
133		return nil, err
134	}
135
136	fmt.Print(moreConfigText)
137	return conf, nil
138}
139
140// ValidateConfig returns true if all required keys are present
141func (*Jira) ValidateConfig(conf core.Configuration) error {
142	if v, ok := conf[core.ConfigKeyTarget]; !ok {
143		return fmt.Errorf("missing %s key", core.ConfigKeyTarget)
144	} else if v != target {
145		return fmt.Errorf("unexpected target name: %v", v)
146	}
147
148	if _, ok := conf[confKeyProject]; !ok {
149		return fmt.Errorf("missing %s key", confKeyProject)
150	}
151
152	return nil
153}
154
155func promptCredOptions(repo repository.RepoConfig, login, baseUrl string) (auth.Credential, error) {
156	creds, err := auth.List(repo,
157		auth.WithTarget(target),
158		auth.WithKind(auth.KindToken),
159		auth.WithMeta(auth.MetaKeyLogin, login),
160		auth.WithMeta(auth.MetaKeyBaseURL, baseUrl),
161	)
162	if err != nil {
163		return nil, err
164	}
165
166	cred, index, err := input.PromptCredential(target, "password", creds, []string{
167		"enter my password",
168		"ask my password each time",
169	})
170	switch {
171	case err != nil:
172		return nil, err
173	case cred != nil:
174		return cred, nil
175	case index == 0:
176		password, err := input.PromptPassword("Password", "password", input.Required)
177		if err != nil {
178			return nil, err
179		}
180		lp := auth.NewLoginPassword(target, login, password)
181		lp.SetMetadata(auth.MetaKeyLogin, login)
182		lp.SetMetadata(auth.MetaKeyBaseURL, baseUrl)
183		return lp, nil
184	case index == 1:
185		l := auth.NewLogin(target, login)
186		l.SetMetadata(auth.MetaKeyLogin, login)
187		l.SetMetadata(auth.MetaKeyBaseURL, baseUrl)
188		return l, nil
189	default:
190		panic("missed case")
191	}
192}