mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-10 15:47:36 +02:00
httputil: use http error wrapper
Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
parent
d26f935cbb
commit
6e6ab3baa0
11 changed files with 325 additions and 677 deletions
|
@ -7,11 +7,14 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
// CSPHeaders are the content security headers added to the service's handlers
|
||||
|
@ -46,6 +49,8 @@ func (a *Authenticate) Handler() http.Handler {
|
|||
|
||||
// RobotsTxt handles the /robots.txt route.
|
||||
func (a *Authenticate) RobotsTxt(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "User-agent: *\nDisallow: /")
|
||||
}
|
||||
|
@ -54,93 +59,74 @@ func (a *Authenticate) authenticate(w http.ResponseWriter, r *http.Request, sess
|
|||
if session.RefreshPeriodExpired() {
|
||||
session, err := a.provider.Refresh(r.Context(), session)
|
||||
if err != nil {
|
||||
return fmt.Errorf("authenticate: session refresh failed : %v", err)
|
||||
return xerrors.Errorf("session refresh failed : %w", err)
|
||||
}
|
||||
err = a.sessionStore.SaveSession(w, r, session)
|
||||
if err != nil {
|
||||
return fmt.Errorf("authenticate: failed saving refreshed session : %v", err)
|
||||
if err = a.sessionStore.SaveSession(w, r, session); err != nil {
|
||||
return xerrors.Errorf("failed saving refreshed session : %w", err)
|
||||
}
|
||||
} else {
|
||||
valid, err := a.provider.Validate(r.Context(), session.IDToken)
|
||||
if err != nil || !valid {
|
||||
return fmt.Errorf("authenticate: session valid: %v : %v", valid, err)
|
||||
return xerrors.Errorf("session valid: %v : %w", valid, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignIn handles the sign_in endpoint. It attempts to authenticate the user,
|
||||
// and if the user is not authenticated, it renders a sign in page.
|
||||
// SignIn handles to authenticating a user.
|
||||
func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := a.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
||||
log.FromRequest(r).Debug().Err(err).Msg("authenticate: invalid session")
|
||||
log.FromRequest(r).Debug().Err(err).Msg("no session loaded, restart auth")
|
||||
a.sessionStore.ClearSession(w, r)
|
||||
a.OAuthStart(w, r)
|
||||
return
|
||||
default:
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: unexpected error")
|
||||
httpErr := &httputil.Error{Message: "An unexpected error occurred", Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
}
|
||||
// if a session already exists, authenticate it
|
||||
if err := a.authenticate(w, r, session); err != nil {
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
err = a.authenticate(w, r, session)
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
return
|
||||
}
|
||||
if err = r.ParseForm(); err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
return
|
||||
}
|
||||
// original `state` parameter received from the proxy application.
|
||||
state := r.Form.Get("state")
|
||||
if state == "" {
|
||||
httpErr := &httputil.Error{Message: "no state parameter supplied", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL, err := url.Parse(r.Form.Get("redirect_uri"))
|
||||
state := r.Form.Get("state")
|
||||
if state == "" {
|
||||
httputil.ErrorResponse(w, r, httputil.Error("sign in state empty", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL, err := urlutil.ParseAndValidateURL(r.Form.Get("redirect_uri"))
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{Message: "malformed redirect_uri parameter passed", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("malformed redirect_uri parameter passed", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
// encrypt session state as json blob
|
||||
encrypted, err := sessions.MarshalSession(session, a.cipher)
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("couldn't marshall session", http.StatusInternalServerError, err))
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, getAuthCodeRedirectURL(redirectURL, state, encrypted), http.StatusFound)
|
||||
}
|
||||
|
||||
func getAuthCodeRedirectURL(redirectURL *url.URL, state, authCode string) string {
|
||||
u, _ := url.Parse(redirectURL.String())
|
||||
params, _ := url.ParseQuery(u.RawQuery)
|
||||
// error handled by go's mux stack
|
||||
params, _ := url.ParseQuery(redirectURL.RawQuery)
|
||||
params.Set("code", authCode)
|
||||
params.Set("state", state)
|
||||
u.RawQuery = params.Encode()
|
||||
if u.Scheme == "" {
|
||||
u.Scheme = "https"
|
||||
}
|
||||
return u.String()
|
||||
redirectURL.RawQuery = params.Encode()
|
||||
return redirectURL.String()
|
||||
}
|
||||
|
||||
// SignOut signs the user out by trying to revoke the user's remote identity session along with
|
||||
// the associated local session state. Handles both GET and POST.
|
||||
func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("authenticate: error SignOut form")
|
||||
httpErr := &httputil.Error{Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
redirectURI := r.Form.Get("redirect_uri")
|
||||
|
@ -153,35 +139,30 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
|
|||
a.sessionStore.ClearSession(w, r)
|
||||
err = a.provider.Revoke(session.AccessToken)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate: failed to revoke user session")
|
||||
httpErr := &httputil.Error{Message: fmt.Sprintf("could not revoke session: %s ", err.Error()), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("could not revoke user session", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, redirectURI, http.StatusFound)
|
||||
}
|
||||
|
||||
// OAuthStart starts the authenticate process by redirecting to the identity provider.
|
||||
// https://openid.net/specs/openid-connect-core-1_0-final.html#AuthRequest
|
||||
// https://tools.ietf.org/html/rfc6749#section-4.2.1
|
||||
func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||
authRedirectURL := a.RedirectURL.ResolveReference(r.URL)
|
||||
|
||||
// generate a nonce to check following authentication with the IdP
|
||||
// Nonce is the opaque, cryptographically binding value used to maintain
|
||||
// state between the request and the callback.
|
||||
// OIDC : 3.1.2.1. Authentication Request
|
||||
nonce := fmt.Sprintf("%x", cryptutil.GenerateKey())
|
||||
a.csrfStore.SetCSRF(w, r, nonce)
|
||||
|
||||
// verify redirect uri is from the root domain
|
||||
if !middleware.SameDomain(authRedirectURL, a.RedirectURL) {
|
||||
httpErr := &httputil.Error{Message: "Invalid redirect parameter: redirect uri not from the root domain", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
return
|
||||
}
|
||||
|
||||
// verify proxy url is from the root domain
|
||||
proxyRedirectURL, err := url.Parse(authRedirectURL.Query().Get("redirect_uri"))
|
||||
// Redirection URI to which the response will be sent. This URI MUST exactly
|
||||
// match one of the Redirection URI values for the Client pre-registered at
|
||||
// at your identity provider
|
||||
proxyRedirectURL, err := urlutil.ParseAndValidateURL(authRedirectURL.Query().Get("redirect_uri"))
|
||||
if err != nil || !middleware.SameDomain(proxyRedirectURL, a.RedirectURL) {
|
||||
httpErr := &httputil.Error{Message: "Invalid redirect parameter: proxy url not from the root domain", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("proxy url not from the root domain", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -189,12 +170,12 @@ func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
proxyRedirectSig := authRedirectURL.Query().Get("sig")
|
||||
ts := authRedirectURL.Query().Get("ts")
|
||||
if !middleware.ValidSignature(proxyRedirectURL.String(), proxyRedirectSig, ts, a.SharedKey) {
|
||||
httpErr := &httputil.Error{Message: "Invalid redirect parameter: invalid signature", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("invalid signature", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
// concat base64'd nonce and authenticate url to make state
|
||||
// State is the opaque value used to maintain state between the request and
|
||||
// the callback; contains both the nonce and redirect URI
|
||||
state := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", nonce, authRedirectURL.String())))
|
||||
|
||||
// build the provider sign in url
|
||||
|
@ -204,83 +185,71 @@ func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// OAuthCallback handles the callback from the identity provider. Displays an error page if there
|
||||
// was an error. If successful, the user is redirected back to the proxy-service.
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
|
||||
func (a *Authenticate) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||
redirect, err := a.getOAuthCallback(w, r)
|
||||
switch h := err.(type) {
|
||||
case nil:
|
||||
break
|
||||
case httputil.Error:
|
||||
log.Error().Err(err).Msg("authenticate: oauth callback error")
|
||||
httpErr := &httputil.Error{Message: h.Message, Code: h.Code}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
return
|
||||
default:
|
||||
log.Error().Err(err).Msg("authenticate: unexpected oauth callback error")
|
||||
httpErr := &httputil.Error{Message: "Internal Error", Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
if err != nil {
|
||||
httputil.ErrorResponse(w, r, xerrors.Errorf("oauth callback : %w", err))
|
||||
return
|
||||
}
|
||||
// redirect back to the proxy-service via sign_in
|
||||
http.Redirect(w, r, redirect, http.StatusFound)
|
||||
}
|
||||
|
||||
// getOAuthCallback completes the oauth cycle from an identity provider's callback
|
||||
func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
// handle the callback response from the identity provider
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return "", httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()}
|
||||
return "", httputil.Error("invalid signature", http.StatusBadRequest, err)
|
||||
}
|
||||
errorString := r.Form.Get("error")
|
||||
if errorString != "" {
|
||||
log.FromRequest(r).Error().Str("Error", errorString).Msg("authenticate: provider returned error")
|
||||
return "", httputil.Error{Code: http.StatusForbidden, Message: errorString}
|
||||
// OIDC : 3.1.2.6. Authentication Error Response
|
||||
// https://openid.net/specs/openid-connect-core-1_0-final.html#AuthError
|
||||
if errorString := r.Form.Get("error"); errorString != "" {
|
||||
return "", httputil.Error("provider returned an error", http.StatusBadRequest, fmt.Errorf("provider returned error: %v", errorString))
|
||||
}
|
||||
// OIDC : 3.1.2.5. Successful Authentication Response
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
|
||||
code := r.Form.Get("code")
|
||||
if code == "" {
|
||||
log.FromRequest(r).Error().Msg("authenticate: provider missing code")
|
||||
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Missing Code"}
|
||||
return "", httputil.Error("provider didn't reply with code", http.StatusBadRequest, nil)
|
||||
}
|
||||
|
||||
// validate the returned code with the identity provider
|
||||
session, err := a.provider.Authenticate(r.Context(), code)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: error redeeming authenticate code")
|
||||
return "", httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()}
|
||||
return "", xerrors.Errorf("error redeeming authenticate code: %w", err)
|
||||
}
|
||||
|
||||
// okay, time to go back to the proxy service.
|
||||
// Opaque value used to maintain state between the request and the callback.
|
||||
// OIDC : 3.1.2.5. Successful Authentication Response
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
|
||||
bytes, err := base64.URLEncoding.DecodeString(r.Form.Get("state"))
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: failed decoding state")
|
||||
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Couldn't decode state"}
|
||||
return "", xerrors.Errorf("failed decoding state: %w", err)
|
||||
}
|
||||
s := strings.SplitN(string(bytes), ":", 2)
|
||||
if len(s) != 2 {
|
||||
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Invalid State"}
|
||||
return "", xerrors.Errorf("invalid state size: %v", len(s))
|
||||
}
|
||||
// state contains both our csrf nonce and the redirect uri
|
||||
nonce := s[0]
|
||||
redirect := s[1]
|
||||
c, err := a.csrfStore.GetCSRF(r)
|
||||
defer a.csrfStore.ClearCSRF(w, r)
|
||||
if err != nil || c.Value != nonce {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: csrf failure")
|
||||
return "", httputil.Error{Code: http.StatusForbidden, Message: "CSRF failed"}
|
||||
return "", xerrors.Errorf("csrf failure: %w", err)
|
||||
|
||||
}
|
||||
redirectURL, err := url.Parse(redirect)
|
||||
redirectURL, err := urlutil.ParseAndValidateURL(redirect)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: malformed redirect url")
|
||||
return "", httputil.Error{Code: http.StatusForbidden, Message: "Malformed redirect url"}
|
||||
return "", httputil.Error(fmt.Sprintf("invalid redirect uri %s", redirect), http.StatusBadRequest, err)
|
||||
}
|
||||
// sanity check, we are redirecting back to the same subdomain right?
|
||||
if !middleware.SameDomain(redirectURL, a.RedirectURL) {
|
||||
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Invalid Redirect URI domain"}
|
||||
return "", httputil.Error(fmt.Sprintf("invalid redirect domain %v, %v", redirectURL, a.RedirectURL), http.StatusBadRequest, nil)
|
||||
}
|
||||
|
||||
if err := a.sessionStore.SaveSession(w, r, session); err != nil {
|
||||
log.Error().Err(err).Msg("authenticate: failed saving new session")
|
||||
return "", httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()}
|
||||
return "", xerrors.Errorf("failed saving new session: %w", err)
|
||||
}
|
||||
|
||||
return redirect, nil
|
||||
}
|
||||
|
||||
|
@ -289,24 +258,21 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
|||
// audience ('aud') attribute must match Pomerium's client_id.
|
||||
func (a *Authenticate) ExchangeToken(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
code := r.Form.Get("id_token")
|
||||
if code == "" {
|
||||
log.FromRequest(r).Error().Msg("authenticate: provider missing id token")
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusBadRequest, Message: "missing id token"})
|
||||
httputil.ErrorResponse(w, r, httputil.Error("provider missing id token", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
session, err := a.provider.IDTokenToSession(r.Context(), code)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: error exchanging identity provider code")
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError, Message: "could not exchange identity for session"})
|
||||
httputil.ErrorResponse(w, r, httputil.Error("could not exchange identity for session", http.StatusInternalServerError, err))
|
||||
return
|
||||
}
|
||||
if err := a.restStore.SaveSession(w, r, session); err != nil {
|
||||
log.Error().Err(err).Msg("authenticate: failed returning new session")
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError, Message: "authenticate: failed returning new session"})
|
||||
httputil.ErrorResponse(w, r, httputil.Error("failed returning new session", http.StatusInternalServerError, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,133 +69,40 @@ func TestAuthenticate_SignIn(t *testing.T) {
|
|||
redirectURI string
|
||||
session sessions.SessionStore
|
||||
provider identity.MockProvider
|
||||
cipher cryptutil.Cipher
|
||||
wantCode int
|
||||
}{
|
||||
{"good",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: true},
|
||||
http.StatusFound},
|
||||
{"session not valid",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: false},
|
||||
http.StatusInternalServerError},
|
||||
{"session refresh error",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(-10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{
|
||||
ValidateResponse: true,
|
||||
RefreshError: errors.New("error")},
|
||||
http.StatusInternalServerError},
|
||||
{"session save after refresh error",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
SaveError: errors.New("error"),
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(-10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{
|
||||
ValidateResponse: true,
|
||||
},
|
||||
http.StatusInternalServerError},
|
||||
{"no cookie found trying to load",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
LoadError: http.ErrNoCookie,
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: true},
|
||||
http.StatusBadRequest},
|
||||
{"unexpected error trying to load session",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
LoadError: errors.New("error"),
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: true},
|
||||
http.StatusInternalServerError},
|
||||
{"malformed form",
|
||||
"state=example",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: true},
|
||||
http.StatusInternalServerError},
|
||||
{"empty state",
|
||||
"state=",
|
||||
"redirect_uri=some.example",
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: true},
|
||||
http.StatusBadRequest},
|
||||
|
||||
{"malformed redirect uri",
|
||||
"state=example",
|
||||
"redirect_uri=https://accounts.google.^",
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
identity.MockProvider{ValidateResponse: true},
|
||||
http.StatusBadRequest},
|
||||
{"good", "state=example", "https://some.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusFound},
|
||||
{"session not valid", "state=example", "https://some.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: false}, &cryptutil.MockCipher{}, http.StatusInternalServerError},
|
||||
{"session refresh error", "state=example", "https://some.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, identity.MockProvider{ValidateResponse: true, RefreshError: errors.New("error")}, &cryptutil.MockCipher{}, http.StatusInternalServerError},
|
||||
{"session save after refresh error", "state=example", "https://some.example", &sessions.MockSessionStore{SaveError: errors.New("error"), Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusInternalServerError},
|
||||
{"no cookie found trying to load", "state=example", "https://some.example", &sessions.MockSessionStore{LoadError: http.ErrNoCookie, Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusBadRequest},
|
||||
{"unexpected error trying to load session", "state=example", "https://some.example", &sessions.MockSessionStore{LoadError: errors.New("error"), Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusBadRequest},
|
||||
{"malformed form", "state=example", "https://some.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusInternalServerError},
|
||||
{"empty state", "state=", "https://some.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusBadRequest},
|
||||
{"malformed redirect uri", "state=example", "https://accounts.google.^", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusBadRequest},
|
||||
// actually caught by go's handler, but we should keep the test.
|
||||
{"bad redirect uri query", "state=nonce", "%gh&%ij", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{}, http.StatusInternalServerError},
|
||||
{"marshal session failure", "state=example", "https://some.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, &cryptutil.MockCipher{MarshalError: errors.New("error")}, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Authenticate{
|
||||
sessionStore: tt.session,
|
||||
provider: tt.provider,
|
||||
RedirectURL: uriParse(tt.redirectURI),
|
||||
RedirectURL: uriParse("https://some.example"),
|
||||
csrfStore: &sessions.MockCSRFStore{},
|
||||
SharedKey: "secret",
|
||||
cipher: mockCipher{},
|
||||
cipher: tt.cipher,
|
||||
}
|
||||
uri := &url.URL{Path: "/"}
|
||||
uri := &url.URL{Host: "corp.some.example", Scheme: "https", Path: "/"}
|
||||
if tt.name == "malformed form" {
|
||||
uri.RawQuery = "example=%zzzzz"
|
||||
} else {
|
||||
uri.RawQuery = fmt.Sprintf("%s&%s", tt.state, tt.redirectURI)
|
||||
uri.RawQuery = fmt.Sprintf("%s&redirect_uri=%s", tt.state, tt.redirectURI)
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodGet, uri.String(), nil)
|
||||
r.Header.Set("Accept", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
a.SignIn(w, r)
|
||||
|
@ -241,7 +148,6 @@ func Test_getAuthCodeRedirectURL(t *testing.T) {
|
|||
{"https", uriParse("https://www.pomerium.io"), "state", "auth-code", "https://www.pomerium.io?code=auth-code&state=state"},
|
||||
{"http", uriParse("http://www.pomerium.io"), "state", "auth-code", "http://www.pomerium.io?code=auth-code&state=state"},
|
||||
{"no subdomain", uriParse("http://pomerium.io"), "state", "auth-code", "http://pomerium.io?code=auth-code&state=state"},
|
||||
{"no scheme make https", uriParse("pomerium.io"), "state", "auth-code", "https://pomerium.io?code=auth-code&state=state"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -274,7 +180,7 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
|||
}{
|
||||
{"good post", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusFound, ""},
|
||||
{"failed revoke", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{RevokeError: errors.New("OH NO")}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusBadRequest, "could not revoke"},
|
||||
{"malformed form", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusBadRequest, ""},
|
||||
{"malformed form", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusInternalServerError, ""},
|
||||
{"load session error", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{LoadError: errors.New("hi"), Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusFound, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -318,6 +224,7 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
redirectURLSetting string
|
||||
|
||||
redirectURL string
|
||||
sig string
|
||||
|
@ -328,47 +235,16 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
|
|||
// sessionStore sessions.SessionStore
|
||||
wantCode int
|
||||
}{
|
||||
{"good",
|
||||
http.MethodGet,
|
||||
"https://corp.pomerium.io/",
|
||||
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
|
||||
fmt.Sprint(time.Now().Unix()),
|
||||
identity.MockProvider{},
|
||||
sessions.MockCSRFStore{},
|
||||
http.StatusFound,
|
||||
},
|
||||
{"bad timestamp",
|
||||
http.MethodGet,
|
||||
"https://corp.pomerium.io/",
|
||||
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
|
||||
fmt.Sprint(time.Now().Add(10 * time.Hour).Unix()),
|
||||
identity.MockProvider{},
|
||||
sessions.MockCSRFStore{},
|
||||
http.StatusBadRequest,
|
||||
},
|
||||
{"missing redirect",
|
||||
http.MethodGet,
|
||||
"",
|
||||
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
|
||||
fmt.Sprint(time.Now().Unix()),
|
||||
identity.MockProvider{},
|
||||
sessions.MockCSRFStore{},
|
||||
http.StatusBadRequest,
|
||||
},
|
||||
{"malformed redirect",
|
||||
http.MethodGet,
|
||||
"https://pomerium.com%zzzzz",
|
||||
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
|
||||
fmt.Sprint(time.Now().Unix()),
|
||||
identity.MockProvider{},
|
||||
sessions.MockCSRFStore{},
|
||||
http.StatusBadRequest,
|
||||
},
|
||||
{"good", http.MethodGet, "https://corp.pomerium.io/", "https://corp.pomerium.io/", redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), fmt.Sprint(time.Now().Unix()), identity.MockProvider{}, sessions.MockCSRFStore{}, http.StatusFound},
|
||||
{"bad timestamp", http.MethodGet, "https://corp.pomerium.io/", "https://corp.pomerium.io/", redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), fmt.Sprint(time.Now().Add(10 * time.Hour).Unix()), identity.MockProvider{}, sessions.MockCSRFStore{}, http.StatusBadRequest},
|
||||
{"missing redirect", http.MethodGet, "https://corp.pomerium.io/", "", redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), fmt.Sprint(time.Now().Unix()), identity.MockProvider{}, sessions.MockCSRFStore{}, http.StatusBadRequest},
|
||||
{"malformed redirect", http.MethodGet, "https://corp.pomerium.io/", "https://pomerium.com%zzzzz", redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), fmt.Sprint(time.Now().Unix()), identity.MockProvider{}, sessions.MockCSRFStore{}, http.StatusBadRequest},
|
||||
{"different domains", http.MethodGet, "https://corp.notpomerium.io/", "https://corp.pomerium.io/", redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), fmt.Sprint(time.Now().Unix()), identity.MockProvider{}, sessions.MockCSRFStore{}, http.StatusBadRequest},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Authenticate{
|
||||
RedirectURL: uriParse("http://www.pomerium.io"),
|
||||
RedirectURL: uriParse(tt.redirectURLSetting),
|
||||
csrfStore: tt.csrfStore,
|
||||
provider: tt.provider,
|
||||
SharedKey: "secret",
|
||||
|
@ -383,18 +259,19 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
|
|||
u.RawQuery = params.Encode()
|
||||
|
||||
r := httptest.NewRequest(tt.method, u.String(), nil)
|
||||
r.Header.Set("Accept", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
a.OAuthStart(w, r)
|
||||
if status := w.Code; status != tt.wantCode {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
|
||||
t.Errorf("handler returned wrong status code: got %v want %v\n%v", status, tt.wantCode, w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticate_getOAuthCallback(t *testing.T) {
|
||||
|
||||
func TestAuthenticate_OAuthCallback(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
|
@ -409,215 +286,21 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
|
|||
csrfStore sessions.MockCSRFStore
|
||||
|
||||
want string
|
||||
wantErr bool
|
||||
wantCode int
|
||||
}{
|
||||
{"good",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"https://corp.pomerium.io",
|
||||
false,
|
||||
},
|
||||
{"get csrf error",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
GetError: errors.New("error"),
|
||||
Cookie: &http.Cookie{Value: "not nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"csrf nonce error",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "not nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"failed authenticate",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateError: errors.New("error"),
|
||||
},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"failed save session",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{SaveError: errors.New("error")},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
|
||||
{"error returned",
|
||||
http.MethodGet,
|
||||
"idp error",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"empty code",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"invalid state string",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
"nonce:https://corp.pomerium.io",
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"malformed state",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"invalid redirect uri",
|
||||
http.MethodGet,
|
||||
"",
|
||||
"code",
|
||||
base64.URLEncoding.EncodeToString([]byte("nonce:corp.pomerium.io")),
|
||||
"https://authenticate.pomerium.io",
|
||||
&sessions.MockSessionStore{},
|
||||
identity.MockProvider{
|
||||
AuthenticateResponse: sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}},
|
||||
sessions.MockCSRFStore{
|
||||
ResponseCSRF: "csrf",
|
||||
Cookie: &http.Cookie{Value: "nonce"}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{"good", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "https://corp.pomerium.io", http.StatusFound},
|
||||
{"get csrf error", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", GetError: errors.New("error"), Cookie: &http.Cookie{Value: "not nonce"}}, "", http.StatusInternalServerError},
|
||||
{"csrf nonce error", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "not nonce"}}, "", http.StatusInternalServerError},
|
||||
{"failed authenticate", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateError: errors.New("error")}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusInternalServerError},
|
||||
{"failed save session", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{SaveError: errors.New("error")}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusInternalServerError},
|
||||
{"provider returned error", http.MethodGet, "idp error", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusBadRequest},
|
||||
{"empty code", http.MethodGet, "", "", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusBadRequest},
|
||||
{"invalid state string", http.MethodGet, "", "code", "nonce:https://corp.pomerium.io", "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusInternalServerError},
|
||||
{"malformed state", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusInternalServerError},
|
||||
{"invalid redirect uri", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusBadRequest},
|
||||
{"malformed form", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "", http.StatusBadRequest},
|
||||
{"bad redirect uri", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:http://^^^")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "https://corp.pomerium.io", http.StatusBadRequest},
|
||||
{"different domains", http.MethodGet, "", "code", base64.URLEncoding.EncodeToString([]byte("nonce:http://some.example.notpomerium.io")), "https://authenticate.pomerium.io", &sessions.MockSessionStore{}, identity.MockProvider{AuthenticateResponse: sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, sessions.MockCSRFStore{ResponseCSRF: "csrf", Cookie: &http.Cookie{Value: "nonce"}}, "https://corp.pomerium.io", http.StatusBadRequest},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -636,17 +319,18 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
|
|||
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
if tt.name == "malformed form" {
|
||||
u.RawQuery = "example=%zzzzz"
|
||||
}
|
||||
r := httptest.NewRequest(tt.method, u.String(), nil)
|
||||
r.Header.Set("Accept", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
got, err := a.getOAuthCallback(w, r)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Authenticate.getOAuthCallback() error = %v, wantErr %v", err, tt.wantErr)
|
||||
a.OAuthCallback(w, r)
|
||||
if w.Result().StatusCode != tt.wantCode {
|
||||
t.Errorf("Authenticate.OAuthCallback() error = %v, wantErr %v\n%v", w.Result().StatusCode, tt.wantCode, w.Body.String())
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("Authenticate.getOAuthCallback() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- HTTP status codes now better adhere to [RFC7235](https://tools.ietf.org/html/rfc7235). In particular, authentication failures reply with [401 Unauthorized](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401) while authorization failures reply with [403 Forbidden](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403). [GH-272](https://github.com/pomerium/pomerium/pull/272)
|
||||
|
||||
### Changed
|
||||
|
||||
- A policy's custom certificate authority can set as a file or a base64 encoded blob(`tls_custom_ca`/`tls_custom_ca_file`). [GH-259](https://github.com/pomerium/pomerium/pull/259)
|
||||
|
|
1
go.mod
1
go.mod
|
@ -26,6 +26,7 @@ require (
|
|||
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae // indirect
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
|
||||
google.golang.org/api v0.6.0
|
||||
google.golang.org/appengine v1.6.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20190611190212-a7e196e89fd3 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -257,6 +257,8 @@ golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBn
|
|||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
|
|
|
@ -6,27 +6,64 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
)
|
||||
|
||||
// Error reports an http error, its http status code, a custom message, and
|
||||
// whether it is CanDebug.
|
||||
type Error struct {
|
||||
// Error formats creates a HTTP error with code, user friendly (and safe) error
|
||||
// message. If nil or empty:
|
||||
// HTTP status code defaults to 500.
|
||||
// Message defaults to the text of the status code.
|
||||
func Error(message string, code int, err error) error {
|
||||
if code == 0 {
|
||||
code = http.StatusInternalServerError
|
||||
}
|
||||
if message == "" {
|
||||
message = http.StatusText(code)
|
||||
}
|
||||
return &httpError{Message: message, Code: code, Err: err}
|
||||
}
|
||||
|
||||
type httpError struct {
|
||||
// Message to present to the end user.
|
||||
Message string
|
||||
// HTTP status codes as registered with IANA.
|
||||
Code int
|
||||
CanDebug bool
|
||||
|
||||
Err error // the cause
|
||||
}
|
||||
|
||||
// Error fulfills the error interface, returning a string representation of the error.
|
||||
func (h Error) Error() string {
|
||||
return fmt.Sprintf("%d %s: %s", h.Code, http.StatusText(h.Code), h.Message)
|
||||
func (e *httpError) Error() string {
|
||||
s := fmt.Sprintf("%d %s: %s", e.Code, http.StatusText(e.Code), e.Message)
|
||||
if e.Err != nil {
|
||||
return s + ": " + e.Err.Error()
|
||||
}
|
||||
return s
|
||||
}
|
||||
func (e *httpError) Unwrap() error { return e.Err }
|
||||
|
||||
// ErrorResponse renders an error page for errors given a message and a status code.
|
||||
// If no message is passed, defaults to the text of the status code.
|
||||
func ErrorResponse(rw http.ResponseWriter, r *http.Request, e *Error) {
|
||||
// Timeout reports whether this error represents a user debuggable error.
|
||||
func (e *httpError) Debugable() bool { return e.Code == http.StatusUnauthorized }
|
||||
|
||||
// ErrorResponse renders an error page given an error. If the error is a
|
||||
// http error from this package, a user friendly message is set, http status code,
|
||||
// the ability to debug are also set.
|
||||
func ErrorResponse(rw http.ResponseWriter, r *http.Request, e error) {
|
||||
statusCode := http.StatusInternalServerError // default status code to return
|
||||
errorString := e.Error()
|
||||
var canDebug bool
|
||||
var requestID string
|
||||
var httpError *httpError
|
||||
// if this is an HTTPError, we can add some additional useful information
|
||||
if xerrors.As(e, &httpError) {
|
||||
canDebug = httpError.Debugable()
|
||||
statusCode = httpError.Code
|
||||
errorString = httpError.Message
|
||||
}
|
||||
log.FromRequest(r).Error().Err(e).Str("http-message", errorString).Int("http-code", statusCode).Msg("http-error")
|
||||
|
||||
if id, ok := log.IDFromRequest(r); ok {
|
||||
requestID = id
|
||||
}
|
||||
|
@ -34,10 +71,10 @@ func ErrorResponse(rw http.ResponseWriter, r *http.Request, e *Error) {
|
|||
var response struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
response.Error = e.Message
|
||||
writeJSONResponse(rw, e.Code, response)
|
||||
response.Error = e.Error()
|
||||
writeJSONResponse(rw, statusCode, response)
|
||||
} else {
|
||||
rw.WriteHeader(e.Code)
|
||||
rw.WriteHeader(statusCode)
|
||||
t := struct {
|
||||
Code int
|
||||
Title string
|
||||
|
@ -45,11 +82,11 @@ func ErrorResponse(rw http.ResponseWriter, r *http.Request, e *Error) {
|
|||
RequestID string
|
||||
CanDebug bool
|
||||
}{
|
||||
Code: e.Code,
|
||||
Title: http.StatusText(e.Code),
|
||||
Message: e.Message,
|
||||
Code: statusCode,
|
||||
Title: http.StatusText(statusCode),
|
||||
Message: errorString,
|
||||
RequestID: requestID,
|
||||
CanDebug: e.CanDebug,
|
||||
CanDebug: canDebug,
|
||||
}
|
||||
templates.New().ExecuteTemplate(rw, "error.html", t)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package httputil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestErrorResponse(t *testing.T) {
|
||||
|
@ -11,10 +14,10 @@ func TestErrorResponse(t *testing.T) {
|
|||
name string
|
||||
rw http.ResponseWriter
|
||||
r *http.Request
|
||||
e *Error
|
||||
e *httpError
|
||||
}{
|
||||
{"good", httptest.NewRecorder(), &http.Request{Method: http.MethodGet}, &Error{Code: http.StatusBadRequest, Message: "missing id token"}},
|
||||
{"good json", httptest.NewRecorder(), &http.Request{Method: http.MethodGet, Header: http.Header{"Accept": []string{"application/json"}}}, &Error{Code: http.StatusBadRequest, Message: "missing id token"}},
|
||||
{"good", httptest.NewRecorder(), &http.Request{Method: http.MethodGet}, &httpError{Code: http.StatusBadRequest, Message: "missing id token"}},
|
||||
{"good json", httptest.NewRecorder(), &http.Request{Method: http.MethodGet, Header: http.Header{"Accept": []string{"application/json"}}}, &httpError{Code: http.StatusBadRequest, Message: "missing id token"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -29,20 +32,44 @@ func TestError_Error(t *testing.T) {
|
|||
name string
|
||||
Message string
|
||||
Code int
|
||||
CanDebug bool
|
||||
InnerErr error
|
||||
want string
|
||||
}{
|
||||
{"good", "short and stout", http.StatusTeapot, false, "418 I'm a teapot: short and stout"},
|
||||
{"good", "short and stout", http.StatusTeapot, nil, "418 I'm a teapot: short and stout"},
|
||||
{"nested error", "short and stout", http.StatusTeapot, errors.New("another error"), "418 I'm a teapot: short and stout: another error"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := Error{
|
||||
h := httpError{
|
||||
Message: tt.Message,
|
||||
Code: tt.Code,
|
||||
CanDebug: tt.CanDebug,
|
||||
Err: tt.InnerErr,
|
||||
}
|
||||
if got := h.Error(); got != tt.want {
|
||||
t.Errorf("Error.Error() = %v, want %v", got, tt.want)
|
||||
got := h.Error()
|
||||
if diff := cmp.Diff(got, tt.want); diff != "" {
|
||||
t.Errorf("Error.Error() = %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_httpError_Error(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
code int
|
||||
err error
|
||||
want string
|
||||
}{
|
||||
{"good", "foobar", 200, nil, "200 OK: foobar"},
|
||||
{"no code", "foobar", 0, nil, "500 Internal Server Error: foobar"},
|
||||
{"no message or code", "", 0, nil, "500 Internal Server Error: Internal Server Error"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := Error(tt.message, tt.code, tt.err)
|
||||
if got := e.Error(); got != tt.want {
|
||||
t.Errorf("httpError.Error() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -40,8 +40,7 @@ func ValidateClientSecret(sharedSecret string) func(next http.Handler) http.Hand
|
|||
defer span.End()
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("couldn't parse form", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
clientSecret := r.Form.Get("shared_secret")
|
||||
|
@ -51,7 +50,7 @@ func ValidateClientSecret(sharedSecret string) func(next http.Handler) http.Hand
|
|||
}
|
||||
|
||||
if clientSecret != sharedSecret {
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError})
|
||||
httputil.ErrorResponse(w, r, httputil.Error("client secret mismatch", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
@ -68,25 +67,16 @@ func ValidateRedirectURI(rootDomain *url.URL) func(next http.Handler) http.Handl
|
|||
defer span.End()
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("couldn't parse form", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
redirectURI, err := url.Parse(r.Form.Get("redirect_uri"))
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("bad redirect_uri", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
if !SameDomain(redirectURI, rootDomain) {
|
||||
httpErr := &httputil.Error{
|
||||
Message: "Invalid redirect parameter",
|
||||
Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("redirect uri and root domain differ", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
@ -117,18 +107,14 @@ func ValidateSignature(sharedSecret string) func(next http.Handler) http.Handler
|
|||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("couldn't parse form", http.StatusBadRequest, err))
|
||||
return
|
||||
}
|
||||
redirectURI := r.Form.Get("redirect_uri")
|
||||
sigVal := r.Form.Get("sig")
|
||||
timestamp := r.Form.Get("ts")
|
||||
if !ValidSignature(redirectURI, sigVal, timestamp, sharedSecret) {
|
||||
httpErr := &httputil.Error{
|
||||
Message: "Cross service signature failed to validate",
|
||||
Code: http.StatusUnauthorized}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("invalid signature", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -145,7 +131,7 @@ func ValidateHost(validHost func(host string) bool) func(next http.Handler) http
|
|||
defer span.End()
|
||||
|
||||
if !validHost(r.Host) {
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusNotFound})
|
||||
httputil.ErrorResponse(w, r, httputil.Error(fmt.Sprintf("%s is not a known route.", r.Host), http.StatusNotFound, nil))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
|
|
@ -175,8 +175,8 @@ func TestValidateClientSecret(t *testing.T) {
|
|||
}{
|
||||
{"simple", "secret", "secret", "secret", http.StatusOK},
|
||||
{"missing get param, valid header", "secret", "", "secret", http.StatusOK},
|
||||
{"missing both", "secret", "", "", http.StatusInternalServerError},
|
||||
{"simple bad", "bad-secret", "secret", "", http.StatusInternalServerError},
|
||||
{"missing both", "secret", "", "", http.StatusBadRequest},
|
||||
{"simple bad", "bad-secret", "secret", "", http.StatusBadRequest},
|
||||
{"malformed, invalid hex digits", "secret", "%zzzzz", "", http.StatusBadRequest},
|
||||
}
|
||||
|
||||
|
@ -218,7 +218,7 @@ func TestValidateSignature(t *testing.T) {
|
|||
status int
|
||||
}{
|
||||
{"valid signature", secretA, goodURL, sig, now, http.StatusOK},
|
||||
{"stale signature", secretA, goodURL, sig, staleTime, http.StatusUnauthorized},
|
||||
{"stale signature", secretA, goodURL, sig, staleTime, http.StatusBadRequest},
|
||||
{"malformed", secretA, goodURL, "%zzzzz", now, http.StatusBadRequest},
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ func (p *Proxy) SignOut(w http.ResponseWriter, r *http.Request) {
|
|||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusBadRequest})
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
uri, err := url.Parse(r.Form.Get("redirect_uri"))
|
||||
|
@ -86,9 +86,7 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
// Encrypt, and save CSRF state. Will be checked on callback.
|
||||
localState, err := p.cipher.Marshal(state)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed to marshal csrf")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
p.csrfStore.SetCSRF(w, r, localState)
|
||||
|
@ -97,9 +95,7 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
// create a different cipher text using another nonce
|
||||
remoteState, err := p.cipher.Marshal(state)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed to encrypt cookie")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -110,9 +106,7 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
// we panic as a failure most likely means the rands entropy source is failing?
|
||||
if remoteState == localState {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Error().Msg("proxy: encrypted state should not match")
|
||||
httpErr := &httputil.Error{Message: http.StatusText(http.StatusBadRequest), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("encrypted state should not match", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -130,15 +124,12 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
// finish the oauth cycle
|
||||
func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed parsing request form")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if errorString := r.Form.Get("error"); errorString != "" {
|
||||
httpErr := &httputil.Error{Message: errorString, Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
if callbackError := r.Form.Get("error"); callbackError != "" {
|
||||
httputil.ErrorResponse(w, r, httputil.Error(callbackError, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -146,18 +137,14 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
remoteStateEncrypted := r.Form.Get("state")
|
||||
remoteStatePlain := new(StateParameter)
|
||||
if err := p.cipher.Unmarshal(remoteStateEncrypted, remoteStatePlain); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: could not unmarshal state")
|
||||
httpErr := &httputil.Error{Message: "Internal error", Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypted CSRF from session storage
|
||||
c, err := p.csrfStore.GetCSRF(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed parsing csrf cookie")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
p.csrfStore.ClearCSRF(w, r)
|
||||
|
@ -165,9 +152,7 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
localStatePlain := new(StateParameter)
|
||||
err = p.cipher.Unmarshal(localStateEncrypted, localStatePlain)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: couldn't unmarshal CSRF")
|
||||
httpErr := &httputil.Error{Message: "Internal error", Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -175,18 +160,16 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
// Likely a replay attack or nonce-reuse.
|
||||
if remoteStateEncrypted == localStateEncrypted {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Error().Msg("proxy: local and remote state should not match")
|
||||
httpErr := &httputil.Error{Message: http.StatusText(http.StatusBadRequest), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
|
||||
httputil.ErrorResponse(w, r, httputil.Error("local and remote state should not match!", http.StatusBadRequest, nil))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypted remote and local state struct (inc. nonce) must match
|
||||
if remoteStatePlain.SessionID != localStatePlain.SessionID {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Error().Msg("proxy: CSRF mismatch")
|
||||
httpErr := &httputil.Error{Message: "CSRF mismatch", Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("CSRF mismatch", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -225,59 +208,50 @@ func isCORSPreflight(r *http.Request) bool {
|
|||
// Proxy authenticates a request, either proxying the request if it is authenticated,
|
||||
// or starting the authenticate service for validation if not.
|
||||
func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||
if !p.shouldSkipAuthentication(r) {
|
||||
// does a route exist for this request?
|
||||
route, ok := p.router(r)
|
||||
if !ok {
|
||||
httputil.ErrorResponse(w, r, httputil.Error(fmt.Sprintf("%s is not a managed route.", r.Host), http.StatusNotFound, nil))
|
||||
return
|
||||
}
|
||||
|
||||
if p.shouldSkipAuthentication(r) {
|
||||
log.FromRequest(r).Debug().Msg("proxy: access control skipped")
|
||||
route.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := p.restStore.LoadSession(r)
|
||||
// if authorization bearer token does not exist or fails, use cookie store
|
||||
if err != nil || s == nil {
|
||||
s, err = p.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
||||
log.FromRequest(r).Debug().Str("cause", err.Error()).Msg("proxy: invalid session, start auth process")
|
||||
log.FromRequest(r).Debug().Str("cause", err.Error()).Msg("proxy: invalid session, re-authenticating")
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
p.OAuthStart(w, r)
|
||||
return
|
||||
default:
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: unexpected error")
|
||||
httpErr := &httputil.Error{Message: "An unexpected error occurred", Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.authenticate(w, r, s); err != nil {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Debug().Err(err).Msg("proxy: user unauthenticated")
|
||||
httpErr := &httputil.Error{Message: "User unauthenticated", Code: http.StatusForbidden, CanDebug: true}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("User unauthenticated", http.StatusUnauthorized, err))
|
||||
return
|
||||
}
|
||||
authorized, err := p.AuthorizeClient.Authorize(r.Context(), r.Host, s)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed authorization")
|
||||
httpErr := &httputil.Error{Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !authorized {
|
||||
log.FromRequest(r).Warn().Err(err).Msg("proxy: user unauthorized")
|
||||
httpErr := &httputil.Error{Code: http.StatusUnauthorized, CanDebug: true}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error(fmt.Sprintf("%s is not authorized for this route", s.Email), http.StatusForbidden, nil))
|
||||
return
|
||||
}
|
||||
r.Header.Set(HeaderUserID, s.User)
|
||||
r.Header.Set(HeaderEmail, s.RequestEmail())
|
||||
r.Header.Set(HeaderGroups, s.RequestGroups())
|
||||
}
|
||||
|
||||
// We have validated the users request and now proxy their request to the provided upstream.
|
||||
route, ok := p.router(r)
|
||||
if !ok {
|
||||
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusNotFound})
|
||||
return
|
||||
}
|
||||
route.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
|
@ -294,18 +268,15 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if err := p.authenticate(w, r, session); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: authenticate failed")
|
||||
httpErr := &httputil.Error{Code: http.StatusUnauthorized, CanDebug: true}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("User unauthenticated", http.StatusUnauthorized, err))
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL := &url.URL{Scheme: "https", Host: r.Host, Path: "/.pomerium/sign_out"}
|
||||
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), session)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: is admin client")
|
||||
httpErr := &httputil.Error{Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -313,9 +284,7 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
csrf := &StateParameter{SessionID: fmt.Sprintf("%x", cryptutil.GenerateKey())}
|
||||
csrfCookie, err := p.cipher.Marshal(csrf)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed to marshal csrf")
|
||||
httpErr := &httputil.Error{Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
p.csrfStore.SetCSRF(w, r, csrfCookie)
|
||||
|
@ -351,38 +320,29 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
func (p *Proxy) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := p.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
iss, err := session.IssuedAt()
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: couldn't get token's create time")
|
||||
httpErr := &httputil.Error{Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// reject a refresh if it's been less than the refresh cooldown to prevent abuse
|
||||
if time.Since(iss) < p.refreshCooldown {
|
||||
log.FromRequest(r).Error().Dur("cooldown", p.refreshCooldown).Err(err).Msg("proxy: refresh cooldown")
|
||||
httpErr := &httputil.Error{
|
||||
Message: fmt.Sprintf("Session must be %v old before refresh", p.refreshCooldown),
|
||||
Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r,
|
||||
httputil.Error(fmt.Sprintf("Session must be %s old before refreshing", p.refreshCooldown), http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
newSession, err := p.AuthenticateClient.Refresh(r.Context(), session)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Warn().Err(err).Msg("proxy: refresh failed")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
if err = p.sessionStore.SaveSession(w, r, newSession); err != nil {
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/.pomerium", http.StatusFound)
|
||||
|
@ -394,50 +354,35 @@ func (p *Proxy) Refresh(w http.ResponseWriter, r *http.Request) {
|
|||
func (p *Proxy) Impersonate(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: impersonate form")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
session, err := p.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: load session")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
// authorization check -- is this user an admin?
|
||||
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), session)
|
||||
if err != nil || !isAdmin {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: user must be admin to impersonate")
|
||||
httpErr := &httputil.Error{
|
||||
Message: fmt.Sprintf("%s must be and administrator", session.Email),
|
||||
Code: http.StatusForbidden,
|
||||
CanDebug: true}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error(fmt.Sprintf("%s is not an administrator", session.Email), http.StatusForbidden, err))
|
||||
return
|
||||
}
|
||||
// CSRF check -- did this request originate from our form?
|
||||
c, err := p.csrfStore.GetCSRF(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed parsing csrf cookie")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusBadRequest}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
p.csrfStore.ClearCSRF(w, r)
|
||||
encryptedCSRF := c.Value
|
||||
decryptedCSRF := new(StateParameter)
|
||||
if err = p.cipher.Unmarshal(encryptedCSRF, decryptedCSRF); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: couldn't unmarshal CSRF")
|
||||
httpErr := &httputil.Error{Message: "Internal error", Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
if decryptedCSRF.SessionID != r.FormValue("csrf") {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: impersonate CSRF mismatch")
|
||||
httpErr := &httputil.Error{Message: "CSRF mismatch", Code: http.StatusForbidden}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, httputil.Error("CSRF mismatch", http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -446,9 +391,7 @@ func (p *Proxy) Impersonate(w http.ResponseWriter, r *http.Request) {
|
|||
session.ImpersonateGroups = strings.Split(r.FormValue("group"), ",")
|
||||
|
||||
if err := p.sessionStore.SaveSession(w, r, session); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: save session")
|
||||
httpErr := &httputil.Error{Message: err.Error(), Code: http.StatusInternalServerError}
|
||||
httputil.ErrorResponse(w, r, httpErr)
|
||||
httputil.ErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -293,21 +293,21 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
{"good email impersonation", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second), ImpersonateEmail: "test@user.example"}}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK},
|
||||
{"good group impersonation", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second), ImpersonateGroups: []string{"group1", "group2"}}}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK},
|
||||
// same request as above, but with cors_allow_preflight=false in the policy
|
||||
{"valid cors, but not allowed", opts, http.MethodOptions, goodCORSHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized},
|
||||
{"valid cors, but not allowed", opts, http.MethodOptions, goodCORSHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden},
|
||||
// cors allowed, but the request is missing proper headers
|
||||
{"invalid cors headers", optsCORS, http.MethodOptions, badCORSHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized},
|
||||
{"unexpected error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: errors.New("ok")}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError},
|
||||
{"invalid cors headers", optsCORS, http.MethodOptions, badCORSHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden},
|
||||
{"unexpected error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: errors.New("ok")}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest},
|
||||
// redirect to start auth process
|
||||
{"unknown host", opts, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusNotFound},
|
||||
{"user not authorized", opts, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized},
|
||||
{"authorization call failed", opts, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeError: errors.New("error")}, http.StatusInternalServerError},
|
||||
{"user not authorized", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden},
|
||||
{"authorization call failed", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeError: errors.New("error")}, http.StatusInternalServerError},
|
||||
// authenticate errors
|
||||
{"weird load session error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: errors.New("weird"), Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError},
|
||||
{"failed refreshed session", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: &sessions.SessionState{RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RefreshError: errors.New("refresh error")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusForbidden},
|
||||
{"cannot resave refreshed session", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{SaveError: errors.New("weird"), Session: &sessions.SessionState{RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusForbidden},
|
||||
{"authenticate validation error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: false}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusForbidden},
|
||||
{"weird load session error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: errors.New("weird"), Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest},
|
||||
{"failed refreshed session", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: &sessions.SessionState{RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RefreshError: errors.New("refresh error")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized},
|
||||
{"cannot resave refreshed session", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{SaveError: errors.New("weird"), Session: &sessions.SessionState{RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized},
|
||||
{"authenticate validation error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: false}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized},
|
||||
{"public access", optsPublic, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusOK},
|
||||
{"public access, but unknown host", optsPublic, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized},
|
||||
{"public access, but unknown host", optsPublic, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusNotFound},
|
||||
// no session, redirect to login
|
||||
{"no http found (no session)", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: http.ErrNoCookie}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest},
|
||||
{"No policies", optsNoPolicies, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusNotFound},
|
||||
|
@ -410,7 +410,7 @@ func TestProxy_Refresh(t *testing.T) {
|
|||
wantStatus int
|
||||
}{
|
||||
{"good", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusFound},
|
||||
{"cannot load session", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{LoadError: errors.New("load error")}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusBadRequest},
|
||||
{"cannot load session", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{LoadError: errors.New("load error")}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusInternalServerError},
|
||||
{"bad id token", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "bad"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusInternalServerError},
|
||||
{"issue date too soon", timeSinceError, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusBadRequest},
|
||||
{"refresh failure", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{RefreshError: errors.New("err")}, clients.MockAuthorize{}, http.StatusInternalServerError},
|
||||
|
@ -460,11 +460,11 @@ func TestProxy_Impersonate(t *testing.T) {
|
|||
{"session load error", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"non admin users rejected", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden},
|
||||
{"non admin users rejected on error", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusForbidden},
|
||||
{"csrf from store retrieve failure", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}, GetError: errors.New("err")}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusBadRequest},
|
||||
{"csrf from store retrieve failure", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}, GetError: errors.New("err")}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"can't decrypt csrf value", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{UnmarshalError: errors.New("err")}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"decrypted csrf mismatch", false, opts, http.MethodPost, "user@blah.com", "", "CSRF!", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusForbidden},
|
||||
{"decrypted csrf mismatch", false, opts, http.MethodPost, "user@blah.com", "", "CSRF!", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusBadRequest},
|
||||
{"save session failure", false, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{SaveError: errors.New("err"), Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"malformed", true, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusBadRequest},
|
||||
{"malformed", true, opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"groups", false, opts, http.MethodPost, "user@blah.com", "group1,group2", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -511,7 +511,7 @@ func TestProxy_OAuthCallback(t *testing.T) {
|
|||
{"good", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusFound},
|
||||
{"error", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"error": "some error"}, http.StatusBadRequest},
|
||||
{"state err", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "error"}, http.StatusInternalServerError},
|
||||
{"csrf err", sessions.MockCSRFStore{GetError: errors.New("error")}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusBadRequest},
|
||||
{"csrf err", sessions.MockCSRFStore{GetError: errors.New("error")}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||
{"unmarshal err", sessions.MockCSRFStore{Cookie: &http.Cookie{Name: "something_csrf", Value: "unmarshal error"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||
{"malformed", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||
}
|
||||
|
@ -555,7 +555,7 @@ func TestProxy_SignOut(t *testing.T) {
|
|||
{"good post", http.MethodPost, "https://test.example", http.StatusFound},
|
||||
{"good get", http.MethodGet, "https://test.example", http.StatusFound},
|
||||
{"good empty default", http.MethodGet, "", http.StatusFound},
|
||||
{"malformed", http.MethodPost, "", http.StatusBadRequest},
|
||||
{"malformed", http.MethodPost, "", http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue