mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
fix: add support for custom auth header with client secret (#10513)
This fixes OAuth2 with JFrog Artifactory.
This commit is contained in:
@ -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)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
2
site/src/api/typesGenerated.ts
generated
2
site/src/api/typesGenerated.ts
generated
@ -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",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user