From fedc66614fff457d975009da3f6aa3bf30aae62e Mon Sep 17 00:00:00 2001 From: Rodney Lorrimar Date: Tue, 3 Aug 2021 13:02:38 +0800 Subject: [PATCH 1/3] Add authentication-method: bearer-token --- jiracli/cli.go | 6 +++++- jiracmd/login.go | 4 ++++ jiracmd/logout.go | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/jiracli/cli.go b/jiracli/cli.go index d834520a..920c0a8d 100644 --- a/jiracli/cli.go +++ b/jiracli/cli.go @@ -173,7 +173,11 @@ func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) { token := globals.GetPass() authHeader := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", globals.Login.Value, token)))) req.Header.Add("Authorization", authHeader) - } + } else if globals.AuthMethod() == "bearer-token" { + token := globals.GetPass() + authHeader := fmt.Sprintf("Bearer %s", token) + req.Header.Add("Authorization", authHeader) + } return req, nil }) diff --git a/jiracmd/login.go b/jiracmd/login.go index e74cb6f6..b969e064 100644 --- a/jiracmd/login.go +++ b/jiracmd/login.go @@ -50,6 +50,10 @@ func CmdLogin(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.Comm log.Noticef("No need to login when using api-token authentication method") return nil } + if globals.AuthMethod() == "bearer-token" { + log.Noticef("No need to login when using bearer-token authentication method") + return nil + } ua := o.WithoutRedirect().WithRetries(0).WithoutCallbacks().WithPostCallback(authCallback) for { diff --git a/jiracmd/logout.go b/jiracmd/logout.go index 1a534bc9..6d909dd8 100644 --- a/jiracmd/logout.go +++ b/jiracmd/logout.go @@ -30,13 +30,13 @@ func CmdLogoutRegistry() *jiracli.CommandRegistryEntry { // CmdLogout will attempt to terminate an active Jira session func CmdLogout(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.CommonOptions) error { - if globals.AuthMethod() == "api-token" { - log.Noticef("No need to logout when using api-token authentication method") + if (globals.AuthMethod() == "api-token" || globals.AuthMethod() == "bearer-token") { + log.Noticef("No need to logout when using api-token or bearer-token authentication method") if globals.GetPass() != "" && terminal.IsTerminal(int(os.Stdin.Fd())) && terminal.IsTerminal(int(os.Stdout.Fd())) { delete := false err := survey.AskOne( &survey.Confirm{ - Message: fmt.Sprintf("Delete api-token from password provider [%s]: ", globals.PasswordSource), + Message: fmt.Sprintf("Delete token from password provider [%s]: ", globals.PasswordSource), Default: false, }, &delete, From ccb5fadfc37fb255980e38d150bb19b92b8079b0 Mon Sep 17 00:00:00 2001 From: Rodney Lorrimar Date: Tue, 3 Aug 2021 13:18:36 +0800 Subject: [PATCH 2/3] Add documentation for bearer token authentication --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 2688c20d..62a36617 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,17 @@ The API Token authentication requires both the token and the email of the user. If your Jira service still allows you to use the Session based authentication method then `jira` will prompt for a password automatically when get a response header from the Jira service that indicates you do not have an active session (ie the `X-Ausername` header is set to `anonymous`). Then after authentication we cache the `cloud.session.token` cookie returned by the service [session login api](https://docs.atlassian.com/jira/REST/cloud/#auth/1/session-login) and reuse that on subsequent requests. Typically this cookie will be valid for several hours (depending on the service configuration). To automatically securely store your password for easy reuse by jira You can enable a `password-source` via `.jira.d/config.yml` with possible values of `keyring`, `pass` or `gopass`. +Depending on how your private Jira service is configured, API tokens may require the "[Bearer][]" authentication scheme instead of the traditional "[Basic][]" [authentication scheme][scheme]. In this case, set the `authentication-method: bearer-token` property in your `$HOME/.jira.d/config.yml` file. + +[scheme]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes +[Bearer]: https://datatracker.ietf.org/doc/html/rfc6750 +[Basic]: https://tools.ietf.org/html/rfc7617 + +| **API token [scheme][]** | `authentication-method` | **Example HTTP request header** | +|:------------------------:|-------------------------|-------------------------------------------------| +| [Basic][] | `api-token` | `Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQK` | +| [Bearer][] | `bearer-token` | `Authorization: Bearer MY_TOKEN` | + #### User vs Login The Jira service has sometimes differing opinions about how a user is identified. In other words the ID you login with might not be ID that the jira system recognized you as. This matters when trying to identify a user via various Jira REST APIs (like issue assignment). This is especially relevant when trying to authenticate with an API Token where the authentication user is usually an email address, but within the Jira system the user is identified by a user name. To accommodate this `jira` now supports two different properties in the config file. So when authentication using the API Tokens you will likely want something like this in your `$HOME/.jira.d/config.yml` file: ```yaml From b3723c7b63563169db5b58ce8b33418576fb9e11 Mon Sep 17 00:00:00 2001 From: Andrew Somerville Date: Sun, 21 Nov 2021 01:03:22 -0500 Subject: [PATCH 3/3] wrap token comparisons in a function, proliferate it virtually everywhere apk-token was, and fix some related tab/space issues --- jiracli/cli.go | 13 +++++++++---- jiracli/password.go | 6 +++--- jiracmd/logout.go | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/jiracli/cli.go b/jiracli/cli.go index 920c0a8d..44c26e84 100644 --- a/jiracli/cli.go +++ b/jiracli/cli.go @@ -55,7 +55,8 @@ const ( ) type GlobalOptions struct { - // AuthenticationMethod is the method we use to authenticate with the jira serivce. Possible values are "api-token" or "session". + // AuthenticationMethod is the method we use to authenticate with the jira serivce. + // Possible values are "api-token", "bearer-token" or "session". // The default is "api-token" when the service endpoint ends with "atlassian.net", otherwise it "session". Session authentication // will promt for user password and use the /auth/1/session-login endpoint. AuthenticationMethod figtree.StringOption `yaml:"authentication-method,omitempty" json:"authentication-method,omitempty"` @@ -154,6 +155,10 @@ func (o *GlobalOptions) AuthMethod() string { return o.AuthenticationMethod.Value } +func (o *GlobalOptions) AuthMethodIsToken() bool{ + return o.AuthMethod() == "api-token" || o.AuthMethod() == "bearer-token"; +} + func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) { globals := GlobalOptions{ User: figtree.NewStringOption(os.Getenv("USER")), @@ -177,7 +182,7 @@ func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) { token := globals.GetPass() authHeader := fmt.Sprintf("Bearer %s", token) req.Header.Add("Authorization", authHeader) - } + } return req, nil }) @@ -198,7 +203,7 @@ func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) { // rerun the original request return o.Do(req) } - } else if globals.AuthMethod() == "api-token" && resp.StatusCode == 401 { + } else if globals.AuthMethodIsToken() && resp.StatusCode == 401 { globals.SetPass("") return o.Do(req) } @@ -236,7 +241,7 @@ func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) { } else if globals.SocksProxy.Value != "" { o = o.WithTransport(socksProxy(globals.SocksProxy.Value)) } - if globals.AuthMethod() == "api-token" { + if globals.AuthMethodIsToken() { o = o.WithCookieFile("") } if globals.Login.Value == "" { diff --git a/jiracli/password.go b/jiracli/password.go index 1baa986f..9a1470ca 100644 --- a/jiracli/password.go +++ b/jiracli/password.go @@ -21,7 +21,7 @@ func (o *GlobalOptions) ProvideAuthParams() *jiradata.AuthParams { func (o *GlobalOptions) keyName() string { user := o.Login.Value - if o.AuthMethod() == "api-token" { + if o.AuthMethodIsToken() { user = "api-token:" + user } @@ -133,14 +133,14 @@ func (o *GlobalOptions) GetPass() string { return o.cachedPassword } - if o.cachedPassword = os.Getenv("JIRA_API_TOKEN"); o.cachedPassword != "" && o.AuthMethod() == "api-token" { + if o.cachedPassword = os.Getenv("JIRA_API_TOKEN"); o.cachedPassword != "" && o.AuthMethodIsToken() { return o.cachedPassword } prompt := fmt.Sprintf("Jira Password [%s]: ", o.Login) help := "" - if o.AuthMethod() == "api-token" { + if o.AuthMethodIsToken() { prompt = fmt.Sprintf("Jira API-Token [%s]: ", o.Login) help = "API Tokens may be required by your Jira service endpoint: https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/" } diff --git a/jiracmd/logout.go b/jiracmd/logout.go index 6d909dd8..3d156c3e 100644 --- a/jiracmd/logout.go +++ b/jiracmd/logout.go @@ -30,7 +30,7 @@ func CmdLogoutRegistry() *jiracli.CommandRegistryEntry { // CmdLogout will attempt to terminate an active Jira session func CmdLogout(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.CommonOptions) error { - if (globals.AuthMethod() == "api-token" || globals.AuthMethod() == "bearer-token") { + if globals.AuthMethodIsToken() { log.Noticef("No need to logout when using api-token or bearer-token authentication method") if globals.GetPass() != "" && terminal.IsTerminal(int(os.Stdin.Fd())) && terminal.IsTerminal(int(os.Stdout.Fd())) { delete := false