Don't add pinniped_idp_name pinniped_idp_type params into upstream state

This commit is contained in:
Ryan Richard 2022-05-06 12:00:46 -07:00
parent ec22b5715b
commit 4c44f583e9
2 changed files with 46 additions and 10 deletions

View File

@ -7,6 +7,7 @@ package auth
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"time" "time"
coreosoidc "github.com/coreos/go-oidc/v3/oidc" coreosoidc "github.com/coreos/go-oidc/v3/oidc"
@ -53,6 +54,12 @@ func NewHandler(
return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method) return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method)
} }
// Note that the client might have used supervisoroidc.AuthorizeUpstreamIDPNameParamName and
// supervisoroidc.AuthorizeUpstreamIDPTypeParamName query params to request a certain upstream IDP.
// The Pinniped CLI has been sending these params since v0.9.0.
// Currently, these are ignored because the Supervisor does not yet support logins when multiple IDPs
// are configured. However, these params should be honored in the future when choosing an upstream
// here, e.g. by calling supervisoroidc.FindUpstreamIDPByNameAndType() when the params are present.
oidcUpstream, ldapUpstream, idpType, err := chooseUpstreamIDP(idpLister) oidcUpstream, ldapUpstream, idpType, err := chooseUpstreamIDP(idpLister)
if err != nil { if err != nil {
plog.WarningErr("authorize upstream config", err) plog.WarningErr("authorize upstream config", err)
@ -65,7 +72,7 @@ func NewHandler(
// The client set a username header, so they are trying to log in with a username/password. // The client set a username header, so they are trying to log in with a username/password.
return handleAuthRequestForOIDCUpstreamPasswordGrant(r, w, oauthHelperWithStorage, oidcUpstream) return handleAuthRequestForOIDCUpstreamPasswordGrant(r, w, oauthHelperWithStorage, oidcUpstream)
} }
return handleAuthRequestForOIDCUpstreamAuthcodeGrant(r, w, return handleAuthRequestForOIDCUpstreamBrowserFlow(r, w,
oauthHelperWithoutStorage, oauthHelperWithoutStorage,
generateCSRF, generateNonce, generatePKCE, generateCSRF, generateNonce, generatePKCE,
oidcUpstream, oidcUpstream,
@ -75,7 +82,7 @@ func NewHandler(
) )
} }
// we know it's an AD/LDAP upstream. // We know it's an AD/LDAP upstream.
if len(r.Header.Values(supervisoroidc.AuthorizeUsernameHeaderName)) > 0 || if len(r.Header.Values(supervisoroidc.AuthorizeUsernameHeaderName)) > 0 ||
len(r.Header.Values(supervisoroidc.AuthorizePasswordHeaderName)) > 0 { len(r.Header.Values(supervisoroidc.AuthorizePasswordHeaderName)) > 0 {
// The client set a username header, so they are trying to log in with a username/password. // The client set a username header, so they are trying to log in with a username/password.
@ -236,7 +243,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
return nil return nil
} }
func handleAuthRequestForOIDCUpstreamAuthcodeGrant( func handleAuthRequestForOIDCUpstreamBrowserFlow(
r *http.Request, r *http.Request,
w http.ResponseWriter, w http.ResponseWriter,
oauthHelper fosite.OAuth2Provider, oauthHelper fosite.OAuth2Provider,
@ -487,7 +494,12 @@ func upstreamStateParam(
encoder oidc.Encoder, encoder oidc.Encoder,
) (string, error) { ) (string, error) {
stateParamData := oidc.UpstreamStateParamData{ stateParamData := oidc.UpstreamStateParamData{
AuthParams: authorizeRequester.GetRequestForm().Encode(), // The auth params might have included supervisoroidc.AuthorizeUpstreamIDPNameParamName and
// supervisoroidc.AuthorizeUpstreamIDPTypeParamName, but those can be ignored by other handlers
// that are reading from the encoded upstream state param being built here.
// The UpstreamName and UpstreamType struct fields can be used instead.
// Remove those params here to avoid potential confusion about which should be used later.
AuthParams: removeCustomIDPParams(authorizeRequester.GetRequestForm()).Encode(),
UpstreamName: upstreamName, UpstreamName: upstreamName,
UpstreamType: upstreamType, UpstreamType: upstreamType,
Nonce: nonceValue, Nonce: nonceValue,
@ -502,6 +514,18 @@ func upstreamStateParam(
return encodedStateParamValue, nil return encodedStateParamValue, nil
} }
func removeCustomIDPParams(params url.Values) url.Values {
p := url.Values{}
// Copy all params.
for k, v := range params {
p[k] = v
}
// Remove the unnecessary params.
delete(p, supervisoroidc.AuthorizeUpstreamIDPNameParamName)
delete(p, supervisoroidc.AuthorizeUpstreamIDPTypeParamName)
return p
}
func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken, codec oidc.Encoder) error { func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken, codec oidc.Encoder) error {
encodedCSRFValue, err := codec.Encode(oidc.CSRFCookieEncodingName, csrfValue) encodedCSRFValue, err := codec.Encode(oidc.CSRFCookieEncodingName, csrfValue)
if err != nil { if err != nil {

View File

@ -847,8 +847,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
cookieEncoder: happyCookieEncoder, cookieEncoder: happyCookieEncoder,
method: http.MethodGet, method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "login"}), path: modifiedHappyGetRequestPath(map[string]string{"prompt": "login"}),
contentType: formContentType,
body: encodeQuery(happyGetRequestQueryMap),
wantStatus: http.StatusSeeOther, wantStatus: http.StatusSeeOther,
wantContentType: htmlContentType, wantContentType: htmlContentType,
wantBodyStringWithLocationInHref: true, wantBodyStringWithLocationInHref: true,
@ -856,6 +854,24 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{"prompt": "login"}, "", oidcUpstreamName, "oidc"), nil), wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{"prompt": "login"}, "", oidcUpstreamName, "oidc"), nil),
wantUpstreamStateParamInLocationHeader: true, wantUpstreamStateParamInLocationHeader: true,
}, },
{
name: "OIDC upstream browser flow happy path with custom IDP name and type query params, which are excluded from the query params in the upstream state",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
generateCSRF: happyCSRFGenerator,
generatePKCE: happyPKCEGenerator,
generateNonce: happyNonceGenerator,
stateEncoder: happyStateEncoder,
cookieEncoder: happyCookieEncoder,
method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"pinniped_idp_name": "currently-ignored", "pinniped_idp_type": "oidc"}),
contentType: formContentType,
wantStatus: http.StatusSeeOther,
wantContentType: htmlContentType,
wantBodyStringWithLocationInHref: true,
wantCSRFValueInCookieHeader: happyCSRF,
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", oidcUpstreamName, "oidc"), nil),
wantUpstreamStateParamInLocationHeader: true,
},
{ {
name: "OIDC upstream browser flow happy path with extra params that get passed through", name: "OIDC upstream browser flow happy path with extra params that get passed through",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().WithAdditionalAuthcodeParams(map[string]string{"prompt": "consent", "abc": "123", "def": "456"}).Build()), idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().WithAdditionalAuthcodeParams(map[string]string{"prompt": "consent", "abc": "123", "def": "456"}).Build()),
@ -866,8 +882,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
cookieEncoder: happyCookieEncoder, cookieEncoder: happyCookieEncoder,
method: http.MethodGet, method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "login"}), path: modifiedHappyGetRequestPath(map[string]string{"prompt": "login"}),
contentType: formContentType,
body: encodeQuery(happyGetRequestQueryMap),
wantStatus: http.StatusSeeOther, wantStatus: http.StatusSeeOther,
wantContentType: htmlContentType, wantContentType: htmlContentType,
wantBodyStringWithLocationInHref: true, wantBodyStringWithLocationInHref: true,
@ -885,8 +899,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
cookieEncoder: happyCookieEncoder, cookieEncoder: happyCookieEncoder,
method: http.MethodGet, method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none"}), path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none"}),
contentType: formContentType,
body: encodeQuery(happyGetRequestQueryMap),
wantStatus: http.StatusSeeOther, wantStatus: http.StatusSeeOther,
wantContentType: jsonContentType, wantContentType: jsonContentType,
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeLoginRequiredErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeLoginRequiredErrorQuery),