mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
* Set JSON mimetype for `/idp/keys` * Fix typos * Test for header presence * Assert JSON for ` /.well-known/openid-configuration` as well
193 lines
5.6 KiB
Go
193 lines
5.6 KiB
Go
// Copyright (c) 2023 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 identityprovider
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/zitadel/oidc/pkg/oidc"
|
|
"gopkg.in/square/go-jose.v2"
|
|
)
|
|
|
|
const (
|
|
issuerBaseURL = "https://api.gitpod.io/idp"
|
|
)
|
|
|
|
func TestRouter(t *testing.T) {
|
|
type Expectation struct {
|
|
Error string
|
|
Response string
|
|
}
|
|
tests := []struct {
|
|
Name string
|
|
Expectation Expectation
|
|
ResponseExpectation func(*Service) string
|
|
ExpectedHeaders map[string]string
|
|
Path string
|
|
}{
|
|
{
|
|
Name: "OIDC discovery",
|
|
Path: oidc.DiscoveryEndpoint,
|
|
Expectation: Expectation{
|
|
Response: `{"issuer":"https://api.gitpod.io/idp","authorization_endpoint":"https://api.gitpod.io/idp/not-supported","token_endpoint":"https://api.gitpod.io/idp/not-supported","introspection_endpoint":"https://api.gitpod.io/idp/not-supported","userinfo_endpoint":"https://api.gitpod.io/idp/not-supported","revocation_endpoint":"https://api.gitpod.io/idp/not-supported","end_session_endpoint":"https://api.gitpod.io/idp/not-supported","jwks_uri":"https://api.gitpod.io/idp/keys","scopes_supported":["openid","profile","email","phone","address","offline_access"],"response_types_supported":["code","id_token","id_token token"],"grant_types_supported":["authorization_code","implicit"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"revocation_endpoint_auth_methods_supported":["none"],"introspection_endpoint_auth_methods_supported":["none"],"introspection_endpoint_auth_signing_alg_values_supported":["RS256"],"claims_supported":["sub","aud","exp","iat","iss","auth_time","nonce","acr","amr","c_hash","at_hash","act","scopes","client_id","azp","preferred_username","name","family_name","given_name","locale","email"],"request_uri_parameter_supported":false}` + "\n",
|
|
},
|
|
ExpectedHeaders: map[string]string{
|
|
"Content-Type": "application/json",
|
|
},
|
|
},
|
|
{
|
|
Name: "keys",
|
|
Path: "/keys",
|
|
ResponseExpectation: func(s *Service) string {
|
|
r, _ := s.keys.PublicKeys(context.Background())
|
|
return string(r)
|
|
},
|
|
ExpectedHeaders: map[string]string{
|
|
"Content-Type": "application/json",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
service, err := NewService(issuerBaseURL, NewInMemoryCache())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server := httptest.NewServer(service.Router())
|
|
t.Cleanup(server.Close)
|
|
|
|
resp, err := http.Get(server.URL + test.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
|
|
var act Expectation
|
|
act.Response = string(respBody)
|
|
if err != nil {
|
|
act.Error = err.Error()
|
|
}
|
|
|
|
if test.ResponseExpectation != nil {
|
|
test.Expectation.Response = test.ResponseExpectation(service)
|
|
}
|
|
|
|
if diff := cmp.Diff(test.Expectation, act); diff != "" {
|
|
t.Errorf("Router() mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
for name, expected := range test.ExpectedHeaders {
|
|
actual := resp.Header.Get(name)
|
|
if actual != expected {
|
|
t.Errorf("Unexpected value for header '%s'. got: '%s', want: '%s'", name, actual, expected)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIDToken(t *testing.T) {
|
|
type Expectation struct {
|
|
Error string
|
|
Token *jwt.Token
|
|
}
|
|
tests := []struct {
|
|
Name string
|
|
Expectation Expectation
|
|
Org string
|
|
Audience []string
|
|
UserInfo oidc.UserInfo
|
|
}{
|
|
{
|
|
Name: "all empty",
|
|
Expectation: Expectation{
|
|
Error: "audience cannot be empty",
|
|
},
|
|
},
|
|
{
|
|
Name: "just audience",
|
|
Audience: []string{"some.audience.com"},
|
|
Expectation: Expectation{
|
|
Error: "user info cannot be nil",
|
|
},
|
|
},
|
|
{
|
|
Name: "with user info",
|
|
Audience: []string{"some.audience.com"},
|
|
UserInfo: func() oidc.UserInfo {
|
|
userInfo := oidc.NewUserInfo()
|
|
userInfo.SetName("foo")
|
|
userInfo.SetSubject("bar")
|
|
return userInfo
|
|
}(),
|
|
Expectation: Expectation{
|
|
Token: &jwt.Token{
|
|
Method: &jwt.SigningMethodRSA{Name: "RS256", Hash: crypto.SHA256},
|
|
Header: map[string]interface{}{"alg": string(jose.RS256)},
|
|
Claims: jwt.MapClaims{
|
|
"aud": []any{string("some.audience.com")},
|
|
"azp": string("some.audience.com"),
|
|
"iss": string("https://api.gitpod.io/idp"),
|
|
"name": "foo",
|
|
"sub": "bar",
|
|
},
|
|
Valid: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
cache := NewInMemoryCache()
|
|
service, err := NewService(issuerBaseURL, cache)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var act Expectation
|
|
token, err := service.IDToken(context.TODO(), test.Org, test.Audience, test.UserInfo)
|
|
if err != nil {
|
|
act.Error = err.Error()
|
|
} else {
|
|
parsedToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { return &cache.current.PublicKey, nil })
|
|
if err != nil {
|
|
t.Fatalf("cannot parse IDToken result: %v", err)
|
|
}
|
|
act.Token = parsedToken
|
|
}
|
|
|
|
if diff := cmp.Diff(test.Expectation, act, cmpJWTToken()...); diff != "" {
|
|
t.Errorf("IDToken() mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func cmpJWTToken() []cmp.Option {
|
|
return []cmp.Option{
|
|
cmpopts.IgnoreFields(jwt.Token{}, "Raw", "Signature"),
|
|
cmpopts.IgnoreMapEntries(func(k string, v any) bool {
|
|
_, ignore := map[string]struct{}{
|
|
"auth_time": {},
|
|
"c_hash": {},
|
|
"exp": {},
|
|
"iat": {},
|
|
"iss": {},
|
|
}[k]
|
|
return ignore
|
|
}),
|
|
}
|
|
}
|