Alex Tugarev 7952153237
Add verify param to OIDC start request – WEB-333 (#17576)
* [papi] Add `verified` flag to config and `ActivateClientConfig` to OIDC service

* [papi] implement `ActivateClientConfig`

* [gitpod-db/go] add `setClientConfigVerifiedFlag`

* [gitpod-db/migration] add `d_b_oidc_client_config.verified` field

* [papi] Don't deactive on UpdateClientConfig

* [gitpod-db/go] add missing `Verified` field

* On "activate" request also mark as verified.

* [gitpod-db/go] fix mapping of `Verified` field

* [papi] ensure only verified OIDC client configs can be activated

* [papi] Skip the sign-in on verify-only requests.

* [papi] fix skipped tests

* [papi] fix mapping of OIDC configs

* rename RPC method

* fix tests after adding validation of claims

* fix: activation of record should deactivate others

* fix: update should unverify the entry

* remove Debug()

* [db-migration] fix: mark active entries as verified
2023-05-16 21:09:02 +08:00

98 lines
2.7 KiB
Go

// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.
package oidc
import (
"context"
"net/http"
"github.com/gitpod-io/gitpod/common-go/log"
"golang.org/x/oauth2"
)
type OAuth2Result struct {
ClientConfigID string
OAuth2Token *oauth2.Token
ReturnToURL string
}
type StateParams struct {
// Internal client config ID
ClientConfigID string `json:"clientConfigId"`
ReturnToURL string `json:"returnTo"`
Activate bool `json:"activate"`
Verify bool `json:"verify"`
}
type keyOAuth2Result struct{}
func AttachOAuth2ResultToContext(parentContext context.Context, result *OAuth2Result) context.Context {
childContext := context.WithValue(parentContext, keyOAuth2Result{}, result)
return childContext
}
func GetOAuth2ResultFromContext(ctx context.Context) *OAuth2Result {
value, ok := ctx.Value(keyOAuth2Result{}).(*OAuth2Result)
if !ok {
return nil
}
return value
}
func (s *Service) OAuth2Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
config, _, err := s.getClientConfigFromCallbackRequest(r)
if err != nil {
log.Warn("client config not found: " + err.Error())
http.Error(rw, "config not found", http.StatusNotFound)
return
}
// http-only cookie written during flow start request
stateCookie, err := r.Cookie(stateCookieName)
if err != nil {
http.Error(rw, "state cookie not found", http.StatusBadRequest)
return
}
// the starte param passed back from IdP
stateParam := r.URL.Query().Get("state")
if stateParam == "" {
http.Error(rw, "state param not found", http.StatusBadRequest)
return
}
// on mismatch, obviously there is a client side error
if stateParam != stateCookie.Value {
http.Error(rw, "state did not match", http.StatusBadRequest)
return
}
state, err := s.decodeStateParam(stateParam)
if err != nil {
http.Error(rw, "bad state param", http.StatusBadRequest)
return
}
code := r.URL.Query().Get("code")
if code == "" {
http.Error(rw, "code param not found", http.StatusBadRequest)
return
}
config.OAuth2Config.RedirectURL = getCallbackURL(r.Host)
oauth2Token, err := config.OAuth2Config.Exchange(r.Context(), code)
if err != nil {
http.Error(rw, "failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
ctx := AttachOAuth2ResultToContext(r.Context(), &OAuth2Result{
OAuth2Token: oauth2Token,
ReturnToURL: state.ReturnToURL,
ClientConfigID: state.ClientConfigID,
})
next.ServeHTTP(rw, r.WithContext(ctx))
})
}