Skip to content

Commit c701a11

Browse files
committed
fix: add support for custom auth header with client secret
This fixes OAuth2 with JFrog Artifactory.
1 parent 08844d0 commit c701a11

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-0
lines changed

coderd/externalauth/externalauth.go

+28
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
457457
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
458458
oauthConfig = &jwtConfig{oc}
459459
}
460+
if entry.Type == string(codersdk.EnhancedExternalAuthProviderJFrog) {
461+
oauthConfig = &exchangeWithClientSecret{oc}
462+
}
460463

461464
cfg := &Config{
462465
OAuth2Config: oauthConfig,
@@ -619,3 +622,28 @@ func (c *jwtConfig) Exchange(ctx context.Context, code string, opts ...oauth2.Au
619622
)...,
620623
)
621624
}
625+
626+
// exchangeWithClientSecret wraps an OAuth config and adds the client secret
627+
// to the Exchange request as a Bearer header. This is used by JFrog Artifactory.
628+
type exchangeWithClientSecret struct {
629+
*oauth2.Config
630+
}
631+
632+
func (e *exchangeWithClientSecret) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
633+
httpClient, ok := ctx.Value(oauth2.HTTPClient).(*http.Client)
634+
if httpClient == nil || !ok {
635+
httpClient = http.DefaultClient
636+
}
637+
oldTransport := httpClient.Transport
638+
httpClient.Transport = roundTripper(func(req *http.Request) (*http.Response, error) {
639+
req.Header.Set("Authorization", "Bearer "+e.ClientSecret)
640+
return oldTransport.RoundTrip(req)
641+
})
642+
return e.Config.Exchange(context.WithValue(ctx, oauth2.HTTPClient, httpClient), code, opts...)
643+
}
644+
645+
type roundTripper func(req *http.Request) (*http.Response, error)
646+
647+
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
648+
return r(req)
649+
}

coderd/externalauth/externalauth_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"net/http"
7+
"net/http/httptest"
78
"net/url"
89
"testing"
910
"time"
@@ -298,6 +299,40 @@ func TestRefreshToken(t *testing.T) {
298299
})
299300
}
300301

302+
func TestExchangeWithClientSecret(t *testing.T) {
303+
t.Parallel()
304+
// This ensures a provider that requires the custom
305+
// client secret exchange works.
306+
configs, err := externalauth.ConvertConfig([]codersdk.ExternalAuthConfig{{
307+
// JFrog just happens to require this custom type.
308+
309+
Type: codersdk.EnhancedExternalAuthProviderJFrog.String(),
310+
ClientID: "id",
311+
ClientSecret: "secret",
312+
}}, &url.URL{})
313+
require.NoError(t, err)
314+
config := configs[0]
315+
316+
client := &http.Client{
317+
Transport: roundTripper(func(req *http.Request) (*http.Response, error) {
318+
require.Equal(t, "Bearer secret", req.Header.Get("Authorization"))
319+
rec := httptest.NewRecorder()
320+
rec.WriteHeader(http.StatusOK)
321+
body, err := json.Marshal(&oauth2.Token{
322+
AccessToken: "bananas",
323+
})
324+
if err != nil {
325+
return nil, err
326+
}
327+
_, err = rec.Write(body)
328+
return rec.Result(), err
329+
}),
330+
}
331+
332+
_, err = config.Exchange(context.WithValue(context.Background(), oauth2.HTTPClient, client), "code")
333+
require.NoError(t, err)
334+
}
335+
301336
func TestConvertYAML(t *testing.T) {
302337
t.Parallel()
303338
for _, tc := range []struct {
@@ -438,3 +473,9 @@ func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *ext
438473

439474
return fake, config, link
440475
}
476+
477+
type roundTripper func(req *http.Request) (*http.Response, error)
478+
479+
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
480+
return r(req)
481+
}

codersdk/externalauth.go

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const (
3535
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
3636
EnhancedExternalAuthProviderBitBucket EnhancedExternalAuthProvider = "bitbucket"
3737
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
38+
EnhancedExternalAuthProviderJFrog EnhancedExternalAuthProvider = "jfrog"
3839
)
3940

4041
type ExternalAuth struct {

site/src/api/typesGenerated.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)