Path: blob/main/components/public-api-server/pkg/oidc/oauth2.go
2500 views
// Copyright (c) 2022 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 oidc56import (7"context"8"net/http"910"github.com/gitpod-io/gitpod/common-go/log"11"golang.org/x/oauth2"12)1314type OAuth2Result struct {15ClientConfigID string16OAuth2Token *oauth2.Token17ReturnToURL string18}1920type StateParams struct {21// Gitpod's client config ID, not to be confused with OAuth `clientID`22ClientConfigID string `json:"clientConfigId"`23ReturnToURL string `json:"returnTo"`24Activate bool `json:"activate"`25Verify bool `json:"verify"`26UseHttpErrors bool `json:"useHttpErrors"`27}2829type keyOAuth2Result struct{}3031func AttachOAuth2ResultToContext(parentContext context.Context, result *OAuth2Result) context.Context {32childContext := context.WithValue(parentContext, keyOAuth2Result{}, result)33return childContext34}3536func GetOAuth2ResultFromContext(ctx context.Context) *OAuth2Result {37value, ok := ctx.Value(keyOAuth2Result{}).(*OAuth2Result)38if !ok {39return nil40}41return value42}4344func (s *Service) OAuth2Middleware(next http.Handler) http.Handler {45return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {46config, _, err := s.getClientConfigFromCallbackRequest(r)47if err != nil {48log.Warn("client config not found: " + err.Error())49http.Error(rw, "config not found", http.StatusNotFound)50return51}5253// http-only cookie written during flow start request54stateCookie, err := r.Cookie(stateCookieName)55if err != nil {56http.Error(rw, "state cookie not found", http.StatusBadRequest)57return58}59// the starte param passed back from IdP60stateParam := r.URL.Query().Get("state")61if stateParam == "" {62http.Error(rw, "state param not found", http.StatusBadRequest)63return64}65// on mismatch, obviously there is a client side error66if stateParam != stateCookie.Value {67http.Error(rw, "state did not match", http.StatusBadRequest)68return69}7071state, err := s.decodeStateParam(stateParam)72if err != nil {73http.Error(rw, "bad state param", http.StatusBadRequest)74return75}76useHttpErrors := state.UseHttpErrors7778code := r.URL.Query().Get("code")79if code == "" {80http.Error(rw, "code param not found", http.StatusBadRequest)81log.Warn("'code' parameter not found.")82respondeWithError(rw, r, "'code' parameter not found.", http.StatusInternalServerError, useHttpErrors)83return84}8586opts := []oauth2.AuthCodeOption{}87if config.UsePKCE {88codeVerifier, err := r.Cookie(verifierCookieName)89if err != nil {90http.Error(rw, "code_verifier cookie not found", http.StatusBadRequest)91return92}93opts = append(opts, oauth2.VerifierOption(codeVerifier.Value))94}9596config.OAuth2Config.RedirectURL = getCallbackURL(r.Host)97oauth2Token, err := config.OAuth2Config.Exchange(r.Context(), code, opts...)98if err != nil {99log.WithError(err).Warn("Failed to exchange OAuth2 token.")100respondeWithError(rw, r, "Failed to exchange OAuth2 token: "+err.Error(), http.StatusInternalServerError, useHttpErrors)101return102}103104ctx := AttachOAuth2ResultToContext(r.Context(), &OAuth2Result{105OAuth2Token: oauth2Token,106ReturnToURL: state.ReturnToURL,107ClientConfigID: state.ClientConfigID,108})109next.ServeHTTP(rw, r.WithContext(ctx))110})111}112113114