From cf2bcefedba490285937de98c46af204ae52a9d8 Mon Sep 17 00:00:00 2001 From: Florian Krauthan Date: Wed, 8 Feb 2017 14:37:57 -0800 Subject: [PATCH 1/4] Added basic auth support to the library --- README.md | 34 ++++++++++++++++++++++++++++++++- authentication.go | 36 +++++++++++++++++++++++++++++++---- jira.go | 48 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3b7fc78..c629015 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,41 @@ func main() { } ``` -### Authenticate with session cookie +### Authenticate with jira Some actions require an authenticated user. + +#### Authenticate with basic auth + +Here is an example with a basic auth authentification. + +```go +package main + +import ( + "fmt" + "github.com/andygrunwald/go-jira" +) + +func main() { + jiraClient, err := jira.NewClient(nil, "https://your.jira-instance.com/") + if err != nil { + panic(err) + } + jiraClient.Authentication.SetBasicAuth("username", "password") + + issue, _, err := jiraClient.Issue.Get("SYS-5156", nil) + if err != nil { + panic(err) + } + + fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary) +} +``` + + +#### Authenticate with session cookie + Here is an example with a session cookie authentification. ```go diff --git a/authentication.go b/authentication.go index dd0ecc9..6186428 100644 --- a/authentication.go +++ b/authentication.go @@ -7,11 +7,27 @@ import ( "net/http" ) +const ( + // HTTP Basic Authentication + authTypeBasic = 1 + // HTTP Session Authentication + authTypeSession = 2 +) + // AuthenticationService handles authentication for the JIRA instance / API. // // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#authentication type AuthenticationService struct { client *Client + + // Authentication type + authType int + + // Basic auth username + username string + + // Basic auth password + password string } // Session represents a Session JSON response by the JIRA API. @@ -68,14 +84,26 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string) } s.client.session = session + s.authType = authTypeSession return true, nil } -// Authenticated reports if the current Client has an authenticated session with JIRA +func (s *AuthenticationService) SetBasicAuth(username, password string) { + s.username = username; + s.password = password; + s.authType = authTypeBasic; +} + +// Authenticated reports if the current Client has authentication details for JIRA func (s *AuthenticationService) Authenticated() bool { if s != nil { - return s.client.session != nil + if s.authType == authTypeSession { + return s.client.session != nil + } else if s.authType == authTypeBasic { + return s.username != "" + } + } return false } @@ -84,7 +112,7 @@ func (s *AuthenticationService) Authenticated() bool { // // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session func (s *AuthenticationService) Logout() error { - if s.client.session == nil { + if s.authType != authTypeSession || s.client.session == nil { return fmt.Errorf("No user is authenticated yet.") } @@ -116,7 +144,7 @@ func (s *AuthenticationService) GetCurrentUser() (*Session, error) { if s == nil { return nil, fmt.Errorf("AUthenticaiton Service is not instantiated") } - if s.client.session == nil { + if s.authType != authTypeSession || s.client.session == nil { return nil, fmt.Errorf("No user is authenticated yet") } diff --git a/jira.go b/jira.go index 2052acb..f7430e9 100644 --- a/jira.go +++ b/jira.go @@ -84,10 +84,18 @@ func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Req req.Header.Set("Content-Type", "application/json") - // Set session cookie if there is one - if c.session != nil { - for _, cookie := range c.session.Cookies { - req.AddCookie(cookie) + // Set authentication information + if c.Authentication.authType == authTypeSession { + // Set session cookie if there is one + if c.session != nil { + for _, cookie := range c.session.Cookies { + req.AddCookie(cookie) + } + } + } else if c.Authentication.authType == authTypeBasic { + // Set basic auth information + if c.Authentication.username != "" { + req.SetBasicAuth(c.Authentication.username, c.Authentication.password) } } @@ -122,10 +130,18 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ req.Header.Set("Content-Type", "application/json") - // Set session cookie if there is one - if c.session != nil { - for _, cookie := range c.session.Cookies { - req.AddCookie(cookie) + // Set authentication information + if c.Authentication.authType == authTypeSession { + // Set session cookie if there is one + if c.session != nil { + for _, cookie := range c.session.Cookies { + req.AddCookie(cookie) + } + } + } else if c.Authentication.authType == authTypeBasic { + // Set basic auth information + if c.Authentication.username != "" { + req.SetBasicAuth(c.Authentication.username, c.Authentication.password) } } @@ -174,10 +190,18 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) ( // Set required headers req.Header.Set("X-Atlassian-Token", "nocheck") - // Set session cookie if there is one - if c.session != nil { - for _, cookie := range c.session.Cookies { - req.AddCookie(cookie) + // Set authentication information + if c.Authentication.authType == authTypeSession { + // Set session cookie if there is one + if c.session != nil { + for _, cookie := range c.session.Cookies { + req.AddCookie(cookie) + } + } + } else if c.Authentication.authType == authTypeBasic { + // Set basic auth information + if c.Authentication.username != "" { + req.SetBasicAuth(c.Authentication.username, c.Authentication.password) } } From dcb1c110067ddac57e5af185c1f4fbb54fbafb05 Mon Sep 17 00:00:00 2001 From: Florian Krauthan Date: Wed, 8 Feb 2017 14:59:20 -0800 Subject: [PATCH 2/4] Added tests for new authentication Fixed some existing tests to work as expected --- authentication_test.go | 23 +++++++++++++++++++ jira_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/authentication_test.go b/authentication_test.go index 3d13bfa..332f81c 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -74,6 +74,29 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { if testClient.Authentication.Authenticated() != true { t.Error("Expected true, but result was false") } + + if testClient.Authentication.authType != authTypeSession { + t.Error("Expected authType %d. Got %d", authTypeSession, testClient.Authentication.authType) + } +} + +func TestAuthenticationService_SetBasicAuth(t *testing.T) { + // Skip setup() because we don't want a fully setup client + testClient = new(Client) + + testClient.Authentication.SetBasicAuth("test-user", "test-password") + + if testClient.Authentication.username != "test-user" { + t.Error("Expected username test-user. Got %s", testClient.Authentication.username) + } + + if testClient.Authentication.password != "test-password" { + t.Error("Expected password test-password. Got %s", testClient.Authentication.password) + } + + if testClient.Authentication.authType != authTypeBasic { + t.Error("Expected authType %d. Got %d", authTypeBasic, testClient.Authentication.authType) + } } func TestAuthenticationService_Authenticated(t *testing.T) { diff --git a/jira_test.go b/jira_test.go index 15ae001..c421a10 100644 --- a/jira_test.go +++ b/jira_test.go @@ -201,6 +201,7 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { cookie := &http.Cookie{Name: "testcookie", Value: "testvalue"} c.session = &Session{Cookies: []*http.Cookie{cookie}} + c.Authentication.authType = authTypeSession inURL := "rest/api/2/issue/" inBody := &Issue{Key: "MESOS"} @@ -221,6 +222,28 @@ func TestClient_NewRequest_SessionCookies(t *testing.T) { } } +func TestClient_NewRequest_BasicAuth(t *testing.T) { + c, err := NewClient(nil, testJIRAInstanceURL) + if err != nil { + t.Errorf("An error occured. Expected nil. Got %+v.", err) + } + + c.Authentication.SetBasicAuth("test-user", "test-password") + + inURL := "rest/api/2/issue/" + inBody := &Issue{Key: "MESOS"} + req, err := c.NewRequest("GET", inURL, inBody) + + if err != nil { + t.Errorf("An error occured. Expected nil. Got %+v.", err) + } + + username, password, ok := req.BasicAuth() + if !ok || username != "test-user" || password != "test-password" { + t.Errorf("An error occured. Expected basic auth username %s and password %s. Got username %s and password %s.", "test-user", "test-password", username, password) + } +} + // If a nil body is passed to gerrit.NewRequest, make sure that nil is also passed to http.NewRequest. // In most cases, passing an io.Reader that returns no content is fine, // since there is no difference between an HTTP request body that is an empty string versus one that is not set at all. @@ -247,6 +270,7 @@ func TestClient_NewMultiPartRequest(t *testing.T) { cookie := &http.Cookie{Name: "testcookie", Value: "testvalue"} c.session = &Session{Cookies: []*http.Cookie{cookie}} + c.Authentication.authType = authTypeSession inURL := "rest/api/2/issue/" inBuf := bytes.NewBufferString("teststring") @@ -271,6 +295,32 @@ func TestClient_NewMultiPartRequest(t *testing.T) { } } +func TestClient_NewMultiPartRequest_BasicAuth(t *testing.T) { + c, err := NewClient(nil, testJIRAInstanceURL) + if err != nil { + t.Errorf("An error occured. Expected nil. Got %+v.", err) + } + + c.Authentication.SetBasicAuth("test-user", "test-password") + + inURL := "rest/api/2/issue/" + inBuf := bytes.NewBufferString("teststring") + req, err := c.NewMultiPartRequest("GET", inURL, inBuf) + + if err != nil { + t.Errorf("An error occured. Expected nil. Got %+v.", err) + } + + username, password, ok := req.BasicAuth() + if !ok || username != "test-user" || password != "test-password" { + t.Errorf("An error occured. Expected basic auth username %s and password %s. Got username %s and password %s.", "test-user", "test-password", username, password) + } + + if req.Header.Get("X-Atlassian-Token") != "nocheck" { + t.Errorf("An error occured. Unexpected X-Atlassian-Token header value. Expected nocheck, actual %s.", req.Header.Get("X-Atlassian-Token")) + } +} + func TestClient_Do(t *testing.T) { setup() defer teardown() From 128b7b7bac990e34d35f1f520e35bd92ec807a69 Mon Sep 17 00:00:00 2001 From: Florian Krauthan Date: Wed, 8 Feb 2017 15:16:05 -0800 Subject: [PATCH 3/4] Fixed test --- authentication_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authentication_test.go b/authentication_test.go index 332f81c..26ea78c 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -81,8 +81,8 @@ func TestAuthenticationService_AcquireSessionCookie_Success(t *testing.T) { } func TestAuthenticationService_SetBasicAuth(t *testing.T) { - // Skip setup() because we don't want a fully setup client - testClient = new(Client) + setup() + defer teardown() testClient.Authentication.SetBasicAuth("test-user", "test-password") From fd59d627728874c39d3773816031bf613feadbf8 Mon Sep 17 00:00:00 2001 From: Florian Krauthan Date: Wed, 8 Feb 2017 15:22:48 -0800 Subject: [PATCH 4/4] Increased test coverage --- authentication_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/authentication_test.go b/authentication_test.go index 26ea78c..e7d8a8d 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -109,6 +109,30 @@ func TestAuthenticationService_Authenticated(t *testing.T) { } } +func TestAuthenticationService_Authenticated_WithBasicAuth(t *testing.T) { + setup() + defer teardown() + + testClient.Authentication.SetBasicAuth("test-user", "test-password") + + // Test before we've attempted to authenticate + if testClient.Authentication.Authenticated() != true { + t.Error("Expected true, but result was false") + } +} + +func TestAuthenticationService_Authenticated_WithBasicAuthButNoUsername(t *testing.T) { + setup() + defer teardown() + + testClient.Authentication.SetBasicAuth("", "test-password") + + // Test before we've attempted to authenticate + if testClient.Authentication.Authenticated() != false { + t.Error("Expected false, but result was true") + } +} + func TestAithenticationService_GetUserInfo_AccessForbidden_Fail(t *testing.T) { setup() defer teardown()