From c88a4d915d5c582f82623fcb3f7805d212868726 Mon Sep 17 00:00:00 2001 From: Pavel Zavora Date: Mon, 14 Dec 2020 18:06:11 +0100 Subject: [PATCH 1/3] feat(oauth): support bitbucket primary email retrieval --- oauth2/generic.go | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/oauth2/generic.go b/oauth2/generic.go index c53c98c87..e221a9211 100644 --- a/oauth2/generic.go +++ b/oauth2/generic.go @@ -1,9 +1,11 @@ package oauth2 import ( + "bytes" "encoding/json" "errors" "fmt" + "io/ioutil" "net/http" "strings" @@ -163,6 +165,14 @@ type UserEmail struct { Email *string `json:"email,omitempty"` Primary *bool `json:"primary,omitempty"` Verified *bool `json:"verified,omitempty"` + // support also indicators sent by bitbucket + IsPrimary *bool `json:"is_primary,omitempty"` + IsConfirmed *bool `json:"is_confirmed,omitempty"` +} + +// WrappedUserEmails represents (bitbucket's) structure that wraps email addresses in a values field +type WrappedUserEmails struct { + Emails []*UserEmail `json:"values,omitempty"` } // getPrimaryEmail gets the private email account for the authenticated user. @@ -173,11 +183,27 @@ func (g *Generic) getPrimaryEmail(client *http.Client) (string, error) { return "", err } defer r.Body.Close() - - emails := []*UserEmail{} - if err = json.NewDecoder(r.Body).Decode(&emails); err != nil { + body, err := ioutil.ReadAll(r.Body) + if err != nil { return "", err } + if len(body) == 0 { + return "", errors.New("No response body from /emails") + } + emails := []*UserEmail{} + if body[0] == '[' { + // array of UserEmail + if err = json.NewDecoder(bytes.NewReader(body)).Decode(&emails); err != nil { + return "", err + } + } else if body[0] == '{' { + // a struct with values that contain []*UserEmail{} + wrapped := WrappedUserEmails{} + if err = json.NewDecoder(bytes.NewReader(body)).Decode(&wrapped); err != nil { + return "", err + } + emails = wrapped.Emails + } email, err := g.primaryEmail(emails) if err != nil { @@ -189,7 +215,9 @@ func (g *Generic) getPrimaryEmail(client *http.Client) (string, error) { func (g *Generic) primaryEmail(emails []*UserEmail) (string, error) { for _, m := range emails { - if m != nil && m.Primary != nil && m.Verified != nil && m.Email != nil { + if m != nil && m.Email != nil && + ((m.Primary != nil && *m.Primary) || (m.IsPrimary != nil && *m.IsPrimary)) && + ((m.Verified != nil && *m.Verified) || (m.IsConfirmed != nil && *m.IsConfirmed)) { return *m.Email, nil } } From c038596f8bce481be9e4ec0df502063209b97227 Mon Sep 17 00:00:00 2001 From: Pavel Zavora Date: Mon, 14 Dec 2020 18:43:40 +0100 Subject: [PATCH 2/3] feat(oauth): test bitbucket primary email retrieval --- oauth2/generic.go | 2 +- oauth2/generic_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/oauth2/generic.go b/oauth2/generic.go index e221a9211..220a26d09 100644 --- a/oauth2/generic.go +++ b/oauth2/generic.go @@ -217,7 +217,7 @@ func (g *Generic) primaryEmail(emails []*UserEmail) (string, error) { for _, m := range emails { if m != nil && m.Email != nil && ((m.Primary != nil && *m.Primary) || (m.IsPrimary != nil && *m.IsPrimary)) && - ((m.Verified != nil && *m.Verified) || (m.IsConfirmed != nil && *m.IsConfirmed)) { + ((m.Verified != nil) || (m.IsConfirmed != nil)) { return *m.Email, nil } } diff --git a/oauth2/generic_test.go b/oauth2/generic_test.go index 89bfc8818..0a5f3f3fe 100644 --- a/oauth2/generic_test.go +++ b/oauth2/generic_test.go @@ -198,3 +198,66 @@ func TestGenericPrincipalIDDomain(t *testing.T) { t.Fatal("Retrieved email was not as expected. Want:", want, "Got:", got) } } + +func TestGenericPrincipalIDDomain_BitBucket(t *testing.T) { + // Test of https://github.com/influxdata/chronograf/issues/5399 + t.Parallel() + emailsResponse := `{ + "pagelen": 10, + "values": [ + { + "is_primary": true, + "is_confirmed": true, + "type": "email", + "email": "pavel.zavora@xyz.io", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/user/emails/pavel.zavora@xyz.io" + } + } + } + ], + "page": 1, + "size": 1 + }` + + mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + enc := json.NewEncoder(rw) + rw.WriteHeader(http.StatusOK) + _ = enc.Encode(struct{}{}) + return + } + if r.URL.Path == "/emails" { + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(emailsResponse)) + return + } + + rw.WriteHeader(http.StatusNotFound) + })) + defer mockAPI.Close() + + logger := clog.New(clog.ParseLevel("debug")) + prov := oauth2.Generic{ + Logger: logger, + Domains: []string{"xyz.io"}, + } + tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) + if err != nil { + t.Fatal("Error initializing TestTripper: err:", err) + } + + tc := &http.Client{ + Transport: tt, + } + + got, err := prov.PrincipalID(tc) + if err != nil { + t.Fatal("Unexpected error while retrieiving PrincipalID: err:", err) + } + want := "pavel.zavora@xyz.io" + if got != want { + t.Fatal("Retrieved email was not as expected. Want:", want, "Got:", got) + } +} From efaacb8b796dd0ae622a237f9e7d52c21d8080c7 Mon Sep 17 00:00:00 2001 From: Pavel Zavora Date: Thu, 17 Dec 2020 07:40:24 +0100 Subject: [PATCH 3/3] feat(oauth): test bitbucket group retrieval --- oauth2/generic.go | 17 ++++++++++++----- oauth2/generic_test.go | 18 ++++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/oauth2/generic.go b/oauth2/generic.go index 220a26d09..421a73d8b 100644 --- a/oauth2/generic.go +++ b/oauth2/generic.go @@ -214,14 +214,21 @@ func (g *Generic) getPrimaryEmail(client *http.Client) (string, error) { } func (g *Generic) primaryEmail(emails []*UserEmail) (string, error) { + var email string for _, m := range emails { - if m != nil && m.Email != nil && - ((m.Primary != nil && *m.Primary) || (m.IsPrimary != nil && *m.IsPrimary)) && - ((m.Verified != nil) || (m.IsConfirmed != nil)) { - return *m.Email, nil + if m != nil && m.Email != nil && ((m.Verified != nil) || (m.IsConfirmed != nil)) { + if email != "" { + email = *m.Email + } + if (m.Primary != nil && *m.Primary) || (m.IsPrimary != nil && *m.IsPrimary) { + return *m.Email, nil + } } } - return "", errors.New("No primary email address") + if email == "" { + return "", errors.New("No primary email address") + } + return email, nil } // ofDomain makes sure that the email is in one of the required domains diff --git a/oauth2/generic_test.go b/oauth2/generic_test.go index 0a5f3f3fe..7cddbe5ee 100644 --- a/oauth2/generic_test.go +++ b/oauth2/generic_test.go @@ -202,6 +202,8 @@ func TestGenericPrincipalIDDomain(t *testing.T) { func TestGenericPrincipalIDDomain_BitBucket(t *testing.T) { // Test of https://github.com/influxdata/chronograf/issues/5399 t.Parallel() + expectedGroup := "xyz.io" + expectedPrincipalID := "pavel.zavora@xyz.io" emailsResponse := `{ "pagelen": 10, "values": [ @@ -241,7 +243,7 @@ func TestGenericPrincipalIDDomain_BitBucket(t *testing.T) { logger := clog.New(clog.ParseLevel("debug")) prov := oauth2.Generic{ Logger: logger, - Domains: []string{"xyz.io"}, + Domains: []string{expectedGroup}, } tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) if err != nil { @@ -256,8 +258,16 @@ func TestGenericPrincipalIDDomain_BitBucket(t *testing.T) { if err != nil { t.Fatal("Unexpected error while retrieiving PrincipalID: err:", err) } - want := "pavel.zavora@xyz.io" - if got != want { - t.Fatal("Retrieved email was not as expected. Want:", want, "Got:", got) + if got != expectedPrincipalID { + t.Fatal("Retrieved email was not as expected. Want:", expectedPrincipalID, "Got:", got) } + + group, err := prov.Group(tc) + if err != nil { + t.Fatal("Unexpected error while retrieiving PrincipalID: err:", err) + } + if group != expectedGroup { + t.Fatal("Retrieved group was not as expected. Want:", expectedGroup, "Got:", group) + } + }