Skip to content
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

add basic auth support #264

Merged
merged 1 commit into from
Apr 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 78 additions & 28 deletions auth.go
Original file line number Diff line number Diff line change
@@ -38,8 +38,17 @@
ctx context.Context //nolint:containedctx
conf *clientcredentials.Config
secretFile string
style authStyle
}

type authStyle int

const (
authStyleNotKnown authStyle = iota
authStyleInHeader
authStyleInParams
)

// Token refreshes the token by using a new client credentials request.
// tokens received this way do not include a refresh token.
func (c *fileTokenSource) Token() (*oauth2.Token, error) {
@@ -63,11 +72,30 @@
return nil, fmt.Errorf("oauth2: cannot read token file %q: %w", c.secretFile, err)
}

tk, err := retrieveToken(c.ctx, c.conf.ClientID, string(content), c.conf.TokenURL, v)
if err != nil {
return nil, err
var tk *oauth2.Token

switch {
case c.style == authStyleNotKnown, c.style == authStyleInHeader:
tk, err = retrieveToken(c.ctx, c.conf.TokenURL, c.conf.ClientID, string(content), v, authStyleInHeader)
if err == nil {
c.style = authStyleInHeader
return tk, nil

Check warning on line 82 in auth.go

Elisa-Codecov / codecov/patch

auth.go#L81-L82

Added lines #L81 - L82 were not covered by tests
}
if c.style == authStyleNotKnown {
tk, err = retrieveToken(c.ctx, c.conf.TokenURL, c.conf.ClientID, string(content), v, authStyleInParams)
if err == nil {
c.style = authStyleInParams
return tk, nil
}
}
case c.style == authStyleInParams:
tk, err = retrieveToken(c.ctx, c.conf.TokenURL, c.conf.ClientID, string(content), v, authStyleInParams)
if err == nil {
c.style = authStyleInParams
return tk, nil
}
}
return tk, nil
return nil, err

Check warning on line 98 in auth.go

Elisa-Codecov / codecov/patch

auth.go#L98

Added line #L98 was not covered by tests
}

func getClient(ctx context.Context) *http.Client {
@@ -77,35 +105,38 @@
return nil
}

func retrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*oauth2.Token, error) {
func buildHeadersAndBody(clientID, clientSecret string, v url.Values, style authStyle) (map[string]string, url.Values) {
headers := map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
}
switch style {
case authStyleInHeader, authStyleNotKnown:
headers["Authorization"] = "Basic " + BasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
case authStyleInParams:
v.Set("client_id", clientID)
v.Set("client_secret", clientSecret)
}
return headers, v
}

func retrieveToken(ctx context.Context, tokenURL, clientID, clientSecret string, v url.Values, style authStyle) (*oauth2.Token, error) {
client := http.DefaultClient
if c := getClient(ctx); c != nil {
client = c
}

// TODO: missing support for plain/form post body, missing support for client id and secret in basic auth header
v.Set("client_id", clientID)
v.Set("client_secret", clientSecret)
encoded := v.Encode()
tj := tokenJSON{}
_, err := MakeRequest(
ctx,
HTTPRequest{
URL: tokenURL,
Method: "POST",
Body: []byte(encoded),
OKCode: []int{200},
Headers: map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
},
},
&tj,
client,
Backoff{
Duration: 100 * time.Millisecond,
MaxRetries: 2,
},
)
headers, v := buildHeadersAndBody(clientID, clientSecret, v, style)
req := HTTPRequest{
URL: tokenURL,
Method: "POST",
Body: []byte(v.Encode()),
OKCode: []int{200},
Headers: headers,
}

var tj *tokenJSON
var err error
tj, err = makeRequest(ctx, client, req)
if err != nil {
return nil, err
}
@@ -126,6 +157,25 @@
return token, err
}

func makeRequest(ctx context.Context, client *http.Client, req HTTPRequest) (*tokenJSON, error) {
// TODO: missing support for plain/form post body
tj := &tokenJSON{}
_, err := MakeRequest(
ctx,
req,
&tj,
client,
Backoff{
Duration: 100 * time.Millisecond,
MaxRetries: 2,
},
)
if err != nil {
return nil, err
}
return tj, nil
}

type tokenJSON struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
16 changes: 10 additions & 6 deletions auth_test.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (
"net/http/httptest"
"net/url"
"testing"
"time"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
@@ -70,12 +71,15 @@ func TestNewClientToken(t *testing.T) {
ctx := context.Background()
c := NewClient(ctx, creds, "secret", "./testdata/token")

req, err := http.NewRequest("GET", fmt.Sprintf("%s?foo=bar", srv.URL), nil)
require.NoError(t, err)
for i := 0; i < 3; i++ {
req, err := http.NewRequest("GET", fmt.Sprintf("%s?foo=bar", srv.URL), nil)
require.NoError(t, err)

resp, err := c.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
resp, err := c.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
time.Sleep(1 * time.Second)
}
}

type tokenRequest struct {
@@ -122,7 +126,7 @@ func mockSrv(secret string) *httptest.Server {
c.JSON(http.StatusOK, gin.H{
"access_token": "token",
"token_type": "Bearer",
"expires_in": 3600,
"expires_in": 1,
})
})
return httptest.NewServer(r)