Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/public-api-server/pkg/identityprovider/idp.go
2500 views
1
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package identityprovider
6
7
import (
8
"context"
9
"crypto/rand"
10
"crypto/rsa"
11
"encoding/json"
12
"fmt"
13
"io"
14
"net/http"
15
"net/url"
16
"time"
17
18
"github.com/gitpod-io/gitpod/common-go/log"
19
"github.com/go-chi/chi/v5"
20
"github.com/zitadel/oidc/pkg/crypto"
21
"github.com/zitadel/oidc/pkg/oidc"
22
"github.com/zitadel/oidc/pkg/op"
23
"gopkg.in/square/go-jose.v2"
24
)
25
26
func NewService(issuerBaseURL string, keyCache KeyCache) (*Service, error) {
27
idpKey, err := rsa.GenerateKey(rand.Reader, 2048)
28
if err != nil {
29
return nil, fmt.Errorf("cannot produce IDP private key: %w", err)
30
}
31
err = keyCache.Set(context.Background(), idpKey)
32
if err != nil {
33
return nil, fmt.Errorf("cannot cache IDP key: %w", err)
34
}
35
36
tokenEncryptionCode := make([]byte, 128)
37
_, err = io.ReadFull(rand.Reader, tokenEncryptionCode)
38
if err != nil {
39
return nil, fmt.Errorf("cannot produce random token encryption code: %w", err)
40
}
41
42
return &Service{
43
IssuerBaseURL: issuerBaseURL,
44
TokenEncryptionCode: tokenEncryptionCode,
45
keys: keyCache,
46
}, nil
47
}
48
49
type Service struct {
50
IssuerBaseURL string
51
TokenEncryptionCode []byte
52
keys KeyCache
53
}
54
55
func (kp *Service) Router() http.Handler {
56
mux := chi.NewRouter()
57
mux.Get(oidc.DiscoveryEndpoint, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58
keysURL, err := url.JoinPath(kp.IssuerBaseURL, "keys")
59
if err != nil {
60
http.Error(w, err.Error(), http.StatusInternalServerError)
61
return
62
}
63
notSupported, err := url.JoinPath(kp.IssuerBaseURL, "not-supported")
64
if err != nil {
65
http.Error(w, err.Error(), http.StatusInternalServerError)
66
return
67
}
68
69
cfg := oidc.DiscoveryConfiguration{
70
Issuer: kp.IssuerBaseURL,
71
ScopesSupported: op.DefaultSupportedScopes,
72
ResponseTypesSupported: []string{
73
string(oidc.ResponseTypeCode),
74
string(oidc.ResponseTypeIDTokenOnly),
75
string(oidc.ResponseTypeIDToken),
76
},
77
GrantTypesSupported: []oidc.GrantType{
78
oidc.GrantTypeCode,
79
oidc.GrantTypeImplicit,
80
},
81
SubjectTypesSupported: []string{"public"},
82
ClaimsSupported: []string{
83
"sub",
84
"aud",
85
"exp",
86
"iat",
87
"iss",
88
"auth_time",
89
"nonce",
90
"acr",
91
"amr",
92
"c_hash",
93
"at_hash",
94
"act",
95
"scopes",
96
"client_id",
97
"azp",
98
"preferred_username",
99
"name",
100
"family_name",
101
"given_name",
102
"locale",
103
"email",
104
},
105
IDTokenSigningAlgValuesSupported: []string{"RS256"},
106
RevocationEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone},
107
IntrospectionEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone},
108
IntrospectionEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
109
RequestURIParameterSupported: false,
110
AuthorizationEndpoint: notSupported,
111
TokenEndpoint: notSupported,
112
IntrospectionEndpoint: notSupported,
113
UserinfoEndpoint: notSupported,
114
RevocationEndpoint: notSupported,
115
EndSessionEndpoint: notSupported,
116
JwksURI: keysURL,
117
}
118
w.Header().Set("Content-Type", "application/json")
119
err = json.NewEncoder(w).Encode(cfg)
120
if err != nil {
121
http.Error(w, err.Error(), http.StatusInternalServerError)
122
return
123
}
124
}))
125
mux.Get("/keys", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126
keys, err := kp.keys.PublicKeys(r.Context())
127
if err != nil {
128
http.Error(w, err.Error(), http.StatusInternalServerError)
129
return
130
}
131
w.Header().Set("Content-Type", "application/json")
132
_, err = w.Write(keys)
133
if err != nil {
134
log.WithError(err).Error("cannot respond to /keys")
135
}
136
}))
137
138
return mux
139
}
140
141
func (kp *Service) IDToken(ctx context.Context, org string, audience []string, user oidc.UserInfo) (string, error) {
142
if len(audience) == 0 {
143
return "", fmt.Errorf("audience cannot be empty")
144
}
145
if user == nil {
146
return "", fmt.Errorf("user info cannot be nil")
147
}
148
149
claims := oidc.NewIDTokenClaims(kp.IssuerBaseURL, user.GetSubject(), audience, time.Now().Add(60*time.Minute), time.Now(), "", "", nil, audience[0], 0)
150
claims.SetUserinfo(user)
151
152
codeHash, err := oidc.ClaimHash(string(kp.TokenEncryptionCode), jose.RS256)
153
if err != nil {
154
return "", err
155
}
156
claims.SetCodeHash(codeHash)
157
158
signer, err := kp.keys.Signer(ctx)
159
if err != nil {
160
return "", err
161
}
162
163
token, err := crypto.Sign(claims, signer)
164
if err != nil {
165
log.WithError(err).Error("cannot sign OIDC ID token")
166
return "", fmt.Errorf("cannot sign ID token")
167
}
168
return token, nil
169
}
170
171