Merge pull request #5643 from influxdata/5399/oauth_bitbucket_emails

feat(oauth): support bitbucket primary email retrieval
pull/5654/head
Pavel Závora 2020-12-17 13:50:07 +01:00 committed by GitHub
commit 44cddd5953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 114 additions and 6 deletions

View File

@ -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 {
@ -188,12 +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.Primary != nil && m.Verified != nil && m.Email != 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

View File

@ -198,3 +198,76 @@ 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()
expectedGroup := "xyz.io"
expectedPrincipalID := "pavel.zavora@xyz.io"
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{expectedGroup},
}
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)
}
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)
}
}