2019-11-07 19:01:08 +03:00
|
|
|
package jira
|
|
|
|
|
|
|
|
import (
|
2020-02-15 18:12:21 +03:00
|
|
|
"context"
|
2019-11-07 19:01:08 +03:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/MichaelMure/git-bug/bridge/core"
|
2020-02-15 18:01:15 +03:00
|
|
|
"github.com/MichaelMure/git-bug/bridge/core/auth"
|
2019-12-18 18:49:49 +03:00
|
|
|
"github.com/MichaelMure/git-bug/cache"
|
2019-11-23 09:34:19 +03:00
|
|
|
"github.com/MichaelMure/git-bug/input"
|
2020-02-15 18:01:15 +03:00
|
|
|
"github.com/MichaelMure/git-bug/repository"
|
2019-11-07 19:01:08 +03:00
|
|
|
)
|
|
|
|
|
2019-11-23 09:34:19 +03:00
|
|
|
const moreConfigText = `
|
|
|
|
NOTE: There are a few optional configuration values that you can additionally
|
|
|
|
set in your git configuration to influence the behavior of the bridge. Please
|
|
|
|
see the notes at:
|
|
|
|
https://github.com/MichaelMure/git-bug/blob/master/doc/jira_bridge.md
|
|
|
|
`
|
|
|
|
|
2019-12-05 09:50:35 +03:00
|
|
|
const credTypeText = `
|
|
|
|
JIRA has recently altered it's authentication strategies. Servers deployed
|
|
|
|
prior to October 1st 2019 must use "SESSION" authentication, whereby the REST
|
|
|
|
client logs in with an actual username and password, is assigned a session, and
|
|
|
|
passes the session cookie with each request. JIRA Cloud and servers deployed
|
|
|
|
after October 1st 2019 must use "TOKEN" authentication. You must create a user
|
|
|
|
API token and the client will provide this along with your username with each
|
2020-02-09 22:52:19 +03:00
|
|
|
request.`
|
2019-12-05 09:50:35 +03:00
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
func (*Jira) ValidParams() map[string]interface{} {
|
|
|
|
return map[string]interface{}{
|
|
|
|
"BaseURL": nil,
|
|
|
|
"Login": nil,
|
|
|
|
"CredPrefix": nil,
|
|
|
|
"Project": nil,
|
2021-05-09 12:14:45 +03:00
|
|
|
"TokenRaw": nil,
|
2020-02-15 18:01:15 +03:00
|
|
|
}
|
|
|
|
}
|
2020-02-09 22:52:19 +03:00
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
// Configure sets up the bridge configuration
|
2021-05-09 12:14:45 +03:00
|
|
|
func (j *Jira) Configure(repo *cache.RepoCache, params core.BridgeParams, interactive bool) (core.Configuration, error) {
|
2019-11-07 19:01:08 +03:00
|
|
|
var err error
|
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
baseURL := params.BaseURL
|
|
|
|
if baseURL == "" {
|
2021-05-09 12:14:45 +03:00
|
|
|
if !interactive {
|
|
|
|
return nil, fmt.Errorf("Non-interactive-mode is active. Please specify the JIRA server URL via the --base-url option.")
|
|
|
|
}
|
2019-11-07 19:01:08 +03:00
|
|
|
// terminal prompt
|
2020-02-15 18:01:15 +03:00
|
|
|
baseURL, err = input.Prompt("JIRA server URL", "URL", input.Required, input.IsURL)
|
2019-11-07 19:01:08 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-09 22:52:19 +03:00
|
|
|
project := params.Project
|
2019-11-07 19:01:08 +03:00
|
|
|
if project == "" {
|
2021-05-09 12:14:45 +03:00
|
|
|
if !interactive {
|
|
|
|
return nil, fmt.Errorf("Non-interactive-mode is active. Please specify the JIRA project key via the --project option.")
|
|
|
|
}
|
2020-02-09 22:52:19 +03:00
|
|
|
project, err = input.Prompt("JIRA project key", "project", input.Required)
|
2019-11-07 19:01:08 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
var login string
|
2021-05-09 12:14:45 +03:00
|
|
|
var credType string
|
2020-02-15 18:01:15 +03:00
|
|
|
var cred auth.Credential
|
2019-11-07 19:01:08 +03:00
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
switch {
|
|
|
|
case params.CredPrefix != "":
|
|
|
|
cred, err = auth.LoadWithPrefix(repo, params.CredPrefix)
|
2020-02-09 22:52:19 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-15 18:01:15 +03:00
|
|
|
l, ok := cred.GetMetadata(auth.MetaKeyLogin)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("credential doesn't have a login")
|
|
|
|
}
|
|
|
|
login = l
|
|
|
|
default:
|
2020-03-28 22:10:52 +03:00
|
|
|
if params.Login == "" {
|
2021-05-09 12:14:45 +03:00
|
|
|
if !interactive {
|
|
|
|
return nil, fmt.Errorf("Non-interactive-mode is active. Please specify the login name via the --login option.")
|
|
|
|
}
|
2020-02-15 18:01:15 +03:00
|
|
|
login, err = input.Prompt("JIRA login", "login", input.Required)
|
2021-05-09 12:14:45 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-28 22:10:52 +03:00
|
|
|
} else {
|
|
|
|
login = params.Login
|
|
|
|
}
|
2021-05-09 12:14:45 +03:00
|
|
|
// TODO: validate username
|
|
|
|
|
|
|
|
if params.TokenRaw == "" {
|
|
|
|
if !interactive {
|
|
|
|
return nil, fmt.Errorf("Non-interactive-mode is active. Please specify the access token via the --token option.")
|
|
|
|
}
|
|
|
|
fmt.Println(credTypeText)
|
|
|
|
credTypeInput, err := input.PromptChoice("Authentication mechanism", []string{"SESSION", "TOKEN"})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
credType = []string{"SESSION", "TOKEN"}[credTypeInput]
|
|
|
|
cred, err = promptCredOptions(repo, login, baseURL)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
credType = "TOKEN"
|
2019-11-07 19:01:08 +03:00
|
|
|
}
|
|
|
|
}
|
2019-11-23 09:34:19 +03:00
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
conf := make(core.Configuration)
|
|
|
|
conf[core.ConfigKeyTarget] = target
|
|
|
|
conf[confKeyBaseUrl] = baseURL
|
|
|
|
conf[confKeyProject] = project
|
|
|
|
conf[confKeyCredentialType] = credType
|
2020-02-23 16:05:03 +03:00
|
|
|
conf[confKeyDefaultLogin] = login
|
2020-02-15 18:01:15 +03:00
|
|
|
|
|
|
|
err = j.ValidateConfig(conf)
|
2019-11-07 19:01:08 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-05 09:50:35 +03:00
|
|
|
fmt.Printf("Attempting to login with credentials...\n")
|
2020-02-15 18:12:21 +03:00
|
|
|
client, err := buildClient(context.TODO(), baseURL, credType, cred)
|
2019-12-05 09:50:35 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify access to the project with credentials
|
|
|
|
fmt.Printf("Checking project ...\n")
|
|
|
|
_, 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",
|
2020-02-15 18:01:15 +03:00
|
|
|
project, baseURL, login)
|
|
|
|
}
|
|
|
|
|
2020-02-17 23:43:42 +03:00
|
|
|
// don't forget to store the now known valid token
|
|
|
|
if !auth.IdExist(repo, cred.ID()) {
|
|
|
|
err = auth.Store(repo, cred)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 18:01:15 +03:00
|
|
|
err = core.FinishConfig(repo, metaKeyJiraLogin, login)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-12-05 09:50:35 +03:00
|
|
|
}
|
|
|
|
|
2019-11-23 09:34:19 +03:00
|
|
|
fmt.Print(moreConfigText)
|
2019-11-07 19:01:08 +03:00
|
|
|
return conf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidateConfig returns true if all required keys are present
|
|
|
|
func (*Jira) ValidateConfig(conf core.Configuration) error {
|
2019-12-18 18:49:49 +03:00
|
|
|
if v, ok := conf[core.ConfigKeyTarget]; !ok {
|
|
|
|
return fmt.Errorf("missing %s key", core.ConfigKeyTarget)
|
2019-11-07 19:01:08 +03:00
|
|
|
} else if v != target {
|
|
|
|
return fmt.Errorf("unexpected target name: %v", v)
|
|
|
|
}
|
2020-02-23 16:05:03 +03:00
|
|
|
if _, ok := conf[confKeyBaseUrl]; !ok {
|
|
|
|
return fmt.Errorf("missing %s key", confKeyBaseUrl)
|
|
|
|
}
|
2020-02-15 18:01:15 +03:00
|
|
|
if _, ok := conf[confKeyProject]; !ok {
|
|
|
|
return fmt.Errorf("missing %s key", confKeyProject)
|
2019-11-07 19:01:08 +03:00
|
|
|
}
|
2020-02-23 16:05:03 +03:00
|
|
|
if _, ok := conf[confKeyCredentialType]; !ok {
|
|
|
|
return fmt.Errorf("missing %s key", confKeyCredentialType)
|
|
|
|
}
|
|
|
|
if _, ok := conf[confKeyDefaultLogin]; !ok {
|
|
|
|
return fmt.Errorf("missing %s key", confKeyDefaultLogin)
|
|
|
|
}
|
2019-11-07 19:01:08 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-02-15 18:01:15 +03:00
|
|
|
|
2020-07-28 13:56:46 +03:00
|
|
|
func promptCredOptions(repo repository.RepoKeyring, login, baseUrl string) (auth.Credential, error) {
|
2020-02-15 18:01:15 +03:00
|
|
|
creds, err := auth.List(repo,
|
|
|
|
auth.WithTarget(target),
|
|
|
|
auth.WithKind(auth.KindToken),
|
|
|
|
auth.WithMeta(auth.MetaKeyLogin, login),
|
|
|
|
auth.WithMeta(auth.MetaKeyBaseURL, baseUrl),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cred, index, err := input.PromptCredential(target, "password", creds, []string{
|
|
|
|
"enter my password",
|
|
|
|
"ask my password each time",
|
|
|
|
})
|
|
|
|
switch {
|
|
|
|
case err != nil:
|
|
|
|
return nil, err
|
|
|
|
case cred != nil:
|
|
|
|
return cred, nil
|
|
|
|
case index == 0:
|
|
|
|
password, err := input.PromptPassword("Password", "password", input.Required)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
lp := auth.NewLoginPassword(target, login, password)
|
|
|
|
lp.SetMetadata(auth.MetaKeyLogin, login)
|
2020-02-17 23:43:42 +03:00
|
|
|
lp.SetMetadata(auth.MetaKeyBaseURL, baseUrl)
|
2020-02-15 18:01:15 +03:00
|
|
|
return lp, nil
|
|
|
|
case index == 1:
|
|
|
|
l := auth.NewLogin(target, login)
|
|
|
|
l.SetMetadata(auth.MetaKeyLogin, login)
|
2020-02-17 23:43:42 +03:00
|
|
|
l.SetMetadata(auth.MetaKeyBaseURL, baseUrl)
|
2020-02-15 18:01:15 +03:00
|
|
|
return l, nil
|
|
|
|
default:
|
|
|
|
panic("missed case")
|
|
|
|
}
|
|
|
|
}
|