vouch-proxy/handlers/validate.go

135 lines
3.6 KiB
Go

/*
Copyright 2020 The Vouch Proxy Authors.
Use of this source code is governed by The MIT License (MIT) that
can be found in the LICENSE file. Software distributed under The
MIT License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
OR CONDITIONS OF ANY KIND, either express or implied.
*/
package handlers
import (
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"go.uber.org/zap"
"github.com/vouch/vouch-proxy/pkg/cfg"
"github.com/vouch/vouch-proxy/pkg/jwtmanager"
"github.com/vouch/vouch-proxy/pkg/responses"
)
var (
errNoJWT = errors.New("no jwt found in request")
errNoUser = errors.New("no User found in jwt")
)
// ValidateRequestHandler /validate
func ValidateRequestHandler(w http.ResponseWriter, r *http.Request) {
fastlog.Debug("/validate")
jwt := jwtmanager.FindJWT(r)
if jwt == "" {
send401or200PublicAccess(w, r, errNoJWT)
return
}
claims, err := jwtmanager.ClaimsFromJWT(jwt)
if err != nil {
send401or200PublicAccess(w, r, err)
return
}
if claims.Username == "" {
send401or200PublicAccess(w, r, errNoUser)
return
}
if !cfg.Cfg.AllowAllUsers {
if !claims.SiteInAudience(r.Host) {
send401or200PublicAccess(w, r,
fmt.Errorf("http header 'Host: %s' not authorized for configured `vouch.domains` (is Host being sent properly?)", r.Host))
return
}
}
generateCustomClaimsHeaders(w, claims)
w.Header().Add(cfg.Cfg.Headers.User, claims.Username)
w.Header().Add(cfg.Cfg.Headers.Success, "true")
if cfg.Cfg.Headers.AccessToken != "" && claims.PAccessToken != "" {
w.Header().Add(cfg.Cfg.Headers.AccessToken, claims.PAccessToken)
}
if cfg.Cfg.Headers.IDToken != "" && claims.PIdToken != "" {
w.Header().Add(cfg.Cfg.Headers.IDToken, claims.PIdToken)
}
// fastlog.Debugf("response headers %+v", w.Header())
// fastlog.Debug("response header",
// zap.String(cfg.Cfg.Headers.User, w.Header().Get(cfg.Cfg.Headers.User)))
fastlog.Debug("response header",
zap.Any("all headers", w.Header()))
// good to go!!
if cfg.Cfg.Testing {
responses.RenderIndex(w, "user authorized "+claims.Username)
} else {
responses.OK200(w, r)
}
// TODO
// parse the jwt and see if the claim is valid for the domain
}
func generateCustomClaimsHeaders(w http.ResponseWriter, claims *jwtmanager.VouchClaims) {
if len(cfg.Cfg.Headers.ClaimsCleaned) > 0 {
log.Debug("Found claims in config, finding specific keys...")
// Run through all the claims found
for k, v := range claims.CustomClaims {
// Run through the claims we are looking for
for claim, header := range cfg.Cfg.Headers.ClaimsCleaned {
// Check for matching claim
if claim == k {
log.Debugf("Found matching claim key: %s", k)
if val, ok := v.([]interface{}); ok {
strs := make([]string, len(val))
for i, v := range val {
strs[i] = fmt.Sprintf("\"%s\"", v)
}
log.Debugf("Adding header for claim %s - %s: %s", k, header, val)
w.Header().Add(header, strings.Join(strs, ","))
} else {
// convert to string
val := fmt.Sprint(v)
if reflect.TypeOf(val).Kind() == reflect.String {
// if val, ok := v.(string); ok {
w.Header().Add(header, val)
log.Debugf("Adding header for claim %s - %s: %s", k, header, val)
} else {
log.Errorf("Couldn't parse header type for %s %+v. Please submit an issue.", k, v)
}
}
}
}
}
}
}
func send401or200PublicAccess(w http.ResponseWriter, r *http.Request, e error) {
if cfg.Cfg.PublicAccess {
log.Debugf("error: %s, but public access is '%v', returning OK200", e, cfg.Cfg.PublicAccess)
w.Header().Add(cfg.Cfg.Headers.User, "")
responses.OK200(w, r)
return
}
responses.Error401(w, r, e)
}