fix: add support for custom auth header with client secret (#10513)

This fixes OAuth2 with JFrog Artifactory.
This commit is contained in:
Kyle Carberry
2023-11-03 12:26:30 -04:00
committed by GitHub
parent 21dc93c8a3
commit bb4ce87242
4 changed files with 72 additions and 0 deletions

View File

@ -457,6 +457,9 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) { if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
oauthConfig = &jwtConfig{oc} oauthConfig = &jwtConfig{oc}
} }
if entry.Type == string(codersdk.EnhancedExternalAuthProviderJFrog) {
oauthConfig = &exchangeWithClientSecret{oc}
}
cfg := &Config{ cfg := &Config{
OAuth2Config: oauthConfig, OAuth2Config: oauthConfig,
@ -619,3 +622,28 @@ func (c *jwtConfig) Exchange(ctx context.Context, code string, opts ...oauth2.Au
)..., )...,
) )
} }
// exchangeWithClientSecret wraps an OAuth config and adds the client secret
// to the Exchange request as a Bearer header. This is used by JFrog Artifactory.
type exchangeWithClientSecret struct {
*oauth2.Config
}
func (e *exchangeWithClientSecret) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
httpClient, ok := ctx.Value(oauth2.HTTPClient).(*http.Client)
if httpClient == nil || !ok {
httpClient = http.DefaultClient
}
oldTransport := httpClient.Transport
httpClient.Transport = roundTripper(func(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+e.ClientSecret)
return oldTransport.RoundTrip(req)
})
return e.Config.Exchange(context.WithValue(ctx, oauth2.HTTPClient, httpClient), code, opts...)
}
type roundTripper func(req *http.Request) (*http.Response, error)
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return r(req)
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/http/httptest"
"net/url" "net/url"
"testing" "testing"
"time" "time"
@ -298,6 +299,40 @@ func TestRefreshToken(t *testing.T) {
}) })
} }
func TestExchangeWithClientSecret(t *testing.T) {
t.Parallel()
// This ensures a provider that requires the custom
// client secret exchange works.
configs, err := externalauth.ConvertConfig([]codersdk.ExternalAuthConfig{{
// JFrog just happens to require this custom type.
Type: codersdk.EnhancedExternalAuthProviderJFrog.String(),
ClientID: "id",
ClientSecret: "secret",
}}, &url.URL{})
require.NoError(t, err)
config := configs[0]
client := &http.Client{
Transport: roundTripper(func(req *http.Request) (*http.Response, error) {
require.Equal(t, "Bearer secret", req.Header.Get("Authorization"))
rec := httptest.NewRecorder()
rec.WriteHeader(http.StatusOK)
body, err := json.Marshal(&oauth2.Token{
AccessToken: "bananas",
})
if err != nil {
return nil, err
}
_, err = rec.Write(body)
return rec.Result(), err
}),
}
_, err = config.Exchange(context.WithValue(context.Background(), oauth2.HTTPClient, client), "code")
require.NoError(t, err)
}
func TestConvertYAML(t *testing.T) { func TestConvertYAML(t *testing.T) {
t.Parallel() t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
@ -438,3 +473,9 @@ func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *ext
return fake, config, link return fake, config, link
} }
type roundTripper func(req *http.Request) (*http.Response, error)
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return r(req)
}

View File

@ -35,6 +35,7 @@ const (
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab" EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
EnhancedExternalAuthProviderBitBucket EnhancedExternalAuthProvider = "bitbucket" EnhancedExternalAuthProviderBitBucket EnhancedExternalAuthProvider = "bitbucket"
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack" EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
EnhancedExternalAuthProviderJFrog EnhancedExternalAuthProvider = "jfrog"
) )
type ExternalAuth struct { type ExternalAuth struct {

View File

@ -1684,12 +1684,14 @@ export type EnhancedExternalAuthProvider =
| "bitbucket" | "bitbucket"
| "github" | "github"
| "gitlab" | "gitlab"
| "jfrog"
| "slack"; | "slack";
export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [ export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [
"azure-devops", "azure-devops",
"bitbucket", "bitbucket",
"github", "github",
"gitlab", "gitlab",
"jfrog",
"slack", "slack",
]; ];