-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bitbucket Server] Implements the GetMe method #128
base: bitbucket-server
Are you sure you want to change the base?
Changes from all commits
803c94c
18005d5
608e55a
0226189
4780f69
78359db
f5ee1bf
dac3b50
92655b0
0110aa0
6028704
477c06e
7abb299
1682d75
c2c210c
04549b3
e393753
d790e0a
d7fd5d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package bitbucketclient | ||
|
||
type Client interface { | ||
GetMe() (*BitbucketUser, error) | ||
} | ||
|
||
type BitbucketClient struct { | ||
ClientConfiguration | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package bitbucketclient | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// TODO: This will be changed to create the main structs when modularized | ||
type Link struct { | ||
Href string `json:"href"` | ||
} | ||
|
||
type BitbucketUser struct { | ||
AccountID int `json:"id"` | ||
Username string `json:"name"` | ||
Links struct { | ||
Self []Link `json:"self"` | ||
} `json:"links"` | ||
} | ||
|
||
type BitbucketServerClient struct { | ||
BitbucketClient | ||
} | ||
|
||
func newServerClient(config ClientConfiguration) Client { | ||
return &BitbucketServerClient{ | ||
BitbucketClient: BitbucketClient{ | ||
ClientConfiguration: ClientConfiguration{ | ||
SelfHostedURL: config.SelfHostedURL, | ||
SelfHostedAPIURL: config.SelfHostedAPIURL, | ||
APIClient: config.APIClient, | ||
OAuthClient: config.OAuthClient, | ||
LogError: config.LogError, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (c *BitbucketServerClient) getWhoAmI() (string, error) { | ||
requestURL := fmt.Sprintf("%s/plugins/servlet/applinks/whoami", c.SelfHostedURL) | ||
|
||
req, err := http.NewRequest("GET", requestURL, nil) | ||
if err != nil { | ||
return "", errors.Wrap(err, "unable to create request for getting whoami identity") | ||
} | ||
|
||
resp, err := c.OAuthClient.Do(req) | ||
if err != nil { | ||
return "", errors.Wrap(err, "failed to make the request for getting whoami identity") | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != 200 { | ||
return "", errors.Errorf("who am i returned non-200 status code: %d", resp.StatusCode) | ||
} | ||
|
||
user, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return "", errors.Wrap(err, "failed to make the request for getting whoami identity") | ||
} | ||
|
||
return string(user), nil | ||
} | ||
|
||
func (c *BitbucketServerClient) GetMe() (*BitbucketUser, error) { | ||
username, err := c.getWhoAmI() | ||
if err != nil { | ||
c.LogError("failed to get whoami identity", "error", err.Error()) | ||
return nil, err | ||
} | ||
|
||
resp, err := c.APIClient.DefaultApi.GetUser(username) | ||
if err != nil { | ||
c.LogError("failed to get user from bitbucket server", "error", err.Error()) | ||
return nil, err | ||
} | ||
|
||
jsonData, err := json.Marshal(resp.Values) | ||
if err != nil { | ||
c.LogError("failed to marshaling user from bitbucket server", "error", err.Error()) | ||
return nil, err | ||
} | ||
|
||
var user BitbucketUser | ||
err = json.Unmarshal(jsonData, &user) | ||
if err != nil { | ||
c.LogError("failed to parse user from bitbucket server", "error", err.Error()) | ||
return nil, err | ||
} | ||
|
||
return &user, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package bitbucketclient | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/mock" | ||
) | ||
|
||
type MockAPIClient struct { | ||
mock.Mock | ||
} | ||
|
||
func (m *MockAPIClient) GetUser(_ string) (*bitbucketv1.APIResponse, error) { | ||
args := m.Called() | ||
return args.Get(0).(*bitbucketv1.APIResponse), args.Error(1) | ||
} | ||
|
||
func TestGetWhoAmI(t *testing.T) { | ||
t.Run("successfully get who am i from oauth", func(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
_, err := w.Write([]byte("testuser")) | ||
if err != nil { | ||
t.Fatalf("Failed to write response: %v", err) | ||
} | ||
})) | ||
defer ts.Close() | ||
|
||
client := newServerClient(ClientConfiguration{SelfHostedURL: ts.URL, OAuthClient: ts.Client()}).(*BitbucketServerClient) | ||
|
||
username, err := client.getWhoAmI() | ||
|
||
assert.Nil(t, err) | ||
assert.Equal(t, "testuser", username) | ||
}) | ||
|
||
t.Run("return error when the status code is different than 200", func(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusBadRequest) | ||
})) | ||
defer ts.Close() | ||
|
||
client := newServerClient(ClientConfiguration{SelfHostedURL: ts.URL, OAuthClient: ts.Client()}).(*BitbucketServerClient) | ||
|
||
_, err := client.getWhoAmI() | ||
assert.Error(t, err) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package bitbucketclient | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
|
||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1" | ||
) | ||
|
||
type ClientConfiguration struct { | ||
SelfHostedURL string | ||
SelfHostedAPIURL string | ||
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been thinking that this |
||
APIClient *bitbucketv1.APIClient | ||
OAuthClient *http.Client | ||
|
||
LogError func(msg string, keyValuePairs ...interface{}) | ||
} | ||
|
||
func GetBitbucketClient(clientType string, config ClientConfiguration) (Client, error) { | ||
if clientType == "server" { | ||
return newServerClient(config), nil | ||
} | ||
return nil, fmt.Errorf("wrong client passed") | ||
} | ||
Comment on lines
+19
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just an idea, but I'm thinking having two different functions for creating the two clients may be more Go-idiomatic. So having a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we would make this function capitalized/exported if we want to expose it