mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
* [papi] Add `verified` flag to config and `ActivateClientConfig` to OIDC service * [papi] implement `ActivateClientConfig` * [gitpod-db/go] add `setClientConfigVerifiedFlag` * [gitpod-db/migration] add `d_b_oidc_client_config.verified` field * [papi] Don't deactive on UpdateClientConfig * [gitpod-db/go] add missing `Verified` field * On "activate" request also mark as verified. * [gitpod-db/go] fix mapping of `Verified` field * [papi] ensure only verified OIDC client configs can be activated * [papi] Skip the sign-in on verify-only requests. * [papi] fix skipped tests * [papi] fix mapping of OIDC configs * rename RPC method * fix tests after adding validation of claims * fix: activation of record should deactivate others * fix: update should unverify the entry * remove Debug() * [db-migration] fix: mark active entries as verified
194 lines
5.6 KiB
Go
194 lines
5.6 KiB
Go
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License.AGPL.txt in the project root for license information.
|
|
|
|
package oidc
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/oauth2"
|
|
|
|
goidc "github.com/coreos/go-oidc/v3/oidc"
|
|
)
|
|
|
|
func TestRoute_start(t *testing.T) {
|
|
// setup fake OIDC service
|
|
idpUrl := newFakeIdP(t)
|
|
|
|
// setup test server with client routes
|
|
baseUrl, _, configId, _ := newTestServer(t, testServerParams{
|
|
issuer: idpUrl,
|
|
state: StateParams{
|
|
ReturnToURL: "",
|
|
},
|
|
})
|
|
|
|
// go to /start
|
|
// don't follow redirect
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
resp, err := client.Get(baseUrl + "/oidc/start?id=" + configId + "&activate=true")
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
|
redirectUrl, err := resp.Location()
|
|
require.NoError(t, err)
|
|
require.Contains(t, redirectUrl.String(), idpUrl, "should redirect to IdP")
|
|
|
|
state := redirectUrl.Query().Get("state")
|
|
require.NotEmpty(t, state, "should contain state param")
|
|
|
|
token, _, err := new(jwt.Parser).ParseUnverified(state, jwt.MapClaims{})
|
|
require.NoError(t, err, "state param should be a JWT")
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
require.True(t, ok)
|
|
|
|
stateParams, ok := claims["stateParams"].(map[string]interface{})
|
|
require.True(t, ok, "JWT is missing 'stateParams'")
|
|
require.Equal(t, true, stateParams["activate"], "`activate` is missing in state")
|
|
require.Equal(t, "/", stateParams["returnTo"], "`returnTo` is missing in state")
|
|
}
|
|
|
|
func TestRoute_callback(t *testing.T) {
|
|
// setup fake OIDC service
|
|
idpUrl := newFakeIdP(t)
|
|
|
|
// setup test server with client routes
|
|
baseUrl, stateParam, _, service := newTestServer(t, testServerParams{
|
|
clientID: "client-id",
|
|
issuer: idpUrl,
|
|
state: StateParams{
|
|
ReturnToURL: "/relative/url/to/some/page",
|
|
},
|
|
})
|
|
state, err := service.encodeStateParam(*stateParam)
|
|
require.NoError(t, err)
|
|
|
|
// hit the /callback endpoint
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
req, err := http.NewRequest("GET", baseUrl+"/oidc/callback?code=123&state="+state, nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "state", Value: state, MaxAge: 60,
|
|
})
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "nonce", Value: "111", MaxAge: 60,
|
|
})
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode, "callback should response with redirect (307)")
|
|
require.NotEmpty(t, resp.Cookies(), "missing cookies on redirect")
|
|
require.Equal(t, "test-cookie", resp.Cookies()[0].Name, "missing cookie on redirect")
|
|
|
|
url, err := resp.Location()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "/relative/url/to/some/page", url.Path, "callback redirects properly")
|
|
|
|
}
|
|
|
|
func TestRoute_callback_verify_only(t *testing.T) {
|
|
// setup fake OIDC service
|
|
idpUrl := newFakeIdP(t)
|
|
|
|
// setup test server with client routes
|
|
baseUrl, stateParam, _, service := newTestServer(t, testServerParams{
|
|
clientID: "client-id",
|
|
issuer: idpUrl,
|
|
state: StateParams{
|
|
ReturnToURL: "/relative/url/to/some/page",
|
|
Verify: true,
|
|
},
|
|
})
|
|
state, err := service.encodeStateParam(*stateParam)
|
|
require.NoError(t, err)
|
|
|
|
// hit the /callback endpoint
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
req, err := http.NewRequest("GET", baseUrl+"/oidc/callback?code=123&state="+state, nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "state", Value: state, MaxAge: 60,
|
|
})
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "nonce", Value: "111", MaxAge: 60,
|
|
})
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode, "callback should response with redirect (307)")
|
|
require.Len(t, resp.Cookies(), 0, "unexpected session cookie on verify request")
|
|
|
|
url, err := resp.Location()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "/relative/url/to/some/page", url.Path, "callback redirects properly")
|
|
}
|
|
|
|
type testServerParams struct {
|
|
issuer string
|
|
clientID string
|
|
state StateParams
|
|
}
|
|
|
|
func newTestServer(t *testing.T, params testServerParams) (url string, state *StateParams, configId string, oidcService *Service) {
|
|
router := chi.NewRouter()
|
|
oidcService, dbConn := setupOIDCServiceForTests(t)
|
|
router.Mount("/oidc", Router(oidcService))
|
|
|
|
ts := httptest.NewServer(router)
|
|
url = ts.URL
|
|
|
|
oidcConfig := &goidc.Config{
|
|
ClientID: params.clientID,
|
|
SkipClientIDCheck: true,
|
|
SkipIssuerCheck: true,
|
|
SkipExpiryCheck: true,
|
|
InsecureSkipSignatureCheck: true,
|
|
}
|
|
oauth2Config := &oauth2.Config{
|
|
ClientID: params.clientID,
|
|
ClientSecret: "secret",
|
|
Scopes: []string{goidc.ScopeOpenID, "profile", "email"},
|
|
}
|
|
clientConfig := &ClientConfig{
|
|
Issuer: params.issuer,
|
|
OAuth2Config: oauth2Config,
|
|
VerifierConfig: oidcConfig,
|
|
}
|
|
config, _ := createConfig(t, dbConn, clientConfig)
|
|
configId = config.ID.String()
|
|
|
|
stateParam := &StateParams{
|
|
ClientConfigID: configId,
|
|
ReturnToURL: params.state.ReturnToURL,
|
|
Activate: params.state.Activate,
|
|
Verify: params.state.Verify,
|
|
}
|
|
|
|
return url, stateParam, configId, oidcService
|
|
}
|