Implement post_login_handler.go to accept form post and auth to LDAP/AD
Also extract some helpers from auth_handler.go so they can be shared with the new handler.
This commit is contained in:
parent
646c6ec9ed
commit
69e5169fc5
@ -7,24 +7,21 @@ 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"
|
||||||
"github.com/felixge/httpsnoop"
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
"github.com/ory/fosite/token/jwt"
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
supervisoroidc "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
supervisoroidc "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
||||||
"go.pinniped.dev/internal/authenticators"
|
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
"go.pinniped.dev/internal/httputil/securityheader"
|
"go.pinniped.dev/internal/httputil/securityheader"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/oidc/csrftoken"
|
"go.pinniped.dev/internal/oidc/csrftoken"
|
||||||
"go.pinniped.dev/internal/oidc/downstreamsession"
|
"go.pinniped.dev/internal/oidc/downstreamsession"
|
||||||
|
"go.pinniped.dev/internal/oidc/login"
|
||||||
"go.pinniped.dev/internal/oidc/provider"
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
"go.pinniped.dev/internal/plog"
|
"go.pinniped.dev/internal/plog"
|
||||||
"go.pinniped.dev/internal/psession"
|
"go.pinniped.dev/internal/psession"
|
||||||
@ -127,36 +124,19 @@ func handleAuthRequestForLDAPUpstreamCLIFlow(
|
|||||||
return httperr.New(http.StatusBadGateway, "unexpected error during upstream authentication")
|
return httperr.New(http.StatusBadGateway, "unexpected error during upstream authentication")
|
||||||
}
|
}
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Username/password not accepted by LDAP provider."), true)
|
fosite.ErrAccessDenied.WithHintf("Username/password not accepted by LDAP provider."), true)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := downstreamSubjectFromUpstreamLDAP(ldapUpstream, authenticateResponse)
|
subject := downstreamsession.DownstreamSubjectFromUpstreamLDAP(ldapUpstream, authenticateResponse)
|
||||||
username = authenticateResponse.User.GetName()
|
username = authenticateResponse.User.GetName()
|
||||||
groups := authenticateResponse.User.GetGroups()
|
groups := authenticateResponse.User.GetGroups()
|
||||||
dn := authenticateResponse.DN
|
customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream, idpType, authenticateResponse)
|
||||||
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
||||||
|
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, true)
|
||||||
|
|
||||||
customSessionData := &psession.CustomSessionData{
|
return nil
|
||||||
ProviderUID: ldapUpstream.GetResourceUID(),
|
|
||||||
ProviderName: ldapUpstream.GetName(),
|
|
||||||
ProviderType: idpType,
|
|
||||||
}
|
|
||||||
|
|
||||||
if idpType == psession.ProviderTypeLDAP {
|
|
||||||
customSessionData.LDAP = &psession.LDAPSessionData{
|
|
||||||
UserDN: dn,
|
|
||||||
ExtraRefreshAttributes: authenticateResponse.ExtraRefreshAttributes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if idpType == psession.ProviderTypeActiveDirectory {
|
|
||||||
customSessionData.ActiveDirectory = &psession.ActiveDirectorySessionData{
|
|
||||||
UserDN: dn,
|
|
||||||
ExtraRefreshAttributes: authenticateResponse.ExtraRefreshAttributes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeDownstreamSessionAndReturnAuthcodeRedirect(r, w,
|
|
||||||
oauthHelper, authorizeRequester, subject, username, groups, customSessionData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthRequestForLDAPUpstreamBrowserFlow(
|
func handleAuthRequestForLDAPUpstreamBrowserFlow(
|
||||||
@ -191,20 +171,7 @@ func handleAuthRequestForLDAPUpstreamBrowserFlow(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
loginURL, err := url.Parse(downstreamIssuer + "/login")
|
return login.RedirectToLoginPage(r, w, downstreamIssuer, encodedStateParamValue, login.ShowNoError)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q := loginURL.Query()
|
|
||||||
q.Set("state", encodedStateParamValue)
|
|
||||||
loginURL.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
http.Redirect(w, r,
|
|
||||||
loginURL.String(),
|
|
||||||
http.StatusSeeOther, // match fosite and https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
||||||
@ -225,9 +192,10 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
|
|
||||||
if !oidcUpstream.AllowsPasswordGrant() {
|
if !oidcUpstream.AllowsPasswordGrant() {
|
||||||
// Return a user-friendly error for this case which is entirely within our control.
|
// Return a user-friendly error for this case which is entirely within our control.
|
||||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHint(
|
fosite.ErrAccessDenied.WithHint(
|
||||||
"Resource owner password credentials grant is not allowed for this upstream provider according to its configuration."), true)
|
"Resource owner password credentials grant is not allowed for this upstream provider according to its configuration."), true)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := oidcUpstream.PasswordCredentialsGrantAndValidateTokens(r.Context(), username, password)
|
token, err := oidcUpstream.PasswordCredentialsGrantAndValidateTokens(r.Context(), username, password)
|
||||||
@ -239,26 +207,33 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
// However, the exact response is undefined in the sense that there is no such thing as a password grant in
|
// However, the exact response is undefined in the sense that there is no such thing as a password grant in
|
||||||
// the OIDC spec, so we don't try too hard to read the upstream errors in this case. (E.g. Dex departs from the
|
// the OIDC spec, so we don't try too hard to read the upstream errors in this case. (E.g. Dex departs from the
|
||||||
// spec and returns something other than an "invalid_grant" error for bad resource owner credentials.)
|
// spec and returns something other than an "invalid_grant" error for bad resource owner credentials.)
|
||||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithDebug(err.Error()), true) // WithDebug hides the error from the client
|
fosite.ErrAccessDenied.WithDebug(err.Error()), true) // WithDebug hides the error from the client
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(oidcUpstream, token.IDToken.Claims)
|
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(oidcUpstream, token.IDToken.Claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return a user-friendly error for this case which is entirely within our control.
|
// Return a user-friendly error for this case which is entirely within our control.
|
||||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
||||||
)
|
)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
customSessionData, err := downstreamsession.MakeDownstreamOIDCCustomSessionData(oidcUpstream, token)
|
customSessionData, err := downstreamsession.MakeDownstreamOIDCCustomSessionData(oidcUpstream, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
||||||
)
|
)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeDownstreamSessionAndReturnAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, subject, username, groups, customSessionData)
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
||||||
|
|
||||||
|
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, true)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthRequestForOIDCUpstreamAuthcodeGrant(
|
func handleAuthRequestForOIDCUpstreamAuthcodeGrant(
|
||||||
@ -322,78 +297,11 @@ func handleAuthRequestForOIDCUpstreamAuthcodeGrant(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAuthorizeError(w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester, err error, isBrowserless bool) error {
|
|
||||||
if plog.Enabled(plog.LevelTrace) {
|
|
||||||
// When trace level logging is enabled, include the stack trace in the log message.
|
|
||||||
keysAndValues := oidc.FositeErrorForLog(err)
|
|
||||||
errWithStack := errors.WithStack(err)
|
|
||||||
keysAndValues = append(keysAndValues, "errWithStack")
|
|
||||||
// klog always prints error values using %s, which does not include stack traces,
|
|
||||||
// so convert the error to a string which includes the stack trace here.
|
|
||||||
keysAndValues = append(keysAndValues, fmt.Sprintf("%+v", errWithStack))
|
|
||||||
plog.Trace("authorize response error", keysAndValues...)
|
|
||||||
} else {
|
|
||||||
plog.Info("authorize response error", oidc.FositeErrorForLog(err)...)
|
|
||||||
}
|
|
||||||
if isBrowserless {
|
|
||||||
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
|
||||||
}
|
|
||||||
// Return an error according to OIDC spec 3.1.2.6 (second paragraph).
|
|
||||||
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeDownstreamSessionAndReturnAuthcodeRedirect(
|
|
||||||
r *http.Request,
|
|
||||||
w http.ResponseWriter,
|
|
||||||
oauthHelper fosite.OAuth2Provider,
|
|
||||||
authorizeRequester fosite.AuthorizeRequester,
|
|
||||||
subject string,
|
|
||||||
username string,
|
|
||||||
groups []string,
|
|
||||||
customSessionData *psession.CustomSessionData,
|
|
||||||
) error {
|
|
||||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
|
||||||
|
|
||||||
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
|
||||||
if err != nil {
|
|
||||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester, err, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
|
||||||
oauthHelper.WriteAuthorizeResponse(w, authorizeRequester, authorizeResponder)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rewriteStatusSeeOtherToStatusFoundForBrowserless(w http.ResponseWriter) http.ResponseWriter {
|
|
||||||
// rewrite http.StatusSeeOther to http.StatusFound for backwards compatibility with old pinniped CLIs.
|
|
||||||
// we can drop this in a few releases once we feel enough time has passed for users to update.
|
|
||||||
//
|
|
||||||
// WriteAuthorizeResponse/WriteAuthorizeError calls used to result in http.StatusFound until
|
|
||||||
// https://github.com/ory/fosite/pull/636 changed it to http.StatusSeeOther to address
|
|
||||||
// https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11
|
|
||||||
// Safari has the bad behavior in the case of http.StatusFound and not just http.StatusTemporaryRedirect.
|
|
||||||
//
|
|
||||||
// in the browserless flows, the OAuth client is the pinniped CLI and it already has access to the user's
|
|
||||||
// password. Thus there is no security issue with using http.StatusFound vs. http.StatusSeeOther.
|
|
||||||
return httpsnoop.Wrap(w, httpsnoop.Hooks{
|
|
||||||
WriteHeader: func(delegate httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
|
|
||||||
return func(code int) {
|
|
||||||
if code == http.StatusSeeOther {
|
|
||||||
code = http.StatusFound
|
|
||||||
}
|
|
||||||
delegate(code)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireNonEmptyUsernameAndPasswordHeaders(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester) (string, string, bool) {
|
func requireNonEmptyUsernameAndPasswordHeaders(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester) (string, string, bool) {
|
||||||
username := r.Header.Get(supervisoroidc.AuthorizeUsernameHeaderName)
|
username := r.Header.Get(supervisoroidc.AuthorizeUsernameHeaderName)
|
||||||
password := r.Header.Get(supervisoroidc.AuthorizePasswordHeaderName)
|
password := r.Header.Get(supervisoroidc.AuthorizePasswordHeaderName)
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
_ = writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Missing or blank username or password."), true)
|
fosite.ErrAccessDenied.WithHintf("Missing or blank username or password."), true)
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
@ -403,7 +311,7 @@ func requireNonEmptyUsernameAndPasswordHeaders(r *http.Request, w http.ResponseW
|
|||||||
func newAuthorizeRequest(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, isBrowserless bool) (fosite.AuthorizeRequester, bool) {
|
func newAuthorizeRequest(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, isBrowserless bool) (fosite.AuthorizeRequester, bool) {
|
||||||
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
|
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = writeAuthorizeError(w, oauthHelper, authorizeRequester, err, isBrowserless)
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, isBrowserless)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,7 +343,8 @@ func readCSRFCookie(r *http.Request, codec oidc.Decoder) csrftoken.CSRFToken {
|
|||||||
return csrfFromCookie
|
return csrfFromCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select either an OIDC, an LDAP or an AD IDP, or return an error.
|
// chooseUpstreamIDP selects either an OIDC, an LDAP, or an AD IDP, or returns an error.
|
||||||
|
// Note that AD and LDAP IDPs both return the same interface type, but different ProviderTypes values.
|
||||||
func chooseUpstreamIDP(idpLister oidc.UpstreamIdentityProvidersLister) (provider.UpstreamOIDCIdentityProviderI, provider.UpstreamLDAPIdentityProviderI, psession.ProviderType, error) {
|
func chooseUpstreamIDP(idpLister oidc.UpstreamIdentityProvidersLister) (provider.UpstreamOIDCIdentityProviderI, provider.UpstreamLDAPIdentityProviderI, psession.ProviderType, error) {
|
||||||
oidcUpstreams := idpLister.GetOIDCIdentityProviders()
|
oidcUpstreams := idpLister.GetOIDCIdentityProviders()
|
||||||
ldapUpstreams := idpLister.GetLDAPIdentityProviders()
|
ldapUpstreams := idpLister.GetLDAPIdentityProviders()
|
||||||
@ -503,7 +412,8 @@ func handleBrowserAuthRequest(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", writeAuthorizeError(w, oauthHelper, authorizeRequester, err, false)
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, false)
|
||||||
|
return "", "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
csrfValue, nonceValue, pkceValue, err := generateValues(generateCSRF, generateNonce, generatePKCE)
|
csrfValue, nonceValue, pkceValue, err := generateValues(generateCSRF, generateNonce, generatePKCE)
|
||||||
@ -532,7 +442,8 @@ func handleBrowserAuthRequest(
|
|||||||
|
|
||||||
promptParam := r.Form.Get(promptParamName)
|
promptParam := r.Form.Get(promptParamName)
|
||||||
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, coreosoidc.ScopeOpenID) {
|
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, coreosoidc.ScopeOpenID) {
|
||||||
return "", "", "", writeAuthorizeError(w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired, false)
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired, false)
|
||||||
|
return "", "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if csrfFromCookie == "" {
|
if csrfFromCookie == "" {
|
||||||
@ -608,8 +519,3 @@ func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downstreamSubjectFromUpstreamLDAP(ldapUpstream provider.UpstreamLDAPIdentityProviderI, authenticateResponse *authenticators.Response) string {
|
|
||||||
ldapURL := *ldapUpstream.GetURL()
|
|
||||||
return downstreamsession.DownstreamLDAPSubject(authenticateResponse.User.GetUID(), ldapURL)
|
|
||||||
}
|
|
||||||
|
@ -70,6 +70,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
downstreamClientID = "pinniped-cli"
|
downstreamClientID = "pinniped-cli"
|
||||||
upstreamLDAPURL = "ldaps://some-ldap-host:123?base=ou%3Dusers%2Cdc%3Dpinniped%2Cdc%3Ddev"
|
upstreamLDAPURL = "ldaps://some-ldap-host:123?base=ou%3Dusers%2Cdc%3Dpinniped%2Cdc%3Ddev"
|
||||||
htmlContentType = "text/html; charset=utf-8"
|
htmlContentType = "text/html; charset=utf-8"
|
||||||
|
jsonContentType = "application/json; charset=utf-8"
|
||||||
|
formContentType = "application/x-www-form-urlencoded"
|
||||||
)
|
)
|
||||||
|
|
||||||
require.Len(t, happyState, 8, "we expect fosite to allow 8 byte state params, so we want to test that boundary case")
|
require.Len(t, happyState, 8, "we expect fosite to allow 8 byte state params, so we want to test that boundary case")
|
||||||
@ -718,7 +720,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
cookieEncoder: happyCookieEncoder,
|
cookieEncoder: happyCookieEncoder,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "",
|
wantContentType: "",
|
||||||
@ -737,7 +739,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
cookieEncoder: happyCookieEncoder,
|
cookieEncoder: happyCookieEncoder,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "",
|
wantContentType: "",
|
||||||
@ -756,7 +758,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
cookieEncoder: happyCookieEncoder,
|
cookieEncoder: happyCookieEncoder,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "",
|
wantContentType: "",
|
||||||
@ -770,7 +772,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().Build()),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().Build()),
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
@ -794,7 +796,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
@ -817,7 +819,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
@ -845,7 +847,7 @@ 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: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
@ -864,7 +866,7 @@ 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: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
@ -883,10 +885,10 @@ 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: "application/x-www-form-urlencoded",
|
contentType: formContentType,
|
||||||
body: encodeQuery(happyGetRequestQueryMap),
|
body: encodeQuery(happyGetRequestQueryMap),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeLoginRequiredErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeLoginRequiredErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1113,7 +1115,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
Password: "wrong-password",
|
Password: "wrong-password",
|
||||||
}},
|
}},
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1125,7 +1127,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr("wrong-password"),
|
customPasswordHeader: pointer.StringPtr("wrong-password"),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1137,7 +1139,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr("wrong-password"),
|
customPasswordHeader: pointer.StringPtr("wrong-password"),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1149,7 +1151,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1161,7 +1163,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1173,7 +1175,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: nil, // do not send header
|
customUsernameHeader: nil, // do not send header
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1185,7 +1187,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: nil, // do not send header
|
customUsernameHeader: nil, // do not send header
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1197,7 +1199,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: nil, // do not send header
|
customUsernameHeader: nil, // do not send header
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1209,7 +1211,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: nil, // do not send header
|
customPasswordHeader: nil, // do not send header
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1221,7 +1223,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: nil, // do not send header
|
customPasswordHeader: nil, // do not send header
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1234,7 +1236,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUserInfoEndpointErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUserInfoEndpointErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1247,7 +1249,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUserInfoEndpointErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUserInfoEndpointErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1260,7 +1262,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1273,7 +1275,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1286,7 +1288,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1299,7 +1301,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingAccessTokenErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1311,7 +1313,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: nil, // do not send header
|
customPasswordHeader: nil, // do not send header
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1323,7 +1325,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithPasswordGrantDisallowedHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithPasswordGrantDisallowedHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1340,7 +1342,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
"redirect_uri": "http://127.0.0.1/does-not-match-what-is-configured-for-pinniped-cli-client",
|
"redirect_uri": "http://127.0.0.1/does-not-match-what-is-configured-for-pinniped-cli-client",
|
||||||
}),
|
}),
|
||||||
wantStatus: http.StatusBadRequest,
|
wantStatus: http.StatusBadRequest,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1353,7 +1355,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusBadRequest,
|
wantStatus: http.StatusBadRequest,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1366,7 +1368,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusBadRequest,
|
wantStatus: http.StatusBadRequest,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1379,7 +1381,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusBadRequest,
|
wantStatus: http.StatusBadRequest,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1393,7 +1395,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1404,7 +1406,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1413,7 +1415,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1422,7 +1424,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1436,7 +1438,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1448,7 +1450,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1460,7 +1462,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1470,7 +1472,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1482,7 +1484,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1492,7 +1494,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeUnsupportedResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1507,7 +1509,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid profile email tuna"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid profile email tuna"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1519,7 +1521,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1531,7 +1533,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1543,7 +1545,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1558,7 +1560,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1570,7 +1572,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1582,7 +1584,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1592,7 +1594,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1604,7 +1606,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1614,7 +1616,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1629,7 +1631,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"client_id": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"client_id": ""}),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1640,7 +1642,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1649,7 +1651,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"client_id": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"client_id": ""}),
|
||||||
wantStatus: http.StatusUnauthorized,
|
wantStatus: http.StatusUnauthorized,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantBodyJSON: fositeInvalidClientErrorBody,
|
wantBodyJSON: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1663,7 +1665,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge": ""}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1676,7 +1678,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1689,7 +1691,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1705,7 +1707,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": "this-is-not-a-valid-pkce-alg"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": "this-is-not-a-valid-pkce-alg"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1718,7 +1720,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1731,7 +1733,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1747,7 +1749,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": "plain"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": "plain"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1760,7 +1762,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1773,7 +1775,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1789,7 +1791,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": ""}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1802,7 +1804,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1815,7 +1817,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
@ -1833,7 +1835,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none login"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none login"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -1848,7 +1850,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 1, // fosite already stored the authcode before it noticed the error
|
wantUnnecessaryStoredRecords: 1, // fosite already stored the authcode before it noticed the error
|
||||||
@ -1863,7 +1865,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantUnnecessaryStoredRecords: 1, // fosite already stored the authcode before it noticed the error
|
wantUnnecessaryStoredRecords: 1, // fosite already stored the authcode before it noticed the error
|
||||||
@ -2052,7 +2054,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithInvalidEmailVerifiedHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithInvalidEmailVerifiedHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2070,7 +2072,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithFalseEmailVerifiedHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithFalseEmailVerifiedHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2159,7 +2161,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimMissingHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimMissingHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2198,7 +2200,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2213,7 +2215,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimEmptyHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimEmptyHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2228,7 +2230,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimMissingHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimMissingHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2243,7 +2245,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimEmptyHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimEmptyHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2258,7 +2260,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2273,7 +2275,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimMissingHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimMissingHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2288,7 +2290,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimEmptyHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimEmptyHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2303,7 +2305,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2318,7 +2320,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2333,7 +2335,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2348,7 +2350,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithRequiredClaimInvalidFormatHintErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2363,7 +2365,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"state": "short"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"state": "short"}),
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidStateErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidStateErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2375,7 +2377,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidStateErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidStateErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
@ -2387,7 +2389,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: jsonContentType,
|
||||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidStateErrorQuery),
|
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidStateErrorQuery),
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/ory/fosite/token/jwt"
|
"github.com/ory/fosite/token/jwt"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/constable"
|
"go.pinniped.dev/internal/constable"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/oidc/provider"
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
@ -61,6 +62,34 @@ func MakeDownstreamSession(subject string, username string, groups []string, cus
|
|||||||
return openIDSession
|
return openIDSession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeDownstreamLDAPOrADCustomSessionData(
|
||||||
|
ldapUpstream provider.UpstreamLDAPIdentityProviderI,
|
||||||
|
idpType psession.ProviderType,
|
||||||
|
authenticateResponse *authenticators.Response,
|
||||||
|
) *psession.CustomSessionData {
|
||||||
|
customSessionData := &psession.CustomSessionData{
|
||||||
|
ProviderUID: ldapUpstream.GetResourceUID(),
|
||||||
|
ProviderName: ldapUpstream.GetName(),
|
||||||
|
ProviderType: idpType,
|
||||||
|
}
|
||||||
|
|
||||||
|
if idpType == psession.ProviderTypeLDAP {
|
||||||
|
customSessionData.LDAP = &psession.LDAPSessionData{
|
||||||
|
UserDN: authenticateResponse.DN,
|
||||||
|
ExtraRefreshAttributes: authenticateResponse.ExtraRefreshAttributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if idpType == psession.ProviderTypeActiveDirectory {
|
||||||
|
customSessionData.ActiveDirectory = &psession.ActiveDirectorySessionData{
|
||||||
|
UserDN: authenticateResponse.DN,
|
||||||
|
ExtraRefreshAttributes: authenticateResponse.ExtraRefreshAttributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return customSessionData
|
||||||
|
}
|
||||||
|
|
||||||
func MakeDownstreamOIDCCustomSessionData(oidcUpstream provider.UpstreamOIDCIdentityProviderI, token *oidctypes.Token) (*psession.CustomSessionData, error) {
|
func MakeDownstreamOIDCCustomSessionData(oidcUpstream provider.UpstreamOIDCIdentityProviderI, token *oidctypes.Token) (*psession.CustomSessionData, error) {
|
||||||
upstreamSubject, err := ExtractStringClaimValue(oidc.IDTokenSubjectClaim, oidcUpstream.GetName(), token.IDToken.Claims)
|
upstreamSubject, err := ExtractStringClaimValue(oidc.IDTokenSubjectClaim, oidcUpstream.GetName(), token.IDToken.Claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -228,6 +257,11 @@ func ExtractStringClaimValue(claimName string, upstreamIDPName string, idTokenCl
|
|||||||
return valueAsString, nil
|
return valueAsString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DownstreamSubjectFromUpstreamLDAP(ldapUpstream provider.UpstreamLDAPIdentityProviderI, authenticateResponse *authenticators.Response) string {
|
||||||
|
ldapURL := *ldapUpstream.GetURL()
|
||||||
|
return DownstreamLDAPSubject(authenticateResponse.User.GetUID(), ldapURL)
|
||||||
|
}
|
||||||
|
|
||||||
func DownstreamLDAPSubject(uid string, ldapURL url.URL) string {
|
func DownstreamLDAPSubject(uid string, ldapURL url.URL) string {
|
||||||
q := ldapURL.Query()
|
q := ldapURL.Query()
|
||||||
q.Set(oidc.IDTokenSubjectClaim, uid)
|
q.Set(oidc.IDTokenSubjectClaim, uid)
|
||||||
|
@ -5,6 +5,7 @@ package login
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
@ -13,6 +14,19 @@ import (
|
|||||||
"go.pinniped.dev/internal/plog"
|
"go.pinniped.dev/internal/plog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ErrorParamValue string
|
||||||
|
|
||||||
|
const (
|
||||||
|
usernameParamName = "username"
|
||||||
|
passwordParamName = "password"
|
||||||
|
stateParamName = "state"
|
||||||
|
errParamName = "err"
|
||||||
|
|
||||||
|
ShowNoError ErrorParamValue = ""
|
||||||
|
ShowInternalError ErrorParamValue = "internal_error"
|
||||||
|
ShowBadUserPassErr ErrorParamValue = "login_error"
|
||||||
|
)
|
||||||
|
|
||||||
// HandlerFunc is a function that can handle either a GET or POST request for the login endpoint.
|
// HandlerFunc is a function that can handle either a GET or POST request for the login endpoint.
|
||||||
type HandlerFunc func(
|
type HandlerFunc func(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
@ -66,3 +80,30 @@ func NewHandler(
|
|||||||
|
|
||||||
return securityheader.Wrap(loginHandler)
|
return securityheader.Wrap(loginHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RedirectToLoginPage(
|
||||||
|
r *http.Request,
|
||||||
|
w http.ResponseWriter,
|
||||||
|
downstreamIssuer string,
|
||||||
|
encodedStateParamValue string,
|
||||||
|
errToDisplay ErrorParamValue,
|
||||||
|
) error {
|
||||||
|
loginURL, err := url.Parse(downstreamIssuer + oidc.PinnipedLoginPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
q := loginURL.Query()
|
||||||
|
q.Set(stateParamName, encodedStateParamValue)
|
||||||
|
if errToDisplay != ShowNoError {
|
||||||
|
q.Set(errParamName, string(errToDisplay))
|
||||||
|
}
|
||||||
|
loginURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
http.Redirect(w, r,
|
||||||
|
loginURL.String(),
|
||||||
|
http.StatusSeeOther, // match fosite and https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -5,15 +5,84 @@ package login
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
|
"go.pinniped.dev/internal/oidc/downstreamsession"
|
||||||
|
"go.pinniped.dev/internal/plog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPostHandler(upstreamIDPs oidc.UpstreamIdentityProvidersLister, oauthHelper fosite.OAuth2Provider) HandlerFunc {
|
func NewPostHandler(issuerURL string, upstreamIDPs oidc.UpstreamIdentityProvidersLister, oauthHelper fosite.OAuth2Provider) HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, encodedState string, decodedState *oidc.UpstreamStateParamData) error {
|
return func(w http.ResponseWriter, r *http.Request, encodedState string, decodedState *oidc.UpstreamStateParamData) error {
|
||||||
// TODO
|
// Note that the login handler prevents this handler from being called with OIDC upstreams.
|
||||||
|
_, ldapUpstream, idpType, err := oidc.FindUpstreamIDPByNameAndType(upstreamIDPs, decodedState.UpstreamName, decodedState.UpstreamType)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't normally happen because the authorization endpoint ensured that this provider existed
|
||||||
|
// at that time. It would be possible in the unlikely event that the provider was deleted during the login.
|
||||||
|
plog.Error("error finding upstream provider", err)
|
||||||
|
return httperr.Wrap(http.StatusUnprocessableEntity, "error finding upstream provider", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the original params that were used at the authorization endpoint.
|
||||||
|
downstreamAuthParams, err := url.ParseQuery(decodedState.AuthParams)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't really happen because the authorization endpoint encoded these query params correctly.
|
||||||
|
plog.Error("error reading state downstream auth params", err)
|
||||||
|
return httperr.New(http.StatusBadRequest, "error reading state downstream auth params")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate enough of the original authorize request so we can pass it to NewAuthorizeRequest().
|
||||||
|
reconstitutedAuthRequest := &http.Request{Form: downstreamAuthParams}
|
||||||
|
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), reconstitutedAuthRequest)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't really happen because the authorization endpoint has already validated these params
|
||||||
|
// by calling NewAuthorizeRequest() itself.
|
||||||
|
plog.Error("error using state downstream auth params", err)
|
||||||
|
return httperr.New(http.StatusBadRequest, "error using state downstream auth params")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically grant the openid, offline_access, and pinniped:request-audience scopes, but only if they were requested.
|
||||||
|
downstreamsession.GrantScopesIfRequested(authorizeRequester)
|
||||||
|
|
||||||
|
// Get the username and password form params from the POST body.
|
||||||
|
username := r.PostFormValue(usernameParamName)
|
||||||
|
password := r.PostFormValue(passwordParamName)
|
||||||
|
|
||||||
|
// Treat blank username or password as a bad username/password combination, as opposed to an internal error.
|
||||||
|
if username == "" || password == "" {
|
||||||
|
// User forgot to enter one of the required fields.
|
||||||
|
// The user may try to log in again if they'd like, so redirect back to the login page with an error.
|
||||||
|
return RedirectToLoginPage(r, w, issuerURL, encodedState, ShowBadUserPassErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to authenticate the user with the upstream IDP.
|
||||||
|
authenticateResponse, authenticated, err := ldapUpstream.AuthenticateUser(r.Context(), username, password)
|
||||||
|
if err != nil {
|
||||||
|
plog.WarningErr("unexpected error during upstream LDAP authentication", err, "upstreamName", ldapUpstream.GetName())
|
||||||
|
// There was some problem during authentication with the upstream, aside from bad username/password.
|
||||||
|
// The user may try to log in again if they'd like, so redirect back to the login page with an error.
|
||||||
|
return RedirectToLoginPage(r, w, issuerURL, encodedState, ShowInternalError)
|
||||||
|
}
|
||||||
|
if !authenticated {
|
||||||
|
// The upstream did not accept the username/password combination.
|
||||||
|
// The user may try to log in again if they'd like, so redirect back to the login page with an error.
|
||||||
|
return RedirectToLoginPage(r, w, issuerURL, encodedState, ShowBadUserPassErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We had previously interrupted the regular steps of the OIDC authcode flow to show the login page UI.
|
||||||
|
// Now the upstream IDP has authenticated the user, so now we're back into the regular OIDC authcode flow steps.
|
||||||
|
// Both success and error responses from this point onwards should look like the usual fosite redirect
|
||||||
|
// responses, and a happy redirect response will include a downstream authcode.
|
||||||
|
subject := downstreamsession.DownstreamSubjectFromUpstreamLDAP(ldapUpstream, authenticateResponse)
|
||||||
|
username = authenticateResponse.User.GetName()
|
||||||
|
groups := authenticateResponse.User.GetGroups()
|
||||||
|
customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream, idpType, authenticateResponse)
|
||||||
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
||||||
|
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, false)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
693
internal/oidc/login/post_login_handler_test.go
Normal file
693
internal/oidc/login/post_login_handler_test.go
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
|
"go.pinniped.dev/internal/oidc"
|
||||||
|
"go.pinniped.dev/internal/oidc/jwks"
|
||||||
|
"go.pinniped.dev/internal/psession"
|
||||||
|
"go.pinniped.dev/internal/testutil"
|
||||||
|
"go.pinniped.dev/internal/testutil/oidctestutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPostLoginEndpoint(t *testing.T) {
|
||||||
|
const (
|
||||||
|
htmlContentType = "text/html; charset=utf-8"
|
||||||
|
|
||||||
|
happyDownstreamCSRF = "test-csrf"
|
||||||
|
happyDownstreamPKCE = "test-pkce"
|
||||||
|
happyDownstreamNonce = "test-nonce"
|
||||||
|
happyDownstreamStateVersion = "2"
|
||||||
|
happyEncodedUpstreamState = "fake-encoded-state-param-value"
|
||||||
|
|
||||||
|
downstreamIssuer = "https://my-downstream-issuer.com/path"
|
||||||
|
downstreamRedirectURI = "http://127.0.0.1/callback"
|
||||||
|
downstreamClientID = "pinniped-cli"
|
||||||
|
happyDownstreamState = "8b-state"
|
||||||
|
downstreamNonce = "some-nonce-value"
|
||||||
|
downstreamPKCEChallenge = "some-challenge"
|
||||||
|
downstreamPKCEChallengeMethod = "S256"
|
||||||
|
|
||||||
|
ldapUpstreamName = "some-ldap-idp"
|
||||||
|
ldapUpstreamType = "ldap"
|
||||||
|
ldapUpstreamResourceUID = "ldap-resource-uid"
|
||||||
|
activeDirectoryUpstreamName = "some-active-directory-idp"
|
||||||
|
activeDirectoryUpstreamType = "activedirectory"
|
||||||
|
activeDirectoryUpstreamResourceUID = "active-directory-resource-uid"
|
||||||
|
upstreamLDAPURL = "ldaps://some-ldap-host:123?base=ou%3Dusers%2Cdc%3Dpinniped%2Cdc%3Ddev"
|
||||||
|
|
||||||
|
userParam = "username"
|
||||||
|
passParam = "password"
|
||||||
|
badUserPassErrParamValue = "login_error"
|
||||||
|
internalErrParamValue = "internal_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fositeMissingCodeChallengeErrorQuery = map[string]string{
|
||||||
|
"error": "invalid_request",
|
||||||
|
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing.",
|
||||||
|
"state": happyDownstreamState,
|
||||||
|
}
|
||||||
|
|
||||||
|
fositeInvalidCodeChallengeErrorQuery = map[string]string{
|
||||||
|
"error": "invalid_request",
|
||||||
|
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The code_challenge_method is not supported, use S256 instead.",
|
||||||
|
"state": happyDownstreamState,
|
||||||
|
}
|
||||||
|
|
||||||
|
fositeMissingCodeChallengeMethodErrorQuery = map[string]string{
|
||||||
|
"error": "invalid_request",
|
||||||
|
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must use code_challenge_method=S256, plain is not allowed.",
|
||||||
|
"state": happyDownstreamState,
|
||||||
|
}
|
||||||
|
|
||||||
|
fositePromptHasNoneAndOtherValueErrorQuery = map[string]string{
|
||||||
|
"error": "invalid_request",
|
||||||
|
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed.",
|
||||||
|
"state": happyDownstreamState,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
happyDownstreamScopesRequested := []string{"openid"}
|
||||||
|
happyDownstreamScopesGranted := []string{"openid"}
|
||||||
|
|
||||||
|
happyDownstreamRequestParamsQuery := url.Values{
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"scope": []string{strings.Join(happyDownstreamScopesRequested, " ")},
|
||||||
|
"client_id": []string{downstreamClientID},
|
||||||
|
"state": []string{happyDownstreamState},
|
||||||
|
"nonce": []string{downstreamNonce},
|
||||||
|
"code_challenge": []string{downstreamPKCEChallenge},
|
||||||
|
"code_challenge_method": []string{downstreamPKCEChallengeMethod},
|
||||||
|
"redirect_uri": []string{downstreamRedirectURI},
|
||||||
|
}
|
||||||
|
happyDownstreamRequestParams := happyDownstreamRequestParamsQuery.Encode()
|
||||||
|
|
||||||
|
copyOfHappyDownstreamRequestParamsQuery := func() url.Values {
|
||||||
|
params := url.Values{}
|
||||||
|
for k, v := range happyDownstreamRequestParamsQuery {
|
||||||
|
params[k] = make([]string, len(v))
|
||||||
|
copy(params[k], v)
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
happyLDAPDecodedState := &oidc.UpstreamStateParamData{
|
||||||
|
AuthParams: happyDownstreamRequestParams,
|
||||||
|
UpstreamName: ldapUpstreamName,
|
||||||
|
UpstreamType: ldapUpstreamType,
|
||||||
|
Nonce: happyDownstreamNonce,
|
||||||
|
CSRFToken: happyDownstreamCSRF,
|
||||||
|
PKCECode: happyDownstreamPKCE,
|
||||||
|
FormatVersion: happyDownstreamStateVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyHappyLDAPDecodedState := func(edit func(*oidc.UpstreamStateParamData)) *oidc.UpstreamStateParamData {
|
||||||
|
copyOfHappyLDAPDecodedState := *happyLDAPDecodedState
|
||||||
|
edit(©OfHappyLDAPDecodedState)
|
||||||
|
return ©OfHappyLDAPDecodedState
|
||||||
|
}
|
||||||
|
|
||||||
|
happyActiveDirectoryDecodedState := &oidc.UpstreamStateParamData{
|
||||||
|
AuthParams: happyDownstreamRequestParams,
|
||||||
|
UpstreamName: activeDirectoryUpstreamName,
|
||||||
|
UpstreamType: activeDirectoryUpstreamType,
|
||||||
|
Nonce: happyDownstreamNonce,
|
||||||
|
CSRFToken: happyDownstreamCSRF,
|
||||||
|
PKCECode: happyDownstreamPKCE,
|
||||||
|
FormatVersion: happyDownstreamStateVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
happyLDAPUsername := "some-ldap-user"
|
||||||
|
happyLDAPUsernameFromAuthenticator := "some-mapped-ldap-username"
|
||||||
|
happyLDAPPassword := "some-ldap-password" //nolint:gosec
|
||||||
|
happyLDAPUID := "some-ldap-uid"
|
||||||
|
happyLDAPUserDN := "cn=foo,dn=bar"
|
||||||
|
happyLDAPGroups := []string{"group1", "group2", "group3"}
|
||||||
|
happyLDAPExtraRefreshAttribute := "some-refresh-attribute"
|
||||||
|
happyLDAPExtraRefreshValue := "some-refresh-attribute-value"
|
||||||
|
|
||||||
|
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ldapAuthenticateFunc := func(ctx context.Context, username, password string) (*authenticators.Response, bool, error) {
|
||||||
|
if username == "" || password == "" {
|
||||||
|
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
||||||
|
}
|
||||||
|
if username == happyLDAPUsername && password == happyLDAPPassword {
|
||||||
|
return &authenticators.Response{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: happyLDAPUsernameFromAuthenticator,
|
||||||
|
UID: happyLDAPUID,
|
||||||
|
Groups: happyLDAPGroups,
|
||||||
|
},
|
||||||
|
DN: happyLDAPUserDN,
|
||||||
|
ExtraRefreshAttributes: map[string]string{
|
||||||
|
happyLDAPExtraRefreshAttribute: happyLDAPExtraRefreshValue,
|
||||||
|
},
|
||||||
|
}, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
|
Name: ldapUpstreamName,
|
||||||
|
ResourceUID: ldapUpstreamResourceUID,
|
||||||
|
URL: parsedUpstreamLDAPURL,
|
||||||
|
AuthenticateFunc: ldapAuthenticateFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamActiveDirectoryIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
|
Name: activeDirectoryUpstreamName,
|
||||||
|
ResourceUID: activeDirectoryUpstreamResourceUID,
|
||||||
|
URL: parsedUpstreamLDAPURL,
|
||||||
|
AuthenticateFunc: ldapAuthenticateFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
erroringUpstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
|
Name: ldapUpstreamName,
|
||||||
|
ResourceUID: ldapUpstreamResourceUID,
|
||||||
|
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticators.Response, bool, error) {
|
||||||
|
return nil, false, fmt.Errorf("some ldap upstream auth error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHappyActiveDirectoryUpstreamCustomSession := &psession.CustomSessionData{
|
||||||
|
ProviderUID: activeDirectoryUpstreamResourceUID,
|
||||||
|
ProviderName: activeDirectoryUpstreamName,
|
||||||
|
ProviderType: psession.ProviderTypeActiveDirectory,
|
||||||
|
OIDC: nil,
|
||||||
|
LDAP: nil,
|
||||||
|
ActiveDirectory: &psession.ActiveDirectorySessionData{
|
||||||
|
UserDN: happyLDAPUserDN,
|
||||||
|
ExtraRefreshAttributes: map[string]string{happyLDAPExtraRefreshAttribute: happyLDAPExtraRefreshValue},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHappyLDAPUpstreamCustomSession := &psession.CustomSessionData{
|
||||||
|
ProviderUID: ldapUpstreamResourceUID,
|
||||||
|
ProviderName: ldapUpstreamName,
|
||||||
|
ProviderType: psession.ProviderTypeLDAP,
|
||||||
|
OIDC: nil,
|
||||||
|
LDAP: &psession.LDAPSessionData{
|
||||||
|
UserDN: happyLDAPUserDN,
|
||||||
|
ExtraRefreshAttributes: map[string]string{happyLDAPExtraRefreshAttribute: happyLDAPExtraRefreshValue},
|
||||||
|
},
|
||||||
|
ActiveDirectory: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
||||||
|
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid&state=` + happyDownstreamState
|
||||||
|
|
||||||
|
happyUsernamePasswordFormParams := url.Values{userParam: []string{happyLDAPUsername}, passParam: []string{happyLDAPPassword}}
|
||||||
|
|
||||||
|
encodeQuery := func(query map[string]string) string {
|
||||||
|
values := url.Values{}
|
||||||
|
for k, v := range query {
|
||||||
|
values[k] = []string{v}
|
||||||
|
}
|
||||||
|
return values.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
urlWithQuery := func(baseURL string, query map[string]string) string {
|
||||||
|
urlToReturn := fmt.Sprintf("%s?%s", baseURL, encodeQuery(query))
|
||||||
|
_, err := url.Parse(urlToReturn)
|
||||||
|
require.NoError(t, err, "urlWithQuery helper was used to create an illegal URL")
|
||||||
|
return urlToReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
idps *oidctestutil.UpstreamIDPListerBuilder
|
||||||
|
decodedState *oidc.UpstreamStateParamData
|
||||||
|
formParams url.Values
|
||||||
|
reqURIQuery url.Values
|
||||||
|
|
||||||
|
wantStatus int
|
||||||
|
wantContentType string
|
||||||
|
wantBodyString string
|
||||||
|
wantErr string
|
||||||
|
|
||||||
|
// Assertion that the response should be a redirect to the login page with an error param.
|
||||||
|
wantRedirectToLoginPageError string
|
||||||
|
|
||||||
|
// Assertions for when an authcode should be returned, i.e. the request was authenticated by an
|
||||||
|
// upstream LDAP or AD provider.
|
||||||
|
wantRedirectLocationRegexp string // for loose matching
|
||||||
|
wantRedirectLocationString string // for exact matching instead
|
||||||
|
wantDownstreamRedirectURI string
|
||||||
|
wantDownstreamGrantedScopes []string
|
||||||
|
wantDownstreamIDTokenSubject string
|
||||||
|
wantDownstreamIDTokenUsername string
|
||||||
|
wantDownstreamIDTokenGroups []string
|
||||||
|
wantDownstreamRequestedScopes []string
|
||||||
|
wantDownstreamPKCEChallenge string
|
||||||
|
wantDownstreamPKCEChallengeMethod string
|
||||||
|
wantDownstreamNonce string
|
||||||
|
wantDownstreamCustomSessionData *psession.CustomSessionData
|
||||||
|
|
||||||
|
// Authorization requests for either a successful OIDC upstream or for an error with any upstream
|
||||||
|
// should never use Kube storage. There is only one exception to this rule, which is that certain
|
||||||
|
// OIDC validations are checked in fosite after the OAuth authcode (and sometimes the OIDC session)
|
||||||
|
// is stored, so it is possible with an LDAP upstream to store objects and then return an error to
|
||||||
|
// the client anyway (which makes the stored objects useless, but oh well).
|
||||||
|
wantUnnecessaryStoredRecords int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy LDAP login",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().
|
||||||
|
WithLDAP(&upstreamLDAPIdentityProvider). // should pick this one
|
||||||
|
WithActiveDirectory(&erroringUpstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
|
wantDownstreamRedirectURI: downstreamRedirectURI,
|
||||||
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy AD login",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().
|
||||||
|
WithLDAP(&erroringUpstreamLDAPIdentityProvider).
|
||||||
|
WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider), // should pick this one
|
||||||
|
decodedState: happyActiveDirectoryDecodedState,
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
|
wantDownstreamRedirectURI: downstreamRedirectURI,
|
||||||
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: expectedHappyActiveDirectoryUpstreamCustomSession,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy LDAP login when downstream redirect uri matches what is configured for client except for the port number",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["redirect_uri"] = []string{"http://127.0.0.1:4242/callback"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationRegexp: "http://127.0.0.1:4242/callback" + `\?code=([^&]+)&scope=openid&state=` + happyDownstreamState,
|
||||||
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
|
wantDownstreamRedirectURI: "http://127.0.0.1:4242/callback",
|
||||||
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy LDAP login when there are additional allowed downstream requested scopes",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["scope"] = []string{"openid offline_access pinniped:request-audience"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+pinniped%3Arequest-audience&state=` + happyDownstreamState,
|
||||||
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
|
wantDownstreamRequestedScopes: []string{"openid", "offline_access", "pinniped:request-audience"},
|
||||||
|
wantDownstreamRedirectURI: downstreamRedirectURI,
|
||||||
|
wantDownstreamGrantedScopes: []string{"openid", "offline_access", "pinniped:request-audience"},
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy LDAP when downstream OIDC validations are skipped because the openid scope was not requested",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["scope"] = []string{"email"}
|
||||||
|
// The following prompt value is illegal when openid is requested, but note that openid is not requested.
|
||||||
|
query["prompt"] = []string{"none login"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyDownstreamState, // no scopes granted
|
||||||
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
|
wantDownstreamRequestedScopes: []string{"email"}, // only email was requested
|
||||||
|
wantDownstreamRedirectURI: downstreamRedirectURI,
|
||||||
|
wantDownstreamGrantedScopes: []string{}, // no scopes granted
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad username LDAP login",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: url.Values{userParam: []string{"wrong!"}, passParam: []string{happyLDAPPassword}},
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectToLoginPageError: badUserPassErrParamValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad password LDAP login",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: url.Values{userParam: []string{happyLDAPUsername}, passParam: []string{"wrong!"}},
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectToLoginPageError: badUserPassErrParamValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blank username LDAP login",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: url.Values{userParam: []string{""}, passParam: []string{happyLDAPPassword}},
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectToLoginPageError: badUserPassErrParamValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blank password LDAP login",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: url.Values{userParam: []string{happyLDAPUsername}, passParam: []string{""}},
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectToLoginPageError: badUserPassErrParamValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "username and password sent as URI query params should be ignored since they are expected in form post body",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
reqURIQuery: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectToLoginPageError: badUserPassErrParamValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error during upstream LDAP authentication",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&erroringUpstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectToLoginPageError: internalErrParamValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "downstream redirect uri does not match what is configured for client",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["redirect_uri"] = []string{"http://127.0.0.1/wrong_callback"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "downstream client does not exist",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["client_id"] = []string{"wrong_client_id"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "downstream client is missing",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
delete(query, "client_id")
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "response type is unsupported",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["response_type"] = []string{"unsupported"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "response type is missing",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
delete(query, "response_type")
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PKCE code_challenge is missing",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
delete(query, "code_challenge")
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationString: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeErrorQuery),
|
||||||
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PKCE code_challenge_method is invalid",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["code_challenge_method"] = []string{"this-is-not-a-valid-pkce-alg"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationString: urlWithQuery(downstreamRedirectURI, fositeInvalidCodeChallengeErrorQuery),
|
||||||
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PKCE code_challenge_method is `plain`",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["code_challenge_method"] = []string{"plain"} // plain is not allowed
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationString: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PKCE code_challenge_method is missing",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
delete(query, "code_challenge_method")
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationString: urlWithQuery(downstreamRedirectURI, fositeMissingCodeChallengeMethodErrorQuery),
|
||||||
|
wantUnnecessaryStoredRecords: 2, // fosite already stored the authcode and oidc session before it noticed the error
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prompt param is not allowed to have none and another legal value at the same time",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["prompt"] = []string{"none login"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationString: urlWithQuery(downstreamRedirectURI, fositePromptHasNoneAndOtherValueErrorQuery),
|
||||||
|
wantUnnecessaryStoredRecords: 1, // fosite already stored the authcode before it noticed the error
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "downstream state does not have enough entropy",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["state"] = []string{"short"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "downstream scopes do not match what is configured for client",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["scope"] = []string{"openid offline_access pinniped:request-audience scope_not_allowed"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error using state downstream auth params",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no upstream providers are configured or provider cannot be found by name",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder(), // empty
|
||||||
|
decodedState: happyLDAPDecodedState,
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error finding upstream provider: provider not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "upstream provider cannot be found by name and type",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
decodedState: happyActiveDirectoryDecodedState, // correct upstream IDP name, but wrong upstream IDP type
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantErr: "error finding upstream provider: provider not found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tt := test
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
kubeClient := fake.NewSimpleClientset()
|
||||||
|
secretsClient := kubeClient.CoreV1().Secrets("some-namespace")
|
||||||
|
|
||||||
|
// Configure fosite the same way that the production code would.
|
||||||
|
// Inject this into our test subject at the last second so we get a fresh storage for every test.
|
||||||
|
timeoutsConfiguration := oidc.DefaultOIDCTimeoutsConfiguration()
|
||||||
|
kubeOauthStore := oidc.NewKubeStorage(secretsClient, timeoutsConfiguration)
|
||||||
|
hmacSecretFunc := func() []byte { return []byte("some secret - must have at least 32 bytes") }
|
||||||
|
require.GreaterOrEqual(t, len(hmacSecretFunc()), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
||||||
|
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
||||||
|
oauthHelper := oidc.FositeOauth2Helper(kubeOauthStore, downstreamIssuer, hmacSecretFunc, jwksProviderIsUnused, timeoutsConfiguration)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/ignored", strings.NewReader(tt.formParams.Encode()))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if tt.reqURIQuery != nil {
|
||||||
|
req.URL.RawQuery = tt.reqURIQuery.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
rsp := httptest.NewRecorder()
|
||||||
|
|
||||||
|
subject := NewPostHandler(downstreamIssuer, tt.idps.Build(), oauthHelper)
|
||||||
|
|
||||||
|
err := subject(rsp, req, happyEncodedUpstreamState, tt.decodedState)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
require.Empty(t, kubeClient.Actions())
|
||||||
|
return // the http response doesn't matter when the function returns an error, because the caller should handle the error
|
||||||
|
}
|
||||||
|
// Otherwise, expect no error.
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, tt.wantStatus, rsp.Code)
|
||||||
|
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), tt.wantContentType)
|
||||||
|
require.Equal(t, test.wantBodyString, rsp.Body.String())
|
||||||
|
|
||||||
|
actualLocation := rsp.Header().Get("Location")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case tt.wantRedirectLocationRegexp != "":
|
||||||
|
require.Len(t, rsp.Header().Values("Location"), 1)
|
||||||
|
oidctestutil.RequireAuthCodeRegexpMatch(
|
||||||
|
t,
|
||||||
|
actualLocation,
|
||||||
|
test.wantRedirectLocationRegexp,
|
||||||
|
kubeClient,
|
||||||
|
secretsClient,
|
||||||
|
kubeOauthStore,
|
||||||
|
test.wantDownstreamGrantedScopes,
|
||||||
|
test.wantDownstreamIDTokenSubject,
|
||||||
|
test.wantDownstreamIDTokenUsername,
|
||||||
|
test.wantDownstreamIDTokenGroups,
|
||||||
|
test.wantDownstreamRequestedScopes,
|
||||||
|
test.wantDownstreamPKCEChallenge,
|
||||||
|
test.wantDownstreamPKCEChallengeMethod,
|
||||||
|
test.wantDownstreamNonce,
|
||||||
|
downstreamClientID,
|
||||||
|
test.wantDownstreamRedirectURI,
|
||||||
|
test.wantDownstreamCustomSessionData,
|
||||||
|
)
|
||||||
|
case tt.wantRedirectToLoginPageError != "":
|
||||||
|
expectedLocation := downstreamIssuer + oidc.PinnipedLoginPath +
|
||||||
|
"?err=" + tt.wantRedirectToLoginPageError + "&state=" + happyEncodedUpstreamState
|
||||||
|
require.Equal(t, expectedLocation, actualLocation)
|
||||||
|
require.Len(t, kubeClient.Actions(), test.wantUnnecessaryStoredRecords)
|
||||||
|
case tt.wantRedirectLocationString != "":
|
||||||
|
require.Equal(t, tt.wantRedirectLocationString, actualLocation)
|
||||||
|
require.Len(t, kubeClient.Actions(), test.wantUnnecessaryStoredRecords)
|
||||||
|
default:
|
||||||
|
require.Failf(t, "test should have expected a redirect",
|
||||||
|
"actual location was %q", actualLocation)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -6,18 +6,25 @@ package oidc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/felixge/httpsnoop"
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/compose"
|
"github.com/ory/fosite/compose"
|
||||||
|
errorsx "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
"go.pinniped.dev/internal/oidc/csrftoken"
|
"go.pinniped.dev/internal/oidc/csrftoken"
|
||||||
"go.pinniped.dev/internal/oidc/jwks"
|
"go.pinniped.dev/internal/oidc/jwks"
|
||||||
"go.pinniped.dev/internal/oidc/provider"
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
"go.pinniped.dev/internal/oidc/provider/formposthtml"
|
"go.pinniped.dev/internal/oidc/provider/formposthtml"
|
||||||
|
"go.pinniped.dev/internal/plog"
|
||||||
|
"go.pinniped.dev/internal/psession"
|
||||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||||
"go.pinniped.dev/pkg/oidcclient/pkce"
|
"go.pinniped.dev/pkg/oidcclient/pkce"
|
||||||
)
|
)
|
||||||
@ -365,3 +372,106 @@ func validateCSRFValue(state *UpstreamStateParamData, csrfCookieValue csrftoken.
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindUpstreamIDPByNameAndType finds the requested IDP by name and type, or returns an error.
|
||||||
|
// Note that AD and LDAP IDPs both return the same interface type, but different ProviderTypes values.
|
||||||
|
func FindUpstreamIDPByNameAndType(
|
||||||
|
idpLister UpstreamIdentityProvidersLister,
|
||||||
|
upstreamName string,
|
||||||
|
upstreamType string,
|
||||||
|
) (
|
||||||
|
provider.UpstreamOIDCIdentityProviderI,
|
||||||
|
provider.UpstreamLDAPIdentityProviderI,
|
||||||
|
psession.ProviderType,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
switch upstreamType {
|
||||||
|
case string(v1alpha1.IDPTypeOIDC):
|
||||||
|
for _, p := range idpLister.GetOIDCIdentityProviders() {
|
||||||
|
if p.GetName() == upstreamName {
|
||||||
|
return p, nil, psession.ProviderTypeOIDC, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case string(v1alpha1.IDPTypeLDAP):
|
||||||
|
for _, p := range idpLister.GetLDAPIdentityProviders() {
|
||||||
|
if p.GetName() == upstreamName {
|
||||||
|
return nil, p, psession.ProviderTypeLDAP, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case string(v1alpha1.IDPTypeActiveDirectory):
|
||||||
|
for _, p := range idpLister.GetActiveDirectoryIdentityProviders() {
|
||||||
|
if p.GetName() == upstreamName {
|
||||||
|
return nil, p, psession.ProviderTypeActiveDirectory, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, "", errors.New("provider not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteAuthorizeError writes an authorization error as it should be returned by the authorization endpoint and other
|
||||||
|
// similar endpoints that are the end of the downstream authcode flow. Errors responses are written in the usual fosite style.
|
||||||
|
func WriteAuthorizeError(w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester, err error, isBrowserless bool) {
|
||||||
|
if plog.Enabled(plog.LevelTrace) {
|
||||||
|
// When trace level logging is enabled, include the stack trace in the log message.
|
||||||
|
keysAndValues := FositeErrorForLog(err)
|
||||||
|
errWithStack := errorsx.WithStack(err)
|
||||||
|
keysAndValues = append(keysAndValues, "errWithStack")
|
||||||
|
// klog always prints error values using %s, which does not include stack traces,
|
||||||
|
// so convert the error to a string which includes the stack trace here.
|
||||||
|
keysAndValues = append(keysAndValues, fmt.Sprintf("%+v", errWithStack))
|
||||||
|
plog.Trace("authorize response error", keysAndValues...)
|
||||||
|
} else {
|
||||||
|
plog.Info("authorize response error", FositeErrorForLog(err)...)
|
||||||
|
}
|
||||||
|
if isBrowserless {
|
||||||
|
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
||||||
|
}
|
||||||
|
// Return an error according to OIDC spec 3.1.2.6 (second paragraph).
|
||||||
|
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformAuthcodeRedirect successfully completes a downstream login by creating a session and
|
||||||
|
// writing the authcode redirect response as it should be returned by the authorization endpoint and other
|
||||||
|
// similar endpoints that are the end of the downstream authcode flow.
|
||||||
|
func PerformAuthcodeRedirect(
|
||||||
|
r *http.Request,
|
||||||
|
w http.ResponseWriter,
|
||||||
|
oauthHelper fosite.OAuth2Provider,
|
||||||
|
authorizeRequester fosite.AuthorizeRequester,
|
||||||
|
openIDSession *psession.PinnipedSession,
|
||||||
|
isBrowserless bool,
|
||||||
|
) {
|
||||||
|
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
||||||
|
if err != nil {
|
||||||
|
plog.WarningErr("error while generating and saving authcode", err)
|
||||||
|
WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, isBrowserless)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isBrowserless {
|
||||||
|
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
||||||
|
}
|
||||||
|
oauthHelper.WriteAuthorizeResponse(w, authorizeRequester, authorizeResponder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rewriteStatusSeeOtherToStatusFoundForBrowserless(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
// rewrite http.StatusSeeOther to http.StatusFound for backwards compatibility with old pinniped CLIs.
|
||||||
|
// we can drop this in a few releases once we feel enough time has passed for users to update.
|
||||||
|
//
|
||||||
|
// WriteAuthorizeResponse/WriteAuthorizeError calls used to result in http.StatusFound until
|
||||||
|
// https://github.com/ory/fosite/pull/636 changed it to http.StatusSeeOther to address
|
||||||
|
// https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11
|
||||||
|
// Safari has the bad behavior in the case of http.StatusFound and not just http.StatusTemporaryRedirect.
|
||||||
|
//
|
||||||
|
// in the browserless flows, the OAuth client is the pinniped CLI and it already has access to the user's
|
||||||
|
// password. Thus there is no security issue with using http.StatusFound vs. http.StatusSeeOther.
|
||||||
|
return httpsnoop.Wrap(w, httpsnoop.Hooks{
|
||||||
|
WriteHeader: func(delegate httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
|
||||||
|
return func(code int) {
|
||||||
|
if code == http.StatusSeeOther {
|
||||||
|
code = http.StatusFound
|
||||||
|
}
|
||||||
|
delegate(code)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -140,7 +140,7 @@ func (m *Manager) SetProviders(federationDomains ...*provider.FederationDomainIs
|
|||||||
upstreamStateEncoder,
|
upstreamStateEncoder,
|
||||||
csrfCookieEncoder,
|
csrfCookieEncoder,
|
||||||
login.NewGetHandler(m.upstreamIDPs),
|
login.NewGetHandler(m.upstreamIDPs),
|
||||||
login.NewPostHandler(m.upstreamIDPs, oauthHelperWithKubeStorage),
|
login.NewPostHandler(issuer, m.upstreamIDPs, oauthHelperWithKubeStorage),
|
||||||
)
|
)
|
||||||
|
|
||||||
plog.Debug("oidc provider manager added or updated issuer", "issuer", issuer)
|
plog.Debug("oidc provider manager added or updated issuer", "issuer", issuer)
|
||||||
|
Loading…
Reference in New Issue
Block a user