First unit test for auth endpoint's password grant and related refactor
This commit is contained in:
parent
69964fc788
commit
50085a505b
@ -5,6 +5,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -36,6 +37,16 @@ import (
|
|||||||
|
|
||||||
func TestAuthorizationEndpoint(t *testing.T) {
|
func TestAuthorizationEndpoint(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
|
passwordGrantUpstreamName = "some-password-granting-oidc-idp"
|
||||||
|
|
||||||
|
oidcUpstreamIssuer = "https://my-upstream-issuer.com"
|
||||||
|
oidcUpstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
||||||
|
oidcUpstreamSubjectQueryEscaped = "abc123-some+guid"
|
||||||
|
oidcUpstreamUsername = "test-oidc-pinniped-username"
|
||||||
|
oidcUpstreamPassword = "test-oidc-pinniped-password" //nolint: gosec
|
||||||
|
oidcUpstreamUsernameClaim = "the-user-claim"
|
||||||
|
oidcUpstreamGroupsClaim = "the-groups-claim"
|
||||||
|
|
||||||
downstreamIssuer = "https://my-downstream-issuer.com/some-path"
|
downstreamIssuer = "https://my-downstream-issuer.com/some-path"
|
||||||
downstreamRedirectURI = "http://127.0.0.1/callback"
|
downstreamRedirectURI = "http://127.0.0.1/callback"
|
||||||
downstreamRedirectURIWithDifferentPort = "http://127.0.0.1:42/callback"
|
downstreamRedirectURIWithDifferentPort = "http://127.0.0.1:42/callback"
|
||||||
@ -51,6 +62,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
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")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
oidcUpstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"}
|
||||||
|
|
||||||
fositeInvalidClientErrorBody = here.Doc(`
|
fositeInvalidClientErrorBody = here.Doc(`
|
||||||
{
|
{
|
||||||
"error": "invalid_client",
|
"error": "invalid_client",
|
||||||
@ -145,11 +158,31 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
upstreamAuthURL, err := url.Parse("https://some-upstream-idp:8443/auth")
|
upstreamAuthURL, err := url.Parse("https://some-upstream-idp:8443/auth")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
upstreamOIDCIdentityProvider := oidctestutil.TestUpstreamOIDCIdentityProvider{
|
upstreamOIDCIdentityProvider := oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||||
Name: "some-oidc-idp",
|
WithName("some-oidc-idp").
|
||||||
ClientID: "some-client-id",
|
WithClientID("some-client-id").
|
||||||
AuthorizationURL: *upstreamAuthURL,
|
WithAuthorizationURL(*upstreamAuthURL).
|
||||||
Scopes: []string{"scope1", "scope2"}, // the scopes to request when starting the upstream authorization flow
|
WithScopes([]string{"scope1", "scope2"}). // the scopes to request when starting the upstream authorization flow
|
||||||
|
WithAllowPasswordGrant(false).
|
||||||
|
WithPasswordGrantError(errors.New("should not have used password grant on this instance")).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
passwordGrantUpstreamOIDCIdentityProviderBuilder := func() *oidctestutil.TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
return oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||||
|
WithName(passwordGrantUpstreamName).
|
||||||
|
WithClientID("some-client-id").
|
||||||
|
WithAuthorizationURL(*upstreamAuthURL).
|
||||||
|
WithScopes([]string{"scope1", "scope2"}). // the scopes to request when starting the upstream authorization flow
|
||||||
|
WithAllowPasswordGrant(false).
|
||||||
|
WithUsernameClaim(oidcUpstreamUsernameClaim).
|
||||||
|
WithGroupsClaim(oidcUpstreamGroupsClaim).
|
||||||
|
WithIDTokenClaim("iss", oidcUpstreamIssuer).
|
||||||
|
WithIDTokenClaim("sub", oidcUpstreamSubject).
|
||||||
|
WithIDTokenClaim(oidcUpstreamUsernameClaim, oidcUpstreamUsername).
|
||||||
|
WithIDTokenClaim(oidcUpstreamGroupsClaim, oidcUpstreamGroupMembership).
|
||||||
|
WithIDTokenClaim("other-claim", "should be ignored").
|
||||||
|
WithAllowPasswordGrant(true).
|
||||||
|
WithUpstreamAuthcodeExchangeError(errors.New("should not have tried to exchange upstream authcode on this instance"))
|
||||||
}
|
}
|
||||||
|
|
||||||
happyLDAPUsername := "some-ldap-user"
|
happyLDAPUsername := "some-ldap-user"
|
||||||
@ -322,7 +355,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
|
|
||||||
idpLister provider.DynamicUpstreamIDPProvider
|
idps *oidctestutil.UpstreamIDPListerBuilder
|
||||||
generateCSRF func() (csrftoken.CSRFToken, error)
|
generateCSRF func() (csrftoken.CSRFToken, error)
|
||||||
generatePKCE func() (pkce.Code, error)
|
generatePKCE func() (pkce.Code, error)
|
||||||
generateNonce func() (nonce.Nonce, error)
|
generateNonce func() (nonce.Nonce, error)
|
||||||
@ -345,7 +378,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantLocationHeader string
|
wantLocationHeader string
|
||||||
wantUpstreamStateParamInLocationHeader bool
|
wantUpstreamStateParamInLocationHeader bool
|
||||||
|
|
||||||
// For when the request was authenticated by an upstream LDAP provider and an authcode is being returned.
|
// Assertions for when an authcode should be returned, i.e. the request was authenticated by an
|
||||||
|
// upstream LDAP provider or an upstream OIDC password grant flow.
|
||||||
wantRedirectLocationRegexp string
|
wantRedirectLocationRegexp string
|
||||||
wantDownstreamRedirectURI string
|
wantDownstreamRedirectURI string
|
||||||
wantDownstreamGrantedScopes []string
|
wantDownstreamGrantedScopes []string
|
||||||
@ -357,11 +391,12 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantDownstreamPKCEChallengeMethod string
|
wantDownstreamPKCEChallengeMethod string
|
||||||
wantDownstreamNonce string
|
wantDownstreamNonce string
|
||||||
wantUnnecessaryStoredRecords int
|
wantUnnecessaryStoredRecords int
|
||||||
|
wantPasswordGrantCall *expectedPasswordGrant
|
||||||
}
|
}
|
||||||
tests := []testCase{
|
tests := []testCase{
|
||||||
{
|
{
|
||||||
name: "OIDC upstream happy path using GET without a CSRF cookie",
|
name: "OIDC upstream browser flow happy path using GET without a CSRF cookie",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -376,9 +411,35 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantUpstreamStateParamInLocationHeader: true,
|
wantUpstreamStateParamInLocationHeader: true,
|
||||||
wantBodyStringWithLocationInHref: true,
|
wantBodyStringWithLocationInHref: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OIDC upstream password grant happy path using GET",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().Build()),
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: happyGetRequestPath,
|
||||||
|
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||||
|
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||||
|
wantPasswordGrantCall: &expectedPasswordGrant{
|
||||||
|
performedByUpstreamName: passwordGrantUpstreamName,
|
||||||
|
args: &oidctestutil.PasswordCredentialsGrantAndValidateTokensArgs{
|
||||||
|
Username: oidcUpstreamUsername,
|
||||||
|
Password: oidcUpstreamPassword,
|
||||||
|
}},
|
||||||
|
wantStatus: http.StatusFound,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
|
wantDownstreamRedirectURI: downstreamRedirectURI,
|
||||||
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "LDAP upstream happy path using GET",
|
name: "LDAP upstream happy path using GET",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -386,7 +447,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
wantBodyStringWithLocationInHref: false,
|
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
@ -399,7 +459,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OIDC upstream happy path using GET with a CSRF cookie",
|
name: "OIDC upstream happy path using GET with a CSRF cookie",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -416,7 +476,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OIDC upstream happy path using POST",
|
name: "OIDC upstream happy path using POST",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -435,7 +495,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "LDAP upstream happy path using POST",
|
name: "LDAP upstream happy path using POST",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: "application/x-www-form-urlencoded",
|
||||||
@ -445,7 +505,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
wantBodyStringWithLocationInHref: false,
|
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
@ -458,7 +517,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OIDC upstream happy path with prompt param login passed through to redirect uri",
|
name: "OIDC upstream happy path with prompt param login passed through to redirect uri",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -477,7 +536,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OIDC upstream with error while decoding CSRF cookie just generates a new cookie and succeeds as usual",
|
name: "OIDC upstream with error while decoding CSRF cookie just generates a new cookie and succeeds as usual",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -496,7 +555,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OIDC upstream happy path when downstream redirect uri matches what is configured for client except for the port number",
|
name: "OIDC upstream happy path when downstream redirect uri matches what is configured for client except for the port number",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -517,7 +576,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "LDAP upstream happy path when downstream redirect uri matches what is configured for client except for the port number",
|
name: "LDAP upstream happy path when downstream redirect uri matches what is configured for client except for the port number",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{
|
path: modifiedHappyGetRequestPath(map[string]string{
|
||||||
"redirect_uri": downstreamRedirectURIWithDifferentPort, // not the same port number that is registered for the client
|
"redirect_uri": downstreamRedirectURIWithDifferentPort, // not the same port number that is registered for the client
|
||||||
@ -527,7 +586,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid&state=` + happyState,
|
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid&state=` + happyState,
|
||||||
wantBodyStringWithLocationInHref: false,
|
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
@ -540,7 +598,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OIDC upstream happy path when downstream requested scopes include offline_access",
|
name: "OIDC upstream happy path when downstream requested scopes include offline_access",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -559,7 +617,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error during upstream LDAP authentication",
|
name: "error during upstream LDAP authentication",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&erroringUpstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&erroringUpstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -570,7 +628,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong upstream password for LDAP authentication",
|
name: "wrong upstream password for LDAP authentication",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -582,7 +640,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong upstream username for LDAP authentication",
|
name: "wrong upstream username for LDAP authentication",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
||||||
@ -594,7 +652,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing upstream username on request for LDAP authentication",
|
name: "missing upstream username on request for LDAP authentication",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
customUsernameHeader: nil, // do not send header
|
customUsernameHeader: nil, // do not send header
|
||||||
@ -606,7 +664,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing upstream password on request for LDAP authentication",
|
name: "missing upstream password on request for LDAP authentication",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -618,7 +676,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream redirect uri does not match what is configured for client when using OIDC upstream",
|
name: "downstream redirect uri does not match what is configured for client when using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -634,7 +692,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream redirect uri does not match what is configured for client when using LDAP upstream",
|
name: "downstream redirect uri does not match what is configured for client when using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{
|
path: modifiedHappyGetRequestPath(map[string]string{
|
||||||
"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",
|
||||||
@ -647,7 +705,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream client does not exist when using OIDC upstream",
|
name: "downstream client does not exist when using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -661,7 +719,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream client does not exist when using LDAP upstream",
|
name: "downstream client does not exist when using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
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,
|
||||||
@ -670,7 +728,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "response type is unsupported when using OIDC upstream",
|
name: "response type is unsupported when using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -685,7 +743,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "response type is unsupported when using LDAP upstream",
|
name: "response type is unsupported when using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
@ -695,7 +753,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream scopes do not match what is configured for client using OIDC upstream",
|
name: "downstream scopes do not match what is configured for client using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -710,7 +768,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream scopes do not match what is configured for client using LDAP upstream",
|
name: "downstream scopes do not match what is configured for client using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid tuna"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid tuna"}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -722,7 +780,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing response type in request using OIDC upstream",
|
name: "missing response type in request using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -737,7 +795,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing response type in request using LDAP upstream",
|
name: "missing response type in request using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
@ -747,7 +805,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing client id in request using OIDC upstream",
|
name: "missing client id in request using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -761,7 +819,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing client id in request using LDAP upstream",
|
name: "missing client id in request using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
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,
|
||||||
@ -770,7 +828,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing PKCE code_challenge in request using OIDC upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
name: "missing PKCE code_challenge in request using OIDC upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -785,7 +843,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing PKCE code_challenge in request using LDAP upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
name: "missing PKCE code_challenge in request using LDAP upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge": ""}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -798,7 +856,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value for PKCE code_challenge_method in request using OIDC upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
name: "invalid value for PKCE code_challenge_method in request using OIDC upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -813,7 +871,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value for PKCE code_challenge_method in request using LDAP upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
name: "invalid value for PKCE code_challenge_method in request using LDAP upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
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"}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -826,7 +884,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when PKCE code_challenge_method in request is `plain` using OIDC upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
name: "when PKCE code_challenge_method in request is `plain` using OIDC upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -841,7 +899,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when PKCE code_challenge_method in request is `plain` using LDAP upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
name: "when PKCE code_challenge_method in request is `plain` using LDAP upstream", // https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": "plain"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": "plain"}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -854,7 +912,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing PKCE code_challenge_method in request using OIDC upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
name: "missing PKCE code_challenge_method in request using OIDC upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -869,7 +927,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing PKCE code_challenge_method in request using LDAP upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
name: "missing PKCE code_challenge_method in request using LDAP upstream", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": ""}),
|
path: modifiedHappyGetRequestPath(map[string]string{"code_challenge_method": ""}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -884,7 +942,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
// This is just one of the many OIDC validations run by fosite. This test is to ensure that we are running
|
// This is just one of the many OIDC validations run by fosite. This test is to ensure that we are running
|
||||||
// through that part of the fosite library when using an OIDC upstream.
|
// through that part of the fosite library when using an OIDC upstream.
|
||||||
name: "prompt param is not allowed to have none and another legal value at the same time using OIDC upstream",
|
name: "prompt param is not allowed to have none and another legal value at the same time using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -901,7 +959,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
// This is just one of the many OIDC validations run by fosite. This test is to ensure that we are running
|
// This is just one of the many OIDC validations run by fosite. This test is to ensure that we are running
|
||||||
// through that part of the fosite library when using an LDAP upstream.
|
// through that part of the fosite library when using an LDAP upstream.
|
||||||
name: "prompt param is not allowed to have none and another legal value at the same time using LDAP upstream",
|
name: "prompt param is not allowed to have none and another legal value at the same time using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none login"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none login"}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -914,7 +972,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy path: downstream OIDC validations are skipped when the openid scope was not requested using OIDC upstream",
|
name: "happy path: downstream OIDC validations are skipped when the openid scope was not requested using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -934,7 +992,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy path: downstream OIDC validations are skipped when the openid scope was not requested using LDAP upstream",
|
name: "happy path: downstream OIDC validations are skipped when the openid scope was not requested using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
// The following prompt value is illegal when openid is requested, but note that openid is not requested.
|
// The following prompt value is illegal when openid is requested, but note that openid is not requested.
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none login", "scope": "email"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "none login", "scope": "email"}),
|
||||||
@ -943,7 +1001,6 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyState, // no scopes granted
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyState, // no scopes granted
|
||||||
wantBodyStringWithLocationInHref: false,
|
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
@ -956,7 +1013,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream state does not have enough entropy using OIDC upstream",
|
name: "downstream state does not have enough entropy using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -971,7 +1028,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "downstream state does not have enough entropy using LDAP upstream",
|
name: "downstream state does not have enough entropy using LDAP upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: modifiedHappyGetRequestPath(map[string]string{"state": "short"}),
|
path: modifiedHappyGetRequestPath(map[string]string{"state": "short"}),
|
||||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||||
@ -983,7 +1040,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error while encoding upstream state param using OIDC upstream",
|
name: "error while encoding upstream state param using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -997,7 +1054,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error while encoding CSRF cookie value for new cookie using OIDC upstream",
|
name: "error while encoding CSRF cookie value for new cookie using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -1011,7 +1068,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error while generating CSRF token using OIDC upstream",
|
name: "error while generating CSRF token using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: sadCSRFGenerator,
|
generateCSRF: sadCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -1025,7 +1082,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error while generating nonce using OIDC upstream",
|
name: "error while generating nonce using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: happyPKCEGenerator,
|
generatePKCE: happyPKCEGenerator,
|
||||||
generateNonce: sadNonceGenerator,
|
generateNonce: sadNonceGenerator,
|
||||||
@ -1039,7 +1096,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error while generating PKCE using OIDC upstream",
|
name: "error while generating PKCE using OIDC upstream",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
generateCSRF: happyCSRFGenerator,
|
generateCSRF: happyCSRFGenerator,
|
||||||
generatePKCE: sadPKCEGenerator,
|
generatePKCE: sadPKCEGenerator,
|
||||||
generateNonce: happyNonceGenerator,
|
generateNonce: happyNonceGenerator,
|
||||||
@ -1053,7 +1110,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no upstream providers are configured",
|
name: "no upstream providers are configured",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC().Build(), // empty
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(), // empty
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
@ -1062,7 +1119,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "too many upstream providers are configured: multiple OIDC",
|
name: "too many upstream providers are configured: multiple OIDC",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider, &upstreamOIDCIdentityProvider).Build(), // more than one not allowed
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider, upstreamOIDCIdentityProvider), // more than one not allowed
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
@ -1071,7 +1128,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "too many upstream providers are configured: multiple LDAP",
|
name: "too many upstream providers are configured: multiple LDAP",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider, &upstreamLDAPIdentityProvider).Build(), // more than one not allowed
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider, &upstreamLDAPIdentityProvider), // more than one not allowed
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
@ -1080,7 +1137,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "too many upstream providers are configured: both OIDC and LDAP",
|
name: "too many upstream providers are configured: both OIDC and LDAP",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).WithLDAP(&upstreamLDAPIdentityProvider).Build(), // more than one not allowed
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider).WithLDAP(&upstreamLDAPIdentityProvider), // more than one not allowed
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: happyGetRequestPath,
|
path: happyGetRequestPath,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
@ -1089,7 +1146,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PUT is a bad method",
|
name: "PUT is a bad method",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -1098,7 +1155,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PATCH is a bad method",
|
name: "PATCH is a bad method",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
method: http.MethodPatch,
|
method: http.MethodPatch,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -1107,7 +1164,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "DELETE is a bad method",
|
name: "DELETE is a bad method",
|
||||||
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider),
|
||||||
method: http.MethodDelete,
|
method: http.MethodDelete,
|
||||||
path: "/some/path",
|
path: "/some/path",
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -1117,7 +1174,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runOneTestCase := func(t *testing.T, test testCase, subject http.Handler, kubeOauthStore *oidc.KubeStorage, kubeClient *fake.Clientset, secretsClient v1.SecretInterface) {
|
runOneTestCase := func(t *testing.T, test testCase, subject http.Handler, kubeOauthStore *oidc.KubeStorage, kubeClient *fake.Clientset, secretsClient v1.SecretInterface) {
|
||||||
req := httptest.NewRequest(test.method, test.path, strings.NewReader(test.body))
|
reqContext := context.WithValue(context.Background(), struct{ name string }{name: "test"}, "request-context")
|
||||||
|
req := httptest.NewRequest(test.method, test.path, strings.NewReader(test.body)).WithContext(reqContext)
|
||||||
req.Header.Set("Content-Type", test.contentType)
|
req.Header.Set("Content-Type", test.contentType)
|
||||||
if test.csrfCookie != "" {
|
if test.csrfCookie != "" {
|
||||||
req.Header.Set("Cookie", test.csrfCookie)
|
req.Header.Set("Cookie", test.csrfCookie)
|
||||||
@ -1137,6 +1195,15 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), test.wantContentType)
|
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), test.wantContentType)
|
||||||
testutil.RequireSecurityHeaders(t, rsp)
|
testutil.RequireSecurityHeaders(t, rsp)
|
||||||
|
|
||||||
|
if test.wantPasswordGrantCall != nil {
|
||||||
|
test.wantPasswordGrantCall.args.Ctx = reqContext
|
||||||
|
test.idps.RequireExactlyOneCallToPasswordCredentialsGrantAndValidateTokens(t,
|
||||||
|
test.wantPasswordGrantCall.performedByUpstreamName, test.wantPasswordGrantCall.args,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
test.idps.RequireExactlyZeroCallsToPasswordCredentialsGrantAndValidateTokens(t)
|
||||||
|
}
|
||||||
|
|
||||||
actualLocation := rsp.Header().Get("Location")
|
actualLocation := rsp.Header().Get("Location")
|
||||||
switch {
|
switch {
|
||||||
case test.wantLocationHeader != "":
|
case test.wantLocationHeader != "":
|
||||||
@ -1212,7 +1279,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
oauthHelperWithRealStorage, kubeOauthStore := createOauthHelperWithRealStorage(secretsClient)
|
oauthHelperWithRealStorage, kubeOauthStore := createOauthHelperWithRealStorage(secretsClient)
|
||||||
subject := NewHandler(
|
subject := NewHandler(
|
||||||
downstreamIssuer,
|
downstreamIssuer,
|
||||||
test.idpLister,
|
test.idps.Build(),
|
||||||
oauthHelperWithNullStorage, oauthHelperWithRealStorage,
|
oauthHelperWithNullStorage, oauthHelperWithRealStorage,
|
||||||
test.generateCSRF, test.generatePKCE, test.generateNonce,
|
test.generateCSRF, test.generatePKCE, test.generateNonce,
|
||||||
test.stateEncoder, test.cookieEncoder,
|
test.stateEncoder, test.cookieEncoder,
|
||||||
@ -1223,14 +1290,16 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("allows upstream provider configuration to change between requests", func(t *testing.T) {
|
t.Run("allows upstream provider configuration to change between requests", func(t *testing.T) {
|
||||||
test := tests[0]
|
test := tests[0]
|
||||||
require.Equal(t, "OIDC upstream happy path using GET without a CSRF cookie", test.name) // re-use the happy path test case
|
// Double-check that we are re-using the happy path test case here as we intend.
|
||||||
|
require.Equal(t, "OIDC upstream browser flow happy path using GET without a CSRF cookie", test.name)
|
||||||
|
|
||||||
kubeClient := fake.NewSimpleClientset()
|
kubeClient := fake.NewSimpleClientset()
|
||||||
secretsClient := kubeClient.CoreV1().Secrets("some-namespace")
|
secretsClient := kubeClient.CoreV1().Secrets("some-namespace")
|
||||||
oauthHelperWithRealStorage, kubeOauthStore := createOauthHelperWithRealStorage(secretsClient)
|
oauthHelperWithRealStorage, kubeOauthStore := createOauthHelperWithRealStorage(secretsClient)
|
||||||
|
idpLister := test.idps.Build()
|
||||||
subject := NewHandler(
|
subject := NewHandler(
|
||||||
downstreamIssuer,
|
downstreamIssuer,
|
||||||
test.idpLister,
|
idpLister,
|
||||||
oauthHelperWithNullStorage, oauthHelperWithRealStorage,
|
oauthHelperWithNullStorage, oauthHelperWithRealStorage,
|
||||||
test.generateCSRF, test.generatePKCE, test.generateNonce,
|
test.generateCSRF, test.generatePKCE, test.generateNonce,
|
||||||
test.stateEncoder, test.cookieEncoder,
|
test.stateEncoder, test.cookieEncoder,
|
||||||
@ -1238,23 +1307,25 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
runOneTestCase(t, test, subject, kubeOauthStore, kubeClient, secretsClient)
|
runOneTestCase(t, test, subject, kubeOauthStore, kubeClient, secretsClient)
|
||||||
|
|
||||||
// Call the setter to change the upstream IDP settings.
|
// Call the idpLister's setter to change the upstream IDP settings.
|
||||||
newProviderSettings := oidctestutil.TestUpstreamOIDCIdentityProvider{
|
newProviderSettings := oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||||
Name: "some-other-idp",
|
WithName("some-other-new-idp-name").
|
||||||
ClientID: "some-other-client-id",
|
WithClientID("some-other-new-client-id").
|
||||||
AuthorizationURL: *upstreamAuthURL,
|
WithAuthorizationURL(*upstreamAuthURL).
|
||||||
Scopes: []string{"other-scope1", "other-scope2"},
|
WithScopes([]string{"some-other-new-scope1", "some-other-new-scope2"}).
|
||||||
}
|
Build()
|
||||||
test.idpLister.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{provider.UpstreamOIDCIdentityProviderI(&newProviderSettings)})
|
idpLister.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{provider.UpstreamOIDCIdentityProviderI(newProviderSettings)})
|
||||||
|
|
||||||
// Update the expectations of the test case to match the new upstream IDP settings.
|
// Update the expectations of the test case to match the new upstream IDP settings.
|
||||||
test.wantLocationHeader = urlWithQuery(upstreamAuthURL.String(),
|
test.wantLocationHeader = urlWithQuery(upstreamAuthURL.String(),
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"access_type": "offline",
|
"access_type": "offline",
|
||||||
"scope": "other-scope1 other-scope2",
|
"scope": "some-other-new-scope1 some-other-new-scope2", // updated expectation
|
||||||
"client_id": "some-other-client-id",
|
"client_id": "some-other-new-client-id", // updated expectation
|
||||||
"state": expectedUpstreamStateParam(nil, "", newProviderSettings.Name),
|
"state": expectedUpstreamStateParam(
|
||||||
|
nil, "", "some-other-new-idp-name",
|
||||||
|
), // updated expectation
|
||||||
"nonce": happyNonce,
|
"nonce": happyNonce,
|
||||||
"code_challenge": expectedUpstreamCodeChallenge,
|
"code_challenge": expectedUpstreamCodeChallenge,
|
||||||
"code_challenge_method": downstreamPKCEChallengeMethod,
|
"code_challenge_method": downstreamPKCEChallengeMethod,
|
||||||
@ -1282,6 +1353,11 @@ func (*errorReturningEncoder) Encode(_ string, _ interface{}) (string, error) {
|
|||||||
return "", fmt.Errorf("some encoding error")
|
return "", fmt.Errorf("some encoding error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type expectedPasswordGrant struct {
|
||||||
|
performedByUpstreamName string
|
||||||
|
args *oidctestutil.PasswordCredentialsGrantAndValidateTokensArgs
|
||||||
|
}
|
||||||
|
|
||||||
func requireEqualDecodedStateParams(t *testing.T, actualURL string, expectedURL string, stateParamDecoder oidc.Codec) {
|
func requireEqualDecodedStateParams(t *testing.T, actualURL string, expectedURL string, stateParamDecoder oidc.Codec) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
actualLocationURL, err := url.Parse(actualURL)
|
actualLocationURL, err := url.Parse(actualURL)
|
||||||
|
@ -21,20 +21,19 @@ import (
|
|||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
"go.pinniped.dev/internal/testutil/oidctestutil"
|
"go.pinniped.dev/internal/testutil/oidctestutil"
|
||||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
|
||||||
oidcpkce "go.pinniped.dev/pkg/oidcclient/pkce"
|
oidcpkce "go.pinniped.dev/pkg/oidcclient/pkce"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
happyUpstreamIDPName = "upstream-idp-name"
|
happyUpstreamIDPName = "upstream-idp-name"
|
||||||
|
|
||||||
upstreamIssuer = "https://my-upstream-issuer.com"
|
oidcUpstreamIssuer = "https://my-upstream-issuer.com"
|
||||||
upstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
oidcUpstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
||||||
queryEscapedUpstreamSubject = "abc123-some+guid"
|
oidcUpstreamSubjectQueryEscaped = "abc123-some+guid"
|
||||||
upstreamUsername = "test-pinniped-username"
|
oidcUpstreamUsername = "test-pinniped-username"
|
||||||
|
|
||||||
upstreamUsernameClaim = "the-user-claim"
|
oidcUpstreamUsernameClaim = "the-user-claim"
|
||||||
upstreamGroupsClaim = "the-groups-claim"
|
oidcUpstreamGroupsClaim = "the-groups-claim"
|
||||||
|
|
||||||
happyUpstreamAuthcode = "upstream-auth-code"
|
happyUpstreamAuthcode = "upstream-auth-code"
|
||||||
happyUpstreamRedirectURI = "https://example.com/callback"
|
happyUpstreamRedirectURI = "https://example.com/callback"
|
||||||
@ -56,7 +55,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
upstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"}
|
oidcUpstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"}
|
||||||
happyDownstreamScopesRequested = []string{"openid"}
|
happyDownstreamScopesRequested = []string{"openid"}
|
||||||
happyDownstreamScopesGranted = []string{"openid"}
|
happyDownstreamScopesGranted = []string{"openid"}
|
||||||
|
|
||||||
@ -113,7 +112,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
||||||
idp oidctestutil.TestUpstreamOIDCIdentityProvider
|
idps *oidctestutil.UpstreamIDPListerBuilder
|
||||||
method string
|
method string
|
||||||
path string
|
path string
|
||||||
csrfCookie string
|
csrfCookie string
|
||||||
@ -132,11 +131,11 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantDownstreamPKCEChallenge string
|
wantDownstreamPKCEChallenge string
|
||||||
wantDownstreamPKCEChallengeMethod string
|
wantDownstreamPKCEChallengeMethod string
|
||||||
|
|
||||||
wantExchangeAndValidateTokensCall *oidctestutil.ExchangeAuthcodeAndValidateTokenArgs
|
wantAuthcodeExchangeCall *expectedAuthcodeExchange
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "GET with good state and cookie and successful upstream token exchange with response_mode=form_post returns 200 with HTML+JS form",
|
name: "GET with good state and cookie and successful upstream token exchange with response_mode=form_post returns 200 with HTML+JS form",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(
|
path: newRequestPath().WithState(
|
||||||
happyUpstreamStateParam().WithAuthorizeRequestParams(
|
happyUpstreamStateParam().WithAuthorizeRequestParams(
|
||||||
@ -150,204 +149,254 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusOK,
|
wantStatus: http.StatusOK,
|
||||||
wantContentType: "text/html;charset=UTF-8",
|
wantContentType: "text/html;charset=UTF-8",
|
||||||
wantBodyFormResponseRegexp: `<code id="manual-auth-code">(.+)</code>`,
|
wantBodyFormResponseRegexp: `<code id="manual-auth-code">(.+)</code>`,
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GET with good state and cookie and successful upstream token exchange returns 302 to downstream client callback with its state and code",
|
name: "GET with good state and cookie and successful upstream token exchange returns 302 to downstream client callback with its state and code",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP provides no username or group claim configuration, so we use default username claim and skip groups",
|
name: "upstream IDP provides no username or group claim configuration, so we use default username claim and skip groups",
|
||||||
idp: happyUpstream().WithoutUsernameClaim().WithoutGroupsClaim().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithoutUsernameClaim().WithoutGroupsClaim().Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenUsername: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenGroups: []string{},
|
wantDownstreamIDTokenGroups: []string{},
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is missing",
|
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is missing",
|
||||||
idp: happyUpstream().WithUsernameClaim("email").
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
WithIDTokenClaim("email", "joe@whitehouse.gov").Build(),
|
happyUpstream().WithUsernameClaim("email").WithIDTokenClaim("email", "joe@whitehouse.gov").Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with true value",
|
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with true value",
|
||||||
idp: happyUpstream().WithUsernameClaim("email").
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithUsernameClaim("email").
|
||||||
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
||||||
WithIDTokenClaim("email_verified", true).Build(),
|
WithIDTokenClaim("email_verified", true).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP configures username claim as anything other than special claim `email` and `email_verified` upstream claim is present with false value",
|
name: "upstream IDP configures username claim as anything other than special claim `email` and `email_verified` upstream claim is present with false value",
|
||||||
idp: happyUpstream().WithUsernameClaim("some-claim").
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithUsernameClaim("some-claim").
|
||||||
WithIDTokenClaim("some-claim", "joe").
|
WithIDTokenClaim("some-claim", "joe").
|
||||||
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
||||||
WithIDTokenClaim("email_verified", false).Build(),
|
WithIDTokenClaim("email_verified", false).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound, // succeed despite `email_verified=false` because we're not using the email claim for anything
|
wantStatus: http.StatusFound, // succeed despite `email_verified=false` because we're not using the email claim for anything
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: "joe",
|
wantDownstreamIDTokenUsername: "joe",
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with illegal value",
|
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with illegal value",
|
||||||
idp: happyUpstream().WithUsernameClaim("email").
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().WithUsernameClaim("email").
|
||||||
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
||||||
WithIDTokenClaim("email_verified", "supposed to be boolean").Build(),
|
WithIDTokenClaim("email_verified", "supposed to be boolean").Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: email_verified claim in upstream ID token has invalid format\n",
|
wantBody: "Unprocessable Entity: email_verified claim in upstream ID token has invalid format\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with false value",
|
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with false value",
|
||||||
idp: happyUpstream().WithUsernameClaim("email").
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithUsernameClaim("email").
|
||||||
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
WithIDTokenClaim("email", "joe@whitehouse.gov").
|
||||||
WithIDTokenClaim("email_verified", false).Build(),
|
WithIDTokenClaim("email_verified", false).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: email_verified claim in upstream ID token has false value\n",
|
wantBody: "Unprocessable Entity: email_verified claim in upstream ID token has false value\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP provides username claim configuration as `sub`, so the downstream token subject should be exactly what they asked for",
|
name: "upstream IDP provides username claim configuration as `sub`, so the downstream token subject should be exactly what they asked for",
|
||||||
idp: happyUpstream().WithUsernameClaim("sub").Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithUsernameClaim("sub").Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamSubject,
|
wantDownstreamIDTokenUsername: oidcUpstreamSubject,
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP's configured groups claim in the ID token has a non-array value",
|
name: "upstream IDP's configured groups claim in the ID token has a non-array value",
|
||||||
idp: happyUpstream().WithIDTokenClaim(upstreamGroupsClaim, "notAnArrayGroup1 notAnArrayGroup2").Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim(oidcUpstreamGroupsClaim, "notAnArrayGroup1 notAnArrayGroup2").Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: []string{"notAnArrayGroup1 notAnArrayGroup2"},
|
wantDownstreamIDTokenGroups: []string{"notAnArrayGroup1 notAnArrayGroup2"},
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream IDP's configured groups claim in the ID token is a slice of interfaces",
|
name: "upstream IDP's configured groups claim in the ID token is a slice of interfaces",
|
||||||
idp: happyUpstream().WithIDTokenClaim(upstreamGroupsClaim, []interface{}{"group1", "group2"}).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim(oidcUpstreamGroupsClaim, []interface{}{"group1", "group2"}).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: []string{"group1", "group2"},
|
wantDownstreamIDTokenGroups: []string{"group1", "group2"},
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Pre-upstream-exchange verification
|
// Pre-upstream-exchange verification
|
||||||
{
|
{
|
||||||
name: "PUT method is invalid",
|
name: "PUT method is invalid",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
path: newRequestPath().String(),
|
path: newRequestPath().String(),
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -356,6 +405,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "POST method is invalid",
|
name: "POST method is invalid",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
path: newRequestPath().String(),
|
path: newRequestPath().String(),
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -364,6 +414,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PATCH method is invalid",
|
name: "PATCH method is invalid",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodPatch,
|
method: http.MethodPatch,
|
||||||
path: newRequestPath().String(),
|
path: newRequestPath().String(),
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -372,6 +423,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "DELETE method is invalid",
|
name: "DELETE method is invalid",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodDelete,
|
method: http.MethodDelete,
|
||||||
path: newRequestPath().String(),
|
path: newRequestPath().String(),
|
||||||
wantStatus: http.StatusMethodNotAllowed,
|
wantStatus: http.StatusMethodNotAllowed,
|
||||||
@ -380,6 +432,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "code param was not included on request",
|
name: "code param was not included on request",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).WithoutCode().String(),
|
path: newRequestPath().WithState(happyState).WithoutCode().String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
@ -389,6 +442,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state param was not included on request",
|
name: "state param was not included on request",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithoutState().String(),
|
path: newRequestPath().WithoutState().String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
@ -398,7 +452,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state param was not signed correctly, has expired, or otherwise cannot be decoded for any reason",
|
name: "state param was not signed correctly, has expired, or otherwise cannot be decoded for any reason",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState("this-will-not-decode").String(),
|
path: newRequestPath().WithState("this-will-not-decode").String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
@ -410,7 +464,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
// This shouldn't happen in practice because the authorize endpoint should have already run the same
|
// This shouldn't happen in practice because the authorize endpoint should have already run the same
|
||||||
// validations, but we would like to test the error handling in this endpoint anyway.
|
// validations, but we would like to test the error handling in this endpoint anyway.
|
||||||
name: "state param contains authorization request params which fail validation",
|
name: "state param contains authorization request params which fail validation",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(
|
path: newRequestPath().WithState(
|
||||||
happyUpstreamStateParam().
|
happyUpstreamStateParam().
|
||||||
@ -418,14 +472,18 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
Build(t, happyStateCodec),
|
Build(t, happyStateCodec),
|
||||||
).String(),
|
).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
|
|
||||||
wantStatus: http.StatusInternalServerError,
|
wantStatus: http.StatusInternalServerError,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Internal Server Error: error while generating and saving authcode\n",
|
wantBody: "Internal Server Error: error while generating and saving authcode\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state's internal version does not match what we want",
|
name: "state's internal version does not match what we want",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyUpstreamStateParam().WithStateVersion("wrong-state-version").Build(t, happyStateCodec)).String(),
|
path: newRequestPath().WithState(happyUpstreamStateParam().WithStateVersion("wrong-state-version").Build(t, happyStateCodec)).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
@ -435,7 +493,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state's downstream auth params element is invalid",
|
name: "state's downstream auth params element is invalid",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyUpstreamStateParam().
|
path: newRequestPath().WithState(happyUpstreamStateParam().
|
||||||
WithAuthorizeRequestParams("the following is an invalid url encoding token, and therefore this is an invalid param: %z").
|
WithAuthorizeRequestParams("the following is an invalid url encoding token, and therefore this is an invalid param: %z").
|
||||||
@ -447,7 +505,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state's downstream auth params are missing required value (e.g., client_id)",
|
name: "state's downstream auth params are missing required value (e.g., client_id)",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(
|
path: newRequestPath().WithState(
|
||||||
happyUpstreamStateParam().
|
happyUpstreamStateParam().
|
||||||
@ -461,7 +519,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state's downstream auth params does not contain openid scope",
|
name: "state's downstream auth params does not contain openid scope",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().
|
path: newRequestPath().
|
||||||
WithState(
|
WithState(
|
||||||
@ -472,18 +530,21 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyDownstreamState,
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyDownstreamState,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamRequestedScopes: []string{"profile", "email"},
|
wantDownstreamRequestedScopes: []string{"profile", "email"},
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "state's downstream auth params also included offline_access scope",
|
name: "state's downstream auth params also included offline_access scope",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().
|
path: newRequestPath().
|
||||||
WithState(
|
WithState(
|
||||||
@ -494,19 +555,22 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
|
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
|
||||||
wantDownstreamGrantedScopes: []string{"openid", "offline_access"},
|
wantDownstreamGrantedScopes: []string{"openid", "offline_access"},
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "the OIDCIdentityProvider CRD has been deleted",
|
name: "the OIDCIdentityProvider CRD has been deleted",
|
||||||
idp: otherUpstreamOIDCIdentityProvider,
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&otherUpstreamOIDCIdentityProvider),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
@ -516,7 +580,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "the CSRF cookie does not exist on request",
|
name: "the CSRF cookie does not exist on request",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
wantStatus: http.StatusForbidden,
|
wantStatus: http.StatusForbidden,
|
||||||
@ -525,7 +589,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cookie was not signed correctly, has expired, or otherwise cannot be decoded for any reason",
|
name: "cookie was not signed correctly, has expired, or otherwise cannot be decoded for any reason",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: "__Host-pinniped-csrf=this-value-was-not-signed-by-pinniped",
|
csrfCookie: "__Host-pinniped-csrf=this-value-was-not-signed-by-pinniped",
|
||||||
@ -535,7 +599,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cookie csrf value does not match state csrf value",
|
name: "cookie csrf value does not match state csrf value",
|
||||||
idp: happyUpstream().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyUpstreamStateParam().WithCSRF("wrong-csrf-value").Build(t, happyStateCodec)).String(),
|
path: newRequestPath().WithState(happyUpstreamStateParam().WithCSRF("wrong-csrf-value").Build(t, happyStateCodec)).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
@ -547,110 +611,155 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
// Upstream exchange
|
// Upstream exchange
|
||||||
{
|
{
|
||||||
name: "upstream auth code exchange fails",
|
name: "upstream auth code exchange fails",
|
||||||
idp: happyUpstream().WithoutUpstreamAuthcodeExchangeError(errors.New("some error")).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithUpstreamAuthcodeExchangeError(errors.New("some error")).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusBadGateway,
|
wantStatus: http.StatusBadGateway,
|
||||||
wantBody: "Bad Gateway: error exchanging and validating upstream tokens\n",
|
wantBody: "Bad Gateway: error exchanging and validating upstream tokens\n",
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token does not contain requested username claim",
|
name: "upstream ID token does not contain requested username claim",
|
||||||
idp: happyUpstream().WithoutIDTokenClaim(upstreamUsernameClaim).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithoutIDTokenClaim(oidcUpstreamUsernameClaim).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantBody: "Unprocessable Entity: no username claim in upstream ID token\n",
|
wantBody: "Unprocessable Entity: no username claim in upstream ID token\n",
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token does not contain requested groups claim",
|
name: "upstream ID token does not contain requested groups claim",
|
||||||
idp: happyUpstream().WithoutIDTokenClaim(upstreamGroupsClaim).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithoutIDTokenClaim(oidcUpstreamGroupsClaim).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
wantDownstreamIDTokenGroups: []string{},
|
wantDownstreamIDTokenGroups: []string{},
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token contains username claim with weird format",
|
name: "upstream ID token contains username claim with weird format",
|
||||||
idp: happyUpstream().WithIDTokenClaim(upstreamUsernameClaim, 42).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim(oidcUpstreamUsernameClaim, 42).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: username claim in upstream ID token has invalid format\n",
|
wantBody: "Unprocessable Entity: username claim in upstream ID token has invalid format\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token does not contain iss claim when using default username claim config",
|
name: "upstream ID token does not contain iss claim when using default username claim config",
|
||||||
idp: happyUpstream().WithIDTokenClaim("iss", "").WithoutUsernameClaim().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim("iss", "").WithoutUsernameClaim().Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: issuer claim in upstream ID token missing\n",
|
wantBody: "Unprocessable Entity: issuer claim in upstream ID token missing\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token has an non-string iss claim when using default username claim config",
|
name: "upstream ID token has an non-string iss claim when using default username claim config",
|
||||||
idp: happyUpstream().WithIDTokenClaim("iss", 42).WithoutUsernameClaim().Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim("iss", 42).WithoutUsernameClaim().Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: issuer claim in upstream ID token has invalid format\n",
|
wantBody: "Unprocessable Entity: issuer claim in upstream ID token has invalid format\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token contains groups claim with weird format",
|
name: "upstream ID token contains groups claim with weird format",
|
||||||
idp: happyUpstream().WithIDTokenClaim(upstreamGroupsClaim, 42).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim(oidcUpstreamGroupsClaim, 42).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: groups claim in upstream ID token has invalid format\n",
|
wantBody: "Unprocessable Entity: groups claim in upstream ID token has invalid format\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token contains groups claim where one element is invalid",
|
name: "upstream ID token contains groups claim where one element is invalid",
|
||||||
idp: happyUpstream().WithIDTokenClaim(upstreamGroupsClaim, []interface{}{"foo", 7}).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim(oidcUpstreamGroupsClaim, []interface{}{"foo", 7}).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: groups claim in upstream ID token has invalid format\n",
|
wantBody: "Unprocessable Entity: groups claim in upstream ID token has invalid format\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ID token contains groups claim with invalid null type",
|
name: "upstream ID token contains groups claim with invalid null type",
|
||||||
idp: happyUpstream().WithIDTokenClaim(upstreamGroupsClaim, nil).Build(),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||||
|
happyUpstream().WithIDTokenClaim(oidcUpstreamGroupsClaim, nil).Build(),
|
||||||
|
),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
path: newRequestPath().WithState(happyState).String(),
|
path: newRequestPath().WithState(happyState).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusUnprocessableEntity,
|
wantStatus: http.StatusUnprocessableEntity,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBody: "Unprocessable Entity: groups claim in upstream ID token has invalid format\n",
|
wantBody: "Unprocessable Entity: groups claim in upstream ID token has invalid format\n",
|
||||||
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -669,9 +778,9 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
||||||
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecretFunc, jwksProviderIsUnused, timeoutsConfiguration)
|
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecretFunc, jwksProviderIsUnused, timeoutsConfiguration)
|
||||||
|
|
||||||
idpLister := oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&test.idp).Build()
|
subject := NewHandler(test.idps.Build(), oauthHelper, happyStateCodec, happyCookieCodec, happyUpstreamRedirectURI)
|
||||||
subject := NewHandler(idpLister, oauthHelper, happyStateCodec, happyCookieCodec, happyUpstreamRedirectURI)
|
reqContext := context.WithValue(context.Background(), struct{ name string }{name: "test"}, "request-context")
|
||||||
req := httptest.NewRequest(test.method, test.path, nil)
|
req := httptest.NewRequest(test.method, test.path, nil).WithContext(reqContext)
|
||||||
if test.csrfCookie != "" {
|
if test.csrfCookie != "" {
|
||||||
req.Header.Set("Cookie", test.csrfCookie)
|
req.Header.Set("Cookie", test.csrfCookie)
|
||||||
}
|
}
|
||||||
@ -682,12 +791,13 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
testutil.RequireSecurityHeaders(t, rsp)
|
testutil.RequireSecurityHeaders(t, rsp)
|
||||||
|
|
||||||
if test.wantExchangeAndValidateTokensCall != nil {
|
if test.wantAuthcodeExchangeCall != nil {
|
||||||
require.Equal(t, 1, test.idp.ExchangeAuthcodeAndValidateTokensCallCount())
|
test.wantAuthcodeExchangeCall.args.Ctx = reqContext
|
||||||
test.wantExchangeAndValidateTokensCall.Ctx = req.Context()
|
test.idps.RequireExactlyOneCallToExchangeAuthcodeAndValidateTokens(t,
|
||||||
require.Equal(t, test.wantExchangeAndValidateTokensCall, test.idp.ExchangeAuthcodeAndValidateTokensArgs(0))
|
test.wantAuthcodeExchangeCall.performedByUpstreamName, test.wantAuthcodeExchangeCall.args,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, 0, test.idp.ExchangeAuthcodeAndValidateTokensCallCount())
|
test.idps.RequireExactlyZeroCallsToExchangeAuthcodeAndValidateTokens(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, test.wantStatus, rsp.Code)
|
require.Equal(t, test.wantStatus, rsp.Code)
|
||||||
@ -749,6 +859,11 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type expectedAuthcodeExchange struct {
|
||||||
|
performedByUpstreamName string
|
||||||
|
args *oidctestutil.ExchangeAuthcodeAndValidateTokenArgs
|
||||||
|
}
|
||||||
|
|
||||||
type requestPath struct {
|
type requestPath struct {
|
||||||
code, state *string
|
code, state *string
|
||||||
}
|
}
|
||||||
@ -838,70 +953,20 @@ func (b *upstreamStateParamBuilder) WithStateVersion(version string) *upstreamSt
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
type upstreamOIDCIdentityProviderBuilder struct {
|
func happyUpstream() *oidctestutil.TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
idToken map[string]interface{}
|
return oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||||
usernameClaim, groupsClaim string
|
WithName(happyUpstreamIDPName).
|
||||||
authcodeExchangeErr error
|
WithClientID("some-client-id").
|
||||||
}
|
WithScopes([]string{"scope1", "scope2"}).
|
||||||
|
WithUsernameClaim(oidcUpstreamUsernameClaim).
|
||||||
func happyUpstream() *upstreamOIDCIdentityProviderBuilder {
|
WithGroupsClaim(oidcUpstreamGroupsClaim).
|
||||||
return &upstreamOIDCIdentityProviderBuilder{
|
WithIDTokenClaim("iss", oidcUpstreamIssuer).
|
||||||
usernameClaim: upstreamUsernameClaim,
|
WithIDTokenClaim("sub", oidcUpstreamSubject).
|
||||||
groupsClaim: upstreamGroupsClaim,
|
WithIDTokenClaim(oidcUpstreamUsernameClaim, oidcUpstreamUsername).
|
||||||
idToken: map[string]interface{}{
|
WithIDTokenClaim(oidcUpstreamGroupsClaim, oidcUpstreamGroupMembership).
|
||||||
"iss": upstreamIssuer,
|
WithIDTokenClaim("other-claim", "should be ignored").
|
||||||
"sub": upstreamSubject,
|
WithAllowPasswordGrant(false).
|
||||||
upstreamUsernameClaim: upstreamUsername,
|
WithPasswordGrantError(errors.New("the callback endpoint should not use password grants"))
|
||||||
upstreamGroupsClaim: upstreamGroupMembership,
|
|
||||||
"other-claim": "should be ignored",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) WithUsernameClaim(value string) *upstreamOIDCIdentityProviderBuilder {
|
|
||||||
u.usernameClaim = value
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) WithoutUsernameClaim() *upstreamOIDCIdentityProviderBuilder {
|
|
||||||
u.usernameClaim = ""
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) WithoutGroupsClaim() *upstreamOIDCIdentityProviderBuilder {
|
|
||||||
u.groupsClaim = ""
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) WithIDTokenClaim(name string, value interface{}) *upstreamOIDCIdentityProviderBuilder {
|
|
||||||
u.idToken[name] = value
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) WithoutIDTokenClaim(claim string) *upstreamOIDCIdentityProviderBuilder {
|
|
||||||
delete(u.idToken, claim)
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) WithoutUpstreamAuthcodeExchangeError(err error) *upstreamOIDCIdentityProviderBuilder {
|
|
||||||
u.authcodeExchangeErr = err
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *upstreamOIDCIdentityProviderBuilder) Build() oidctestutil.TestUpstreamOIDCIdentityProvider {
|
|
||||||
return oidctestutil.TestUpstreamOIDCIdentityProvider{
|
|
||||||
Name: happyUpstreamIDPName,
|
|
||||||
ClientID: "some-client-id",
|
|
||||||
UsernameClaim: u.usernameClaim,
|
|
||||||
GroupsClaim: u.groupsClaim,
|
|
||||||
Scopes: []string{"scope1", "scope2"},
|
|
||||||
ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier oidcpkce.Code, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error) {
|
|
||||||
if u.authcodeExchangeErr != nil {
|
|
||||||
return nil, u.authcodeExchangeErr
|
|
||||||
}
|
|
||||||
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}}, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func shallowCopyAndModifyQuery(query url.Values, modifications map[string]string) url.Values {
|
func shallowCopyAndModifyQuery(query url.Values, modifications map[string]string) url.Values {
|
||||||
|
@ -49,6 +49,14 @@ type ExchangeAuthcodeAndValidateTokenArgs struct {
|
|||||||
RedirectURI string
|
RedirectURI string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PasswordCredentialsGrantAndValidateTokensArgs is used to spy on calls to
|
||||||
|
// TestUpstreamOIDCIdentityProvider.PasswordCredentialsGrantAndValidateTokensFunc().
|
||||||
|
type PasswordCredentialsGrantAndValidateTokensArgs struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
type TestUpstreamLDAPIdentityProvider struct {
|
type TestUpstreamLDAPIdentityProvider struct {
|
||||||
Name string
|
Name string
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
@ -77,6 +85,7 @@ type TestUpstreamOIDCIdentityProvider struct {
|
|||||||
GroupsClaim string
|
GroupsClaim string
|
||||||
Scopes []string
|
Scopes []string
|
||||||
AllowPasswordGrant bool
|
AllowPasswordGrant bool
|
||||||
|
|
||||||
ExchangeAuthcodeAndValidateTokensFunc func(
|
ExchangeAuthcodeAndValidateTokensFunc func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
authcode string,
|
authcode string,
|
||||||
@ -84,8 +93,16 @@ type TestUpstreamOIDCIdentityProvider struct {
|
|||||||
expectedIDTokenNonce nonce.Nonce,
|
expectedIDTokenNonce nonce.Nonce,
|
||||||
) (*oidctypes.Token, error)
|
) (*oidctypes.Token, error)
|
||||||
|
|
||||||
|
PasswordCredentialsGrantAndValidateTokensFunc func(
|
||||||
|
ctx context.Context,
|
||||||
|
username string,
|
||||||
|
password string,
|
||||||
|
) (*oidctypes.Token, error)
|
||||||
|
|
||||||
exchangeAuthcodeAndValidateTokensCallCount int
|
exchangeAuthcodeAndValidateTokensCallCount int
|
||||||
exchangeAuthcodeAndValidateTokensArgs []*ExchangeAuthcodeAndValidateTokenArgs
|
exchangeAuthcodeAndValidateTokensArgs []*ExchangeAuthcodeAndValidateTokenArgs
|
||||||
|
passwordCredentialsGrantAndValidateTokensCallCount int
|
||||||
|
passwordCredentialsGrantAndValidateTokensArgs []*PasswordCredentialsGrantAndValidateTokensArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *TestUpstreamOIDCIdentityProvider) GetName() string {
|
func (u *TestUpstreamOIDCIdentityProvider) GetName() string {
|
||||||
@ -117,8 +134,16 @@ func (u *TestUpstreamOIDCIdentityProvider) AllowsPasswordGrant() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *TestUpstreamOIDCIdentityProvider) PasswordCredentialsGrantAndValidateTokens(ctx context.Context, username, password string) (*oidctypes.Token, error) {
|
func (u *TestUpstreamOIDCIdentityProvider) PasswordCredentialsGrantAndValidateTokens(ctx context.Context, username, password string) (*oidctypes.Token, error) {
|
||||||
// TODO implement this unit test helper
|
if u.passwordCredentialsGrantAndValidateTokensArgs == nil {
|
||||||
return nil, nil
|
u.passwordCredentialsGrantAndValidateTokensArgs = make([]*PasswordCredentialsGrantAndValidateTokensArgs, 0)
|
||||||
|
}
|
||||||
|
u.passwordCredentialsGrantAndValidateTokensCallCount++
|
||||||
|
u.passwordCredentialsGrantAndValidateTokensArgs = append(u.passwordCredentialsGrantAndValidateTokensArgs, &PasswordCredentialsGrantAndValidateTokensArgs{
|
||||||
|
Ctx: ctx,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
})
|
||||||
|
return u.PasswordCredentialsGrantAndValidateTokensFunc(ctx, username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *TestUpstreamOIDCIdentityProvider) ExchangeAuthcodeAndValidateTokens(
|
func (u *TestUpstreamOIDCIdentityProvider) ExchangeAuthcodeAndValidateTokens(
|
||||||
@ -190,10 +215,193 @@ func (b *UpstreamIDPListerBuilder) Build() provider.DynamicUpstreamIDPProvider {
|
|||||||
return idpProvider
|
return idpProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToPasswordCredentialsGrantAndValidateTokens(
|
||||||
|
t *testing.T,
|
||||||
|
expectedPerformedByUpstreamName string,
|
||||||
|
expectedArgs *PasswordCredentialsGrantAndValidateTokensArgs,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
var actualArgs *PasswordCredentialsGrantAndValidateTokensArgs
|
||||||
|
var actualNameOfUpstreamWhichMadeCall string
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
||||||
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
||||||
|
callCountOnThisUpstream := upstreamOIDC.passwordCredentialsGrantAndValidateTokensCallCount
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams += callCountOnThisUpstream
|
||||||
|
if callCountOnThisUpstream == 1 {
|
||||||
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
||||||
|
actualArgs = upstreamOIDC.passwordCredentialsGrantAndValidateTokensArgs[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, actualCallCountAcrossAllOIDCUpstreams,
|
||||||
|
"should have been exactly one call to PasswordCredentialsGrantAndValidateTokens() by all OIDC upstreams",
|
||||||
|
)
|
||||||
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
||||||
|
"PasswordCredentialsGrantAndValidateTokens() was called on the wrong OIDC upstream",
|
||||||
|
)
|
||||||
|
require.Equal(t, expectedArgs, actualArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToPasswordCredentialsGrantAndValidateTokens(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
||||||
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams += upstreamOIDC.passwordCredentialsGrantAndValidateTokensCallCount
|
||||||
|
}
|
||||||
|
require.Equal(t, 0, actualCallCountAcrossAllOIDCUpstreams,
|
||||||
|
"expected exactly zero calls to PasswordCredentialsGrantAndValidateTokens()",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToExchangeAuthcodeAndValidateTokens(
|
||||||
|
t *testing.T,
|
||||||
|
expectedPerformedByUpstreamName string,
|
||||||
|
expectedArgs *ExchangeAuthcodeAndValidateTokenArgs,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
var actualArgs *ExchangeAuthcodeAndValidateTokenArgs
|
||||||
|
var actualNameOfUpstreamWhichMadeCall string
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
||||||
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
||||||
|
callCountOnThisUpstream := upstreamOIDC.exchangeAuthcodeAndValidateTokensCallCount
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams += callCountOnThisUpstream
|
||||||
|
if callCountOnThisUpstream == 1 {
|
||||||
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
||||||
|
actualArgs = upstreamOIDC.exchangeAuthcodeAndValidateTokensArgs[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, actualCallCountAcrossAllOIDCUpstreams,
|
||||||
|
"should have been exactly one call to ExchangeAuthcodeAndValidateTokens() by all OIDC upstreams",
|
||||||
|
)
|
||||||
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
||||||
|
"ExchangeAuthcodeAndValidateTokens() was called on the wrong OIDC upstream",
|
||||||
|
)
|
||||||
|
require.Equal(t, expectedArgs, actualArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToExchangeAuthcodeAndValidateTokens(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
||||||
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
||||||
|
actualCallCountAcrossAllOIDCUpstreams += upstreamOIDC.exchangeAuthcodeAndValidateTokensCallCount
|
||||||
|
}
|
||||||
|
require.Equal(t, 0, actualCallCountAcrossAllOIDCUpstreams,
|
||||||
|
"expected exactly zero calls to ExchangeAuthcodeAndValidateTokens()",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func NewUpstreamIDPListerBuilder() *UpstreamIDPListerBuilder {
|
func NewUpstreamIDPListerBuilder() *UpstreamIDPListerBuilder {
|
||||||
return &UpstreamIDPListerBuilder{}
|
return &UpstreamIDPListerBuilder{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestUpstreamOIDCIdentityProviderBuilder struct {
|
||||||
|
name string
|
||||||
|
clientID string
|
||||||
|
scopes []string
|
||||||
|
idToken map[string]interface{}
|
||||||
|
usernameClaim string
|
||||||
|
groupsClaim string
|
||||||
|
authorizationURL url.URL
|
||||||
|
allowPasswordGrant bool
|
||||||
|
authcodeExchangeErr error
|
||||||
|
passwordGrantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithName(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.name = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithClientID(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.clientID = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAuthorizationURL(value url.URL) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.authorizationURL = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAllowPasswordGrant(value bool) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.allowPasswordGrant = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithScopes(values []string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.scopes = values
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithUsernameClaim(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.usernameClaim = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutUsernameClaim() *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.usernameClaim = ""
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithGroupsClaim(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.groupsClaim = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutGroupsClaim() *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.groupsClaim = ""
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithIDTokenClaim(name string, value interface{}) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
if u.idToken == nil {
|
||||||
|
u.idToken = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
u.idToken[name] = value
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutIDTokenClaim(claim string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
delete(u.idToken, claim)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithUpstreamAuthcodeExchangeError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.authcodeExchangeErr = err
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithPasswordGrantError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
u.passwordGrantErr = err
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) Build() *TestUpstreamOIDCIdentityProvider {
|
||||||
|
return &TestUpstreamOIDCIdentityProvider{
|
||||||
|
Name: u.name,
|
||||||
|
ClientID: u.clientID,
|
||||||
|
UsernameClaim: u.usernameClaim,
|
||||||
|
GroupsClaim: u.groupsClaim,
|
||||||
|
Scopes: u.scopes,
|
||||||
|
AllowPasswordGrant: u.allowPasswordGrant,
|
||||||
|
AuthorizationURL: u.authorizationURL,
|
||||||
|
ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error) {
|
||||||
|
if u.authcodeExchangeErr != nil {
|
||||||
|
return nil, u.authcodeExchangeErr
|
||||||
|
}
|
||||||
|
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}}, nil
|
||||||
|
},
|
||||||
|
PasswordCredentialsGrantAndValidateTokensFunc: func(ctx context.Context, username, password string) (*oidctypes.Token, error) {
|
||||||
|
if u.passwordGrantErr != nil {
|
||||||
|
return nil, u.passwordGrantErr
|
||||||
|
}
|
||||||
|
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestUpstreamOIDCIdentityProviderBuilder() *TestUpstreamOIDCIdentityProviderBuilder {
|
||||||
|
return &TestUpstreamOIDCIdentityProviderBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
// Declare a separate type from the production code to ensure that the state param's contents was serialized
|
// Declare a separate type from the production code to ensure that the state param's contents was serialized
|
||||||
// in the format that we expect, with the json keys that we expect, etc. This also ensure that the order of
|
// in the format that we expect, with the json keys that we expect, etc. This also ensure that the order of
|
||||||
// the serialized fields is the same, which doesn't really matter expect that we can make simpler equality
|
// the serialized fields is the same, which doesn't really matter expect that we can make simpler equality
|
||||||
|
Loading…
Reference in New Issue
Block a user