Path: blob/main/components/public-api-server/pkg/identityprovider/idp.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/rand"9"crypto/rsa"10"encoding/json"11"fmt"12"io"13"net/http"14"net/url"15"time"1617"github.com/gitpod-io/gitpod/common-go/log"18"github.com/go-chi/chi/v5"19"github.com/zitadel/oidc/pkg/crypto"20"github.com/zitadel/oidc/pkg/oidc"21"github.com/zitadel/oidc/pkg/op"22"gopkg.in/square/go-jose.v2"23)2425func NewService(issuerBaseURL string, keyCache KeyCache) (*Service, error) {26idpKey, err := rsa.GenerateKey(rand.Reader, 2048)27if err != nil {28return nil, fmt.Errorf("cannot produce IDP private key: %w", err)29}30err = keyCache.Set(context.Background(), idpKey)31if err != nil {32return nil, fmt.Errorf("cannot cache IDP key: %w", err)33}3435tokenEncryptionCode := make([]byte, 128)36_, err = io.ReadFull(rand.Reader, tokenEncryptionCode)37if err != nil {38return nil, fmt.Errorf("cannot produce random token encryption code: %w", err)39}4041return &Service{42IssuerBaseURL: issuerBaseURL,43TokenEncryptionCode: tokenEncryptionCode,44keys: keyCache,45}, nil46}4748type Service struct {49IssuerBaseURL string50TokenEncryptionCode []byte51keys KeyCache52}5354func (kp *Service) Router() http.Handler {55mux := chi.NewRouter()56mux.Get(oidc.DiscoveryEndpoint, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {57keysURL, err := url.JoinPath(kp.IssuerBaseURL, "keys")58if err != nil {59http.Error(w, err.Error(), http.StatusInternalServerError)60return61}62notSupported, err := url.JoinPath(kp.IssuerBaseURL, "not-supported")63if err != nil {64http.Error(w, err.Error(), http.StatusInternalServerError)65return66}6768cfg := oidc.DiscoveryConfiguration{69Issuer: kp.IssuerBaseURL,70ScopesSupported: op.DefaultSupportedScopes,71ResponseTypesSupported: []string{72string(oidc.ResponseTypeCode),73string(oidc.ResponseTypeIDTokenOnly),74string(oidc.ResponseTypeIDToken),75},76GrantTypesSupported: []oidc.GrantType{77oidc.GrantTypeCode,78oidc.GrantTypeImplicit,79},80SubjectTypesSupported: []string{"public"},81ClaimsSupported: []string{82"sub",83"aud",84"exp",85"iat",86"iss",87"auth_time",88"nonce",89"acr",90"amr",91"c_hash",92"at_hash",93"act",94"scopes",95"client_id",96"azp",97"preferred_username",98"name",99"family_name",100"given_name",101"locale",102"email",103},104IDTokenSigningAlgValuesSupported: []string{"RS256"},105RevocationEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone},106IntrospectionEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone},107IntrospectionEndpointAuthSigningAlgValuesSupported: []string{"RS256"},108RequestURIParameterSupported: false,109AuthorizationEndpoint: notSupported,110TokenEndpoint: notSupported,111IntrospectionEndpoint: notSupported,112UserinfoEndpoint: notSupported,113RevocationEndpoint: notSupported,114EndSessionEndpoint: notSupported,115JwksURI: keysURL,116}117w.Header().Set("Content-Type", "application/json")118err = json.NewEncoder(w).Encode(cfg)119if err != nil {120http.Error(w, err.Error(), http.StatusInternalServerError)121return122}123}))124mux.Get("/keys", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {125keys, err := kp.keys.PublicKeys(r.Context())126if err != nil {127http.Error(w, err.Error(), http.StatusInternalServerError)128return129}130w.Header().Set("Content-Type", "application/json")131_, err = w.Write(keys)132if err != nil {133log.WithError(err).Error("cannot respond to /keys")134}135}))136137return mux138}139140func (kp *Service) IDToken(ctx context.Context, org string, audience []string, user oidc.UserInfo) (string, error) {141if len(audience) == 0 {142return "", fmt.Errorf("audience cannot be empty")143}144if user == nil {145return "", fmt.Errorf("user info cannot be nil")146}147148claims := oidc.NewIDTokenClaims(kp.IssuerBaseURL, user.GetSubject(), audience, time.Now().Add(60*time.Minute), time.Now(), "", "", nil, audience[0], 0)149claims.SetUserinfo(user)150151codeHash, err := oidc.ClaimHash(string(kp.TokenEncryptionCode), jose.RS256)152if err != nil {153return "", err154}155claims.SetCodeHash(codeHash)156157signer, err := kp.keys.Signer(ctx)158if err != nil {159return "", err160}161162token, err := crypto.Sign(claims, signer)163if err != nil {164log.WithError(err).Error("cannot sign OIDC ID token")165return "", fmt.Errorf("cannot sign ID token")166}167return token, nil168}169170171