Path: blob/main/components/public-api-server/pkg/identityprovider/idp_test.go
2500 views
// Copyright (c) 2023 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 identityprovider56import (7"context"8"crypto"9"io"10"net/http"11"net/http/httptest"12"testing"1314"github.com/golang-jwt/jwt/v5"15"github.com/google/go-cmp/cmp"16"github.com/google/go-cmp/cmp/cmpopts"17"github.com/zitadel/oidc/pkg/oidc"18"gopkg.in/square/go-jose.v2"19)2021const (22issuerBaseURL = "https://api.gitpod.io/idp"23)2425func TestRouter(t *testing.T) {26type Expectation struct {27Error string28Response string29}30tests := []struct {31Name string32Expectation Expectation33ResponseExpectation func(*Service) string34ExpectedHeaders map[string]string35Path string36}{37{38Name: "OIDC discovery",39Path: oidc.DiscoveryEndpoint,40Expectation: Expectation{41Response: `{"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",42},43ExpectedHeaders: map[string]string{44"Content-Type": "application/json",45},46},47{48Name: "keys",49Path: "/keys",50ResponseExpectation: func(s *Service) string {51r, _ := s.keys.PublicKeys(context.Background())52return string(r)53},54ExpectedHeaders: map[string]string{55"Content-Type": "application/json",56},57},58}5960for _, test := range tests {61t.Run(test.Name, func(t *testing.T) {62service, err := NewService(issuerBaseURL, NewInMemoryCache())63if err != nil {64t.Fatal(err)65}66server := httptest.NewServer(service.Router())67t.Cleanup(server.Close)6869resp, err := http.Get(server.URL + test.Path)70if err != nil {71t.Fatal(err)72}73respBody, err := io.ReadAll(resp.Body)7475var act Expectation76act.Response = string(respBody)77if err != nil {78act.Error = err.Error()79}8081if test.ResponseExpectation != nil {82test.Expectation.Response = test.ResponseExpectation(service)83}8485if diff := cmp.Diff(test.Expectation, act); diff != "" {86t.Errorf("Router() mismatch (-want +got):\n%s", diff)87}8889for name, expected := range test.ExpectedHeaders {90actual := resp.Header.Get(name)91if actual != expected {92t.Errorf("Unexpected value for header '%s'. got: '%s', want: '%s'", name, actual, expected)93}94}95})96}97}9899func TestIDToken(t *testing.T) {100type Expectation struct {101Error string102Token *jwt.Token103}104tests := []struct {105Name string106Expectation Expectation107Org string108Audience []string109UserInfo oidc.UserInfo110}{111{112Name: "all empty",113Expectation: Expectation{114Error: "audience cannot be empty",115},116},117{118Name: "just audience",119Audience: []string{"some.audience.com"},120Expectation: Expectation{121Error: "user info cannot be nil",122},123},124{125Name: "with user info",126Audience: []string{"some.audience.com"},127UserInfo: func() oidc.UserInfo {128userInfo := oidc.NewUserInfo()129userInfo.SetName("foo")130userInfo.SetSubject("bar")131return userInfo132}(),133Expectation: Expectation{134Token: &jwt.Token{135Method: &jwt.SigningMethodRSA{Name: "RS256", Hash: crypto.SHA256},136Header: map[string]interface{}{"alg": string(jose.RS256)},137Claims: jwt.MapClaims{138"aud": []any{string("some.audience.com")},139"azp": string("some.audience.com"),140"iss": string("https://api.gitpod.io/idp"),141"name": "foo",142"sub": "bar",143},144Valid: true,145},146},147},148{149Name: "with custom claims",150Audience: []string{"some.audience.com"},151UserInfo: func() oidc.UserInfo {152userInfo := oidc.NewUserInfo()153userInfo.AppendClaims("scope", "foobar")154return userInfo155}(),156Expectation: Expectation{157Token: &jwt.Token{158Method: &jwt.SigningMethodRSA{Name: "RS256", Hash: crypto.SHA256},159Header: map[string]interface{}{"alg": string(jose.RS256)},160Claims: jwt.MapClaims{161"aud": []any{string("some.audience.com")},162"azp": string("some.audience.com"),163"iss": string("https://api.gitpod.io/idp"),164"scope": "foobar",165},166Valid: true,167},168},169},170}171172for _, test := range tests {173t.Run(test.Name, func(t *testing.T) {174cache := NewInMemoryCache()175service, err := NewService(issuerBaseURL, cache)176if err != nil {177t.Fatal(err)178}179180var act Expectation181token, err := service.IDToken(context.TODO(), test.Org, test.Audience, test.UserInfo)182if err != nil {183act.Error = err.Error()184} else {185parsedToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { return &cache.current.PublicKey, nil })186if err != nil {187t.Fatalf("cannot parse IDToken result: %v", err)188}189act.Token = parsedToken190}191192if diff := cmp.Diff(test.Expectation, act, cmpJWTToken()...); diff != "" {193t.Errorf("IDToken() mismatch (-want +got):\n%s", diff)194}195})196}197}198199func cmpJWTToken() []cmp.Option {200return []cmp.Option{201cmpopts.IgnoreFields(jwt.Token{}, "Raw", "Signature"),202cmpopts.IgnoreMapEntries(func(k string, v any) bool {203_, ignore := map[string]struct{}{204"auth_time": {},205"c_hash": {},206"exp": {},207"iat": {},208"iss": {},209}[k]210return ignore211}),212}213}214215216