@@ -3,16 +3,19 @@ package externalauth_test
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+ "fmt"
6
7
"net/http"
7
8
"net/http/httptest"
8
9
"net/url"
10
+ "strings"
9
11
"testing"
10
12
"time"
11
13
12
14
"github.com/coreos/go-oidc/v3/oidc"
13
15
"github.com/golang-jwt/jwt/v4"
14
16
"github.com/google/uuid"
15
17
"github.com/prometheus/client_golang/prometheus"
18
+ "github.com/stretchr/testify/assert"
16
19
"github.com/stretchr/testify/require"
17
20
"golang.org/x/oauth2"
18
21
"golang.org/x/xerrors"
@@ -417,6 +420,78 @@ func TestConvertYAML(t *testing.T) {
417
420
})
418
421
}
419
422
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
+
420
495
type testConfig struct {
421
496
FakeIDPOpts []oidctest.FakeIDPOpt
422
497
CoderOIDCConfigOpts []func (cfg * coderd.OIDCConfig )
@@ -433,6 +508,10 @@ type testConfig struct {
433
508
func setupOauth2Test (t * testing.T , settings testConfig ) (* oidctest.FakeIDP , * externalauth.Config , database.ExternalAuthLink ) {
434
509
t .Helper ()
435
510
511
+ if settings .ExternalAuthOpt == nil {
512
+ settings .ExternalAuthOpt = func (_ * externalauth.Config ) {}
513
+ }
514
+
436
515
const providerID = "test-idp"
437
516
fake := oidctest .NewFakeIDP (t ,
438
517
append ([]oidctest.FakeIDPOpt {}, settings .FakeIDPOpts ... )... ,
0 commit comments