Unverified Commit cd73cda9 authored by Sander van Harmelen's avatar Sander van Harmelen Committed by GitHub

Improve using tokens (#398)

* Improve the way we handle tokens

* Use Go's OAuth2 package (#397)

* Use Go's OAuth2 package

* Pass HTTP client to OAuth2 via context
parent f14f44c8
...@@ -32,6 +32,7 @@ import ( ...@@ -32,6 +32,7 @@ import (
"time" "time"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
"golang.org/x/oauth2"
) )
const ( const (
...@@ -48,7 +49,8 @@ type authType int ...@@ -48,7 +49,8 @@ type authType int
// //
// GitLab API docs: https://docs.gitlab.com/ce/api/ // GitLab API docs: https://docs.gitlab.com/ce/api/
const ( const (
oAuthToken authType = iota basicAuth authType = iota
oAuthToken
privateToken privateToken
) )
...@@ -256,10 +258,13 @@ type Client struct { ...@@ -256,10 +258,13 @@ type Client struct {
// should always be specified with a trailing slash. // should always be specified with a trailing slash.
baseURL *url.URL baseURL *url.URL
// token type used to make authenticated API calls. // Token type used to make authenticated API calls.
authType authType authType authType
// token used to make authenticated API calls. // Username and password used for basix authentication.
username, password string
// Token used to make authenticated API calls.
token string token string
// User agent used when communicating with the GitLab API. // User agent used when communicating with the GitLab API.
...@@ -342,68 +347,33 @@ func NewClient(httpClient *http.Client, token string) *Client { ...@@ -342,68 +347,33 @@ func NewClient(httpClient *http.Client, token string) *Client {
// authentication, provide a valid username and password. // authentication, provide a valid username and password.
func NewBasicAuthClient(httpClient *http.Client, endpoint, username, password string) (*Client, error) { func NewBasicAuthClient(httpClient *http.Client, endpoint, username, password string) (*Client, error) {
client := newClient(httpClient) client := newClient(httpClient)
client.authType = oAuthToken client.authType = basicAuth
client.username = username
client.password = password
client.SetBaseURL(endpoint + "/api/v4") client.SetBaseURL(endpoint + "/api/v4")
// We get the first token before the loop to confirm the credentials err := client.requestOAuthToken(context.TODO())
t, err := requestOAuthToken(client, endpoint, username, password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// And then we assign the token inside the loop, so we can keep reassigning
// to renew the token every x seconds (just before it expiated)
go func() {
for {
client.token = t.AccessToken
if t.ExpiresIn == 0 {
return // Token does not expire
}
if t.ExpiresIn > 60 {
t.ExpiresIn -= 60
}
time.Sleep(t.ExpiresIn * time.Second)
t, err = requestOAuthToken(client, endpoint, username, password)
if err != nil {
panic(err)
}
}
}()
return client, nil return client, nil
} }
type token struct { func (c *Client) requestOAuthToken(ctx context.Context) error {
AccessToken string `json:"access_token"` config := &oauth2.Config{
ExpiresIn time.Duration `json:"expires_in"` Endpoint: oauth2.Endpoint{
} AuthURL: fmt.Sprintf("%s://%s/oauth/authorize", c.BaseURL().Scheme, c.BaseURL().Host),
TokenURL: fmt.Sprintf("%s://%s/oauth/token", c.BaseURL().Scheme, c.BaseURL().Host),
func requestOAuthToken(client *Client, endpoint, username, password string) (*token, error) { },
body := []byte(fmt.Sprintf(
`{ "grant_type": "password", "username": %q, "password": %q }`, username, password))
req, err := http.NewRequest("POST", endpoint+"/oauth/token", bytes.NewBuffer(body))
if err != nil {
return nil, err
} }
req.Header.Set("Content-Type", "application/json") ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client)
t, err := config.PasswordCredentialsToken(ctx, c.username, c.password)
resp, err := client.client.Do(req)
if err != nil { if err != nil {
return nil, err return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected response when refreshing oauth token: %s", resp.Status)
} }
c.token = t.AccessToken
t := new(token) return nil
err = json.NewDecoder(resp.Body).Decode(t)
return t, err
} }
// NewOAuthClient returns a new GitLab API client. If a nil httpClient is // NewOAuthClient returns a new GitLab API client. If a nil httpClient is
...@@ -557,10 +527,10 @@ func (c *Client) NewRequest(method, path string, opt interface{}, options []Opti ...@@ -557,10 +527,10 @@ func (c *Client) NewRequest(method, path string, opt interface{}, options []Opti
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
switch c.authType { switch c.authType {
case basicAuth, oAuthToken:
req.Header.Set("Authorization", "Bearer "+c.token)
case privateToken: case privateToken:
req.Header.Set("PRIVATE-TOKEN", c.token) req.Header.Set("PRIVATE-TOKEN", c.token)
case oAuthToken:
req.Header.Set("Authorization", "Bearer "+c.token)
} }
if c.UserAgent != "" { if c.UserAgent != "" {
...@@ -639,6 +609,14 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { ...@@ -639,6 +609,14 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth {
err = c.requestOAuthToken(req.Context())
if err != nil {
return nil, err
}
return c.Do(req, v)
}
response := newResponse(resp) response := newResponse(resp)
err = CheckResponse(resp) err = CheckResponse(resp)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment