@@ -3,16 +3,19 @@ package externalauth_test
33import (
44 "context"
55 "encoding/json"
6+ "fmt"
67 "net/http"
78 "net/http/httptest"
89 "net/url"
10+ "strings"
911 "testing"
1012 "time"
1113
1214 "github.com/coreos/go-oidc/v3/oidc"
1315 "github.com/golang-jwt/jwt/v4"
1416 "github.com/google/uuid"
1517 "github.com/prometheus/client_golang/prometheus"
18+ "github.com/stretchr/testify/assert"
1619 "github.com/stretchr/testify/require"
1720 "golang.org/x/oauth2"
1821 "golang.org/x/xerrors"
@@ -417,6 +420,78 @@ func TestConvertYAML(t *testing.T) {
417420 })
418421}
419422
423+ // TestConstantQueryParams verifies a constant query parameter can be set in the
424+ // "authenticate" url for external auth applications, and it will be carried forward
425+ // to actual auth requests.
426+ // This unit test was specifically created for Auth0 which can set an
427+ // audience query parameter in it's /authorize endpoint.
428+ func TestConstantQueryParams (t * testing.T ) {
429+ t .Parallel ()
430+ const constantQueryParamKey = "audience"
431+ const constantQueryParamValue = "foobar"
432+ constantQueryParam := fmt .Sprintf ("%s=%s" , constantQueryParamKey , constantQueryParamValue )
433+ fake , config , _ := setupOauth2Test (t , testConfig {
434+ FakeIDPOpts : []oidctest.FakeIDPOpt {
435+ oidctest .WithMiddlewares (func (next http.Handler ) http.Handler {
436+ return http .HandlerFunc (func (writer http.ResponseWriter , request * http.Request ) {
437+ if strings .Contains (request .URL .Path , "authorize" ) {
438+ // Assert has the audience query param
439+ assert .Equal (t , request .URL .Query ().Get (constantQueryParamKey ), constantQueryParamValue )
440+ }
441+ next .ServeHTTP (writer , request )
442+ })
443+ }),
444+ },
445+ CoderOIDCConfigOpts : []func (cfg * coderd.OIDCConfig ){
446+ func (cfg * coderd.OIDCConfig ) {
447+ // Include a constant query parameter.
448+ authURL , err := url .Parse (cfg .OAuth2Config .(* oauth2.Config ).Endpoint .AuthURL )
449+ require .NoError (t , err )
450+
451+ authURL .RawQuery = url.Values {constantQueryParamKey : []string {constantQueryParamValue }}.Encode ()
452+ cfg .OAuth2Config .(* oauth2.Config ).Endpoint .AuthURL = authURL .String ()
453+ require .Contains (t , cfg .OAuth2Config .(* oauth2.Config ).Endpoint .AuthURL , constantQueryParam )
454+ },
455+ },
456+ })
457+
458+ callbackCalled := false
459+ fake .SetCoderdCallbackHandler (func (writer http.ResponseWriter , request * http.Request ) {
460+ // Just record the callback was hit, and the auth succeeded.
461+ callbackCalled = true
462+ })
463+
464+ // Verify the AuthURL endpoint contains the constant query parameter and is a valid URL.
465+ // It should look something like:
466+ // http://127.0.0.1:<port>>/oauth2/authorize?
467+ // audience=foobar&
468+ // client_id=d<uuid>&
469+ // redirect_uri=<redirect>&
470+ // response_type=code&
471+ // scope=openid+email+profile&
472+ // state=state
473+ const state = "state"
474+ rawAuthURL := config .AuthCodeURL (state )
475+ // Parsing the url is not perfect. It allows imperfections like the query
476+ // params having 2 question marks '?a=foo?b=bar'.
477+ // So use it to validate, then verify the raw url is as expected.
478+ authURL , err := url .Parse (rawAuthURL )
479+ require .NoError (t , err )
480+ require .Equal (t , authURL .Query ().Get (constantQueryParamKey ), constantQueryParamValue )
481+ // We are not using a real server, so it fakes https://coder.com
482+ require .Equal (t , authURL .Scheme , "https" )
483+ // Validate the raw URL.
484+ // Double check only 1 '?' exists. Url parsing allows multiple '?' in the query string.
485+ require .Equal (t , strings .Count (rawAuthURL , "?" ), 1 )
486+
487+ // Actually run an auth request. Although it says OIDC, the flow is the same
488+ // for oauth2.
489+ //nolint:bodyclose
490+ resp := fake .OIDCCallback (t , state , jwt.MapClaims {})
491+ require .True (t , callbackCalled )
492+ require .Equal (t , http .StatusOK , resp .StatusCode )
493+ }
494+
420495type testConfig struct {
421496 FakeIDPOpts []oidctest.FakeIDPOpt
422497 CoderOIDCConfigOpts []func (cfg * coderd.OIDCConfig )
@@ -433,6 +508,10 @@ type testConfig struct {
433508func setupOauth2Test (t * testing.T , settings testConfig ) (* oidctest.FakeIDP , * externalauth.Config , database.ExternalAuthLink ) {
434509 t .Helper ()
435510
511+ if settings .ExternalAuthOpt == nil {
512+ settings .ExternalAuthOpt = func (_ * externalauth.Config ) {}
513+ }
514+
436515 const providerID = "test-idp"
437516 fake := oidctest .NewFakeIDP (t ,
438517 append ([]oidctest.FakeIDPOpt {}, settings .FakeIDPOpts ... )... ,
0 commit comments