Path: blob/main/components/public-api-server/pkg/oidc/router_test.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package oidc56import (7"net/http"8"net/http/httptest"9"testing"10"time"1112"github.com/go-chi/chi/v5"13"github.com/golang-jwt/jwt/v5"14"github.com/stretchr/testify/require"15"golang.org/x/oauth2"1617goidc "github.com/coreos/go-oidc/v3/oidc"18)1920func TestRoute_start(t *testing.T) {21// setup fake OIDC service22idpUrl := newFakeIdP(t)2324// setup test server with client routes25baseUrl, _, configId, _ := newTestServer(t, testServerParams{26issuer: idpUrl,27state: StateParams{28ReturnToURL: "",29},30})3132// go to /start33// don't follow redirect34client := &http.Client{35Timeout: 10 * time.Second,36CheckRedirect: func(req *http.Request, via []*http.Request) error {37return http.ErrUseLastResponse38},39}40resp, err := client.Get(baseUrl + "/oidc/start?id=" + configId + "&activate=true")41require.NoError(t, err)42defer resp.Body.Close()4344require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)45redirectUrl, err := resp.Location()46require.NoError(t, err)47require.Contains(t, redirectUrl.String(), idpUrl, "should redirect to IdP")4849state := redirectUrl.Query().Get("state")50require.NotEmpty(t, state, "should contain state param")5152token, _, err := new(jwt.Parser).ParseUnverified(state, jwt.MapClaims{})53require.NoError(t, err, "state param should be a JWT")54claims, ok := token.Claims.(jwt.MapClaims)55require.True(t, ok)5657stateParams, ok := claims["stateParams"].(map[string]interface{})58require.True(t, ok, "JWT is missing 'stateParams'")59require.Equal(t, true, stateParams["activate"], "`activate` is missing in state")60require.Equal(t, "/", stateParams["returnTo"], "`returnTo` is missing in state")61}6263func TestRoute_callback(t *testing.T) {64// setup fake OIDC service65idpUrl := newFakeIdP(t)6667// setup test server with client routes68baseUrl, stateParam, _, service := newTestServer(t, testServerParams{69clientID: "client-id",70issuer: idpUrl,71state: StateParams{72ReturnToURL: "/relative/url/to/some/page",73},74})75state, err := service.encodeStateParam(*stateParam)76require.NoError(t, err)7778// hit the /callback endpoint79client := &http.Client{80Timeout: 10 * time.Second,81CheckRedirect: func(req *http.Request, via []*http.Request) error {82return http.ErrUseLastResponse83},84}85req, err := http.NewRequest("GET", baseUrl+"/oidc/callback?code=123&state="+state, nil)86require.NoError(t, err)87req.AddCookie(&http.Cookie{88Name: "state", Value: state, MaxAge: 60,89})90req.AddCookie(&http.Cookie{91Name: "nonce", Value: "111", MaxAge: 60,92})93resp, err := client.Do(req)94require.NoError(t, err)95defer resp.Body.Close()9697require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode, "callback should response with redirect (307)")98require.NotEmpty(t, resp.Cookies(), "missing cookies on redirect")99require.Equal(t, "test-cookie", resp.Cookies()[0].Name, "missing cookie on redirect")100101url, err := resp.Location()102require.NoError(t, err)103require.Equal(t, "/relative/url/to/some/page", url.Path, "callback redirects properly")104105}106107func TestRoute_callback_verify_only(t *testing.T) {108// setup fake OIDC service109idpUrl := newFakeIdP(t)110111// setup test server with client routes112baseUrl, stateParam, _, service := newTestServer(t, testServerParams{113clientID: "client-id",114issuer: idpUrl,115state: StateParams{116ReturnToURL: "/relative/url/to/some/page",117Verify: true,118},119})120state, err := service.encodeStateParam(*stateParam)121require.NoError(t, err)122123// hit the /callback endpoint124client := &http.Client{125Timeout: 10 * time.Second,126CheckRedirect: func(req *http.Request, via []*http.Request) error {127return http.ErrUseLastResponse128},129}130req, err := http.NewRequest("GET", baseUrl+"/oidc/callback?code=123&state="+state, nil)131require.NoError(t, err)132req.AddCookie(&http.Cookie{133Name: "state", Value: state, MaxAge: 60,134})135req.AddCookie(&http.Cookie{136Name: "nonce", Value: "111", MaxAge: 60,137})138resp, err := client.Do(req)139require.NoError(t, err)140defer resp.Body.Close()141142require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode, "callback should response with redirect (307)")143require.Len(t, resp.Cookies(), 0, "unexpected session cookie on verify request")144145url, err := resp.Location()146require.NoError(t, err)147require.Equal(t, "/relative/url/to/some/page", url.Path, "callback redirects properly")148}149150type testServerParams struct {151issuer string152clientID string153state StateParams154}155156func newTestServer(t *testing.T, params testServerParams) (url string, state *StateParams, configId string, oidcService *Service) {157router := chi.NewRouter()158oidcService, dbConn := setupOIDCServiceForTests(t)159router.Mount("/oidc", Router(oidcService))160161ts := httptest.NewServer(router)162url = ts.URL163164oidcConfig := &goidc.Config{165ClientID: params.clientID,166SkipClientIDCheck: true,167SkipIssuerCheck: true,168SkipExpiryCheck: true,169InsecureSkipSignatureCheck: true,170}171oauth2Config := &oauth2.Config{172ClientID: params.clientID,173ClientSecret: "secret",174Scopes: []string{goidc.ScopeOpenID, "profile", "email"},175}176clientConfig := &ClientConfig{177Issuer: params.issuer,178OAuth2Config: oauth2Config,179VerifierConfig: oidcConfig,180}181config, _ := createConfig(t, dbConn, clientConfig)182configId = config.ID.String()183184stateParam := &StateParams{185ClientConfigID: configId,186ReturnToURL: params.state.ReturnToURL,187Activate: params.state.Activate,188Verify: params.state.Verify,189}190191return url, stateParam, configId, oidcService192}193194195