Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/public-api-server/pkg/oidc/oauth2.go
2500 views
1
// Copyright (c) 2022 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 oidc
6
7
import (
8
"context"
9
"net/http"
10
11
"github.com/gitpod-io/gitpod/common-go/log"
12
"golang.org/x/oauth2"
13
)
14
15
type OAuth2Result struct {
16
ClientConfigID string
17
OAuth2Token *oauth2.Token
18
ReturnToURL string
19
}
20
21
type StateParams struct {
22
// Gitpod's client config ID, not to be confused with OAuth `clientID`
23
ClientConfigID string `json:"clientConfigId"`
24
ReturnToURL string `json:"returnTo"`
25
Activate bool `json:"activate"`
26
Verify bool `json:"verify"`
27
UseHttpErrors bool `json:"useHttpErrors"`
28
}
29
30
type keyOAuth2Result struct{}
31
32
func AttachOAuth2ResultToContext(parentContext context.Context, result *OAuth2Result) context.Context {
33
childContext := context.WithValue(parentContext, keyOAuth2Result{}, result)
34
return childContext
35
}
36
37
func GetOAuth2ResultFromContext(ctx context.Context) *OAuth2Result {
38
value, ok := ctx.Value(keyOAuth2Result{}).(*OAuth2Result)
39
if !ok {
40
return nil
41
}
42
return value
43
}
44
45
func (s *Service) OAuth2Middleware(next http.Handler) http.Handler {
46
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
47
config, _, err := s.getClientConfigFromCallbackRequest(r)
48
if err != nil {
49
log.Warn("client config not found: " + err.Error())
50
http.Error(rw, "config not found", http.StatusNotFound)
51
return
52
}
53
54
// http-only cookie written during flow start request
55
stateCookie, err := r.Cookie(stateCookieName)
56
if err != nil {
57
http.Error(rw, "state cookie not found", http.StatusBadRequest)
58
return
59
}
60
// the starte param passed back from IdP
61
stateParam := r.URL.Query().Get("state")
62
if stateParam == "" {
63
http.Error(rw, "state param not found", http.StatusBadRequest)
64
return
65
}
66
// on mismatch, obviously there is a client side error
67
if stateParam != stateCookie.Value {
68
http.Error(rw, "state did not match", http.StatusBadRequest)
69
return
70
}
71
72
state, err := s.decodeStateParam(stateParam)
73
if err != nil {
74
http.Error(rw, "bad state param", http.StatusBadRequest)
75
return
76
}
77
useHttpErrors := state.UseHttpErrors
78
79
code := r.URL.Query().Get("code")
80
if code == "" {
81
http.Error(rw, "code param not found", http.StatusBadRequest)
82
log.Warn("'code' parameter not found.")
83
respondeWithError(rw, r, "'code' parameter not found.", http.StatusInternalServerError, useHttpErrors)
84
return
85
}
86
87
opts := []oauth2.AuthCodeOption{}
88
if config.UsePKCE {
89
codeVerifier, err := r.Cookie(verifierCookieName)
90
if err != nil {
91
http.Error(rw, "code_verifier cookie not found", http.StatusBadRequest)
92
return
93
}
94
opts = append(opts, oauth2.VerifierOption(codeVerifier.Value))
95
}
96
97
config.OAuth2Config.RedirectURL = getCallbackURL(r.Host)
98
oauth2Token, err := config.OAuth2Config.Exchange(r.Context(), code, opts...)
99
if err != nil {
100
log.WithError(err).Warn("Failed to exchange OAuth2 token.")
101
respondeWithError(rw, r, "Failed to exchange OAuth2 token: "+err.Error(), http.StatusInternalServerError, useHttpErrors)
102
return
103
}
104
105
ctx := AttachOAuth2ResultToContext(r.Context(), &OAuth2Result{
106
OAuth2Token: oauth2Token,
107
ReturnToURL: state.ReturnToURL,
108
ClientConfigID: state.ClientConfigID,
109
})
110
next.ServeHTTP(rw, r.WithContext(ctx))
111
})
112
}
113
114