2021-04-07 23:12:13 +00:00
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
2020-11-04 00:17:38 +00:00
// SPDX-License-Identifier: Apache-2.0
package auth
import (
2021-04-09 00:28:01 +00:00
"context"
2020-11-04 00:17:38 +00:00
"fmt"
2020-11-04 20:19:07 +00:00
"html"
2020-11-04 00:17:38 +00:00
"net/http"
"net/http/httptest"
"net/url"
2020-11-12 23:36:59 +00:00
"regexp"
2020-11-04 00:17:38 +00:00
"strings"
"testing"
2020-11-11 01:58:00 +00:00
"github.com/gorilla/securecookie"
2021-04-09 00:28:01 +00:00
"github.com/ory/fosite"
2020-11-04 00:17:38 +00:00
"github.com/stretchr/testify/require"
2021-04-09 00:28:01 +00:00
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/client-go/kubernetes/fake"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
2020-11-04 00:17:38 +00:00
2020-11-04 15:15:19 +00:00
"go.pinniped.dev/internal/here"
2020-11-04 23:04:50 +00:00
"go.pinniped.dev/internal/oidc"
2020-11-11 01:58:00 +00:00
"go.pinniped.dev/internal/oidc/csrftoken"
2020-12-04 15:06:55 +00:00
"go.pinniped.dev/internal/oidc/jwks"
2020-11-04 00:17:38 +00:00
"go.pinniped.dev/internal/oidc/provider"
2020-12-04 15:06:55 +00:00
"go.pinniped.dev/internal/testutil"
2021-04-09 00:28:01 +00:00
"go.pinniped.dev/internal/testutil/oidctestutil"
2020-11-17 18:46:54 +00:00
"go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/pkce"
2020-11-04 00:17:38 +00:00
)
func TestAuthorizationEndpoint ( t * testing . T ) {
2020-11-04 15:15:19 +00:00
const (
2020-11-19 16:35:23 +00:00
downstreamIssuer = "https://my-downstream-issuer.com/some-path"
2020-11-11 20:29:14 +00:00
downstreamRedirectURI = "http://127.0.0.1/callback"
downstreamRedirectURIWithDifferentPort = "http://127.0.0.1:42/callback"
2021-04-09 00:28:01 +00:00
downstreamNonce = "some-nonce-value"
downstreamPKCEChallenge = "some-challenge"
downstreamPKCEChallengeMethod = "S256"
2020-12-12 01:39:58 +00:00
happyState = "8b-state"
2021-04-09 00:28:01 +00:00
downstreamClientID = "pinniped-cli"
upstreamLDAPURL = "ldaps://some-ldap-host:123"
htmlContentType = "text/html; charset=utf-8"
2020-11-04 15:15:19 +00:00
)
2020-12-12 01:39:58 +00:00
require . Len ( t , happyState , 8 , "we expect fosite to allow 8 byte state params, so we want to test that boundary case" )
2020-11-04 15:15:19 +00:00
var (
fositeInvalidClientErrorBody = here . Doc ( `
2020-11-04 16:29:33 +00:00
{
"error" : "invalid_client" ,
2020-12-17 20:09:19 +00:00
"error_description" : "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The requested OAuth 2.0 Client does not exist."
2020-11-04 16:29:33 +00:00
}
2020-11-04 16:43:45 +00:00
` )
2020-11-04 15:30:53 +00:00
fositeInvalidRedirectURIErrorBody = here . Doc ( `
2020-11-04 16:29:33 +00:00
{
"error" : "invalid_request" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls."
2020-11-04 16:29:33 +00:00
}
2020-11-04 16:43:45 +00:00
` )
2020-11-04 16:12:26 +00:00
2020-11-06 22:44:58 +00:00
fositePromptHasNoneAndOtherValueErrorQuery = map [ string ] string {
"error" : "invalid_request" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-06 22:44:58 +00:00
}
2020-11-04 20:19:07 +00:00
fositeMissingCodeChallengeErrorQuery = map [ string ] string {
"error" : "invalid_request" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-04 20:19:07 +00:00
}
fositeMissingCodeChallengeMethodErrorQuery = map [ string ] string {
"error" : "invalid_request" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must use code_challenge_method=S256, plain is not allowed." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-04 20:19:07 +00:00
}
2020-11-04 20:29:43 +00:00
fositeInvalidCodeChallengeErrorQuery = map [ string ] string {
"error" : "invalid_request" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The code_challenge_method is not supported, use S256 instead." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-04 20:29:43 +00:00
}
2020-11-04 17:58:40 +00:00
fositeUnsupportedResponseTypeErrorQuery = map [ string ] string {
"error" : "unsupported_response_type" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The authorization server does not support obtaining a token using this method. The client is not allowed to request response_type 'unsupported'." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-04 17:58:40 +00:00
}
fositeInvalidScopeErrorQuery = map [ string ] string {
"error" : "invalid_scope" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The requested scope is invalid, unknown, or malformed. The OAuth 2.0 Client is not allowed to request scope 'tuna'." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-04 17:58:40 +00:00
}
fositeInvalidStateErrorQuery = map [ string ] string {
"error" : "invalid_state" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The state is missing or does not have enough characters and is therefore considered too weak. Request parameter 'state' must be at least be 8 characters long to ensure sufficient entropy." ,
2020-11-04 17:58:40 +00:00
"state" : "short" ,
}
fositeMissingResponseTypeErrorQuery = map [ string ] string {
"error" : "unsupported_response_type" ,
2020-12-17 20:09:19 +00:00
"error_description" : "The authorization server does not support obtaining a token using this method. `The request is missing the 'response_type' parameter." ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2020-11-04 17:58:40 +00:00
}
2020-11-04 15:15:19 +00:00
2021-04-09 00:28:01 +00:00
fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery = map [ string ] string {
"error" : "access_denied" ,
"error_description" : "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider." ,
"state" : happyState ,
}
fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery = map [ string ] string {
"error" : "access_denied" ,
"error_description" : "The resource owner or authorization server denied the request. Missing or blank username or password." ,
"state" : happyState ,
}
)
2021-04-08 00:05:25 +00:00
hmacSecretFunc := func ( ) [ ] byte { return [ ] byte ( "some secret - must have at least 32 bytes" ) }
require . GreaterOrEqual ( t , len ( hmacSecretFunc ( ) ) , 32 , "fosite requires that hmac secrets have at least 32 bytes" )
jwksProviderIsUnused := jwks . NewDynamicJWKSProvider ( )
timeoutsConfiguration := oidc . DefaultOIDCTimeoutsConfiguration ( )
2021-04-09 00:28:01 +00:00
createOauthHelperWithRealStorage := func ( secretsClient v1 . SecretInterface ) ( fosite . OAuth2Provider , * oidc . KubeStorage ) {
// Configure fosite the same way that the production code would when using Kube storage.
// Inject this into our test subject at the last second so we get a fresh storage for every test.
kubeOauthStore := oidc . NewKubeStorage ( secretsClient , timeoutsConfiguration )
return oidc . FositeOauth2Helper ( kubeOauthStore , downstreamIssuer , hmacSecretFunc , jwksProviderIsUnused , timeoutsConfiguration ) , kubeOauthStore
}
2021-04-08 00:05:25 +00:00
// Configure fosite the same way that the production code would, using NullStorage to turn off storage.
nullOauthStore := oidc . NullStorage { }
oauthHelperWithNullStorage := oidc . FositeOauth2Helper ( nullOauthStore , downstreamIssuer , hmacSecretFunc , jwksProviderIsUnused , timeoutsConfiguration )
2020-11-04 00:17:38 +00:00
upstreamAuthURL , err := url . Parse ( "https://some-upstream-idp:8443/auth" )
require . NoError ( t , err )
2020-11-20 01:57:07 +00:00
upstreamOIDCIdentityProvider := oidctestutil . TestUpstreamOIDCIdentityProvider {
2021-04-09 00:28:01 +00:00
Name : "some-oidc-idp" ,
2020-11-04 00:17:38 +00:00
ClientID : "some-client-id" ,
AuthorizationURL : * upstreamAuthURL ,
2020-12-08 01:22:34 +00:00
Scopes : [ ] string { "scope1" , "scope2" } , // the scopes to request when starting the upstream authorization flow
2020-11-04 00:17:38 +00:00
}
2021-04-09 00:28:01 +00:00
happyLDAPUsername := "some-ldap-user"
happyLDAPUsernameFromAuthenticator := "some-mapped-ldap-username"
happyLDAPPassword := "some-ldap-password" //nolint:gosec
happyLDAPUID := "some-ldap-uid"
happyLDAPGroups := [ ] string { "group1" , "group2" , "group3" }
upstreamLDAPIdentityProvider := oidctestutil . TestUpstreamLDAPIdentityProvider {
Name : "some-ldap-idp" ,
URL : upstreamLDAPURL ,
AuthenticateFunc : func ( ctx context . Context , username , password string ) ( * authenticator . Response , bool , error ) {
if username == "" || password == "" {
return nil , false , fmt . Errorf ( "should not have passed empty username or password to the authenticator" )
}
if username == happyLDAPUsername && password == happyLDAPPassword {
return & authenticator . Response {
User : & user . DefaultInfo {
Name : happyLDAPUsernameFromAuthenticator ,
UID : happyLDAPUID ,
Groups : happyLDAPGroups ,
} ,
} , true , nil
}
return nil , false , nil
} ,
}
erroringUpstreamLDAPIdentityProvider := oidctestutil . TestUpstreamLDAPIdentityProvider {
Name : "some-ldap-idp" ,
AuthenticateFunc : func ( ctx context . Context , username , password string ) ( * authenticator . Response , bool , error ) {
return nil , false , fmt . Errorf ( "some ldap upstream auth error" )
} ,
}
2020-11-11 01:58:00 +00:00
happyCSRF := "test-csrf"
happyPKCE := "test-pkce"
2020-12-12 01:39:58 +00:00
happyNonce := "test-nonce"
2020-11-11 01:58:00 +00:00
happyCSRFGenerator := func ( ) ( csrftoken . CSRFToken , error ) { return csrftoken . CSRFToken ( happyCSRF ) , nil }
happyPKCEGenerator := func ( ) ( pkce . Code , error ) { return pkce . Code ( happyPKCE ) , nil }
happyNonceGenerator := func ( ) ( nonce . Nonce , error ) { return nonce . Nonce ( happyNonce ) , nil }
2021-04-08 00:05:25 +00:00
sadCSRFGenerator := func ( ) ( csrftoken . CSRFToken , error ) { return "" , fmt . Errorf ( "some csrf generator error" ) }
sadPKCEGenerator := func ( ) ( pkce . Code , error ) { return "" , fmt . Errorf ( "some PKCE generator error" ) }
sadNonceGenerator := func ( ) ( nonce . Nonce , error ) { return "" , fmt . Errorf ( "some nonce generator error" ) }
2020-11-04 00:17:38 +00:00
// This is the PKCE challenge which is calculated as base64(sha256("test-pkce")). For example:
// $ echo -n test-pkce | shasum -a 256 | cut -d" " -f1 | xxd -r -p | base64 | cut -d"=" -f1
2020-11-04 20:19:07 +00:00
expectedUpstreamCodeChallenge := "VVaezYqum7reIhoavCHD1n2d-piN3r_mywoYj7fCR7g"
2020-11-04 00:17:38 +00:00
2020-11-12 23:36:59 +00:00
var stateEncoderHashKey = [ ] byte ( "fake-hash-secret" )
var stateEncoderBlockKey = [ ] byte ( "0123456789ABCDEF" ) // block encryption requires 16/24/32 bytes for AES
var cookieEncoderHashKey = [ ] byte ( "fake-hash-secret2" )
var cookieEncoderBlockKey = [ ] byte ( "0123456789ABCDE2" ) // block encryption requires 16/24/32 bytes for AES
require . NotEqual ( t , stateEncoderHashKey , cookieEncoderHashKey )
require . NotEqual ( t , stateEncoderBlockKey , cookieEncoderBlockKey )
var happyStateEncoder = securecookie . New ( stateEncoderHashKey , stateEncoderBlockKey )
happyStateEncoder . SetSerializer ( securecookie . JSONEncoder { } )
var happyCookieEncoder = securecookie . New ( cookieEncoderHashKey , cookieEncoderBlockKey )
happyCookieEncoder . SetSerializer ( securecookie . JSONEncoder { } )
2020-11-11 01:58:00 +00:00
encodeQuery := func ( query map [ string ] string ) string {
2020-11-04 17:58:40 +00:00
values := url . Values { }
for k , v := range query {
values [ k ] = [ ] string { v }
}
2020-11-11 01:58:00 +00:00
return values . Encode ( )
}
pathWithQuery := func ( path string , query map [ string ] string ) string {
pathToReturn := fmt . Sprintf ( "%s?%s" , path , encodeQuery ( query ) )
2020-11-04 17:58:40 +00:00
require . NotRegexp ( t , "^http" , pathToReturn , "pathWithQuery helper was used to create a URL" )
return pathToReturn
}
urlWithQuery := func ( baseURL string , query map [ string ] string ) string {
2020-11-11 01:58:00 +00:00
urlToReturn := fmt . Sprintf ( "%s?%s" , baseURL , encodeQuery ( query ) )
2020-11-04 17:58:40 +00:00
_ , err := url . Parse ( urlToReturn )
require . NoError ( t , err , "urlWithQuery helper was used to create an illegal URL" )
return urlToReturn
}
2021-04-09 00:28:01 +00:00
happyDownstreamScopesRequested := [ ] string { "openid" , "profile" , "email" }
happyDownstreamScopesGranted := [ ] string { "openid" }
2020-11-11 21:13:57 +00:00
happyGetRequestQueryMap := map [ string ] string {
"response_type" : "code" ,
2021-04-09 00:28:01 +00:00
"scope" : strings . Join ( happyDownstreamScopesRequested , " " ) ,
"client_id" : downstreamClientID ,
2020-12-12 01:39:58 +00:00
"state" : happyState ,
2021-04-09 00:28:01 +00:00
"nonce" : downstreamNonce ,
"code_challenge" : downstreamPKCEChallenge ,
"code_challenge_method" : downstreamPKCEChallengeMethod ,
2020-11-11 21:13:57 +00:00
"redirect_uri" : downstreamRedirectURI ,
2020-11-04 17:58:40 +00:00
}
2020-11-11 21:13:57 +00:00
happyGetRequestPath := pathWithQuery ( "/some/path" , happyGetRequestQueryMap )
2020-11-04 17:58:40 +00:00
2020-11-11 21:13:57 +00:00
modifiedHappyGetRequestQueryMap := func ( queryOverrides map [ string ] string ) map [ string ] string {
2020-11-04 17:58:40 +00:00
copyOfHappyGetRequestQueryMap := map [ string ] string { }
2020-11-11 21:13:57 +00:00
for k , v := range happyGetRequestQueryMap {
2020-11-04 17:58:40 +00:00
copyOfHappyGetRequestQueryMap [ k ] = v
}
for k , v := range queryOverrides {
_ , hasKey := copyOfHappyGetRequestQueryMap [ k ]
if v == "" && hasKey {
delete ( copyOfHappyGetRequestQueryMap , k )
} else {
copyOfHappyGetRequestQueryMap [ k ] = v
}
}
2020-11-11 21:13:57 +00:00
return copyOfHappyGetRequestQueryMap
}
modifiedHappyGetRequestPath := func ( queryOverrides map [ string ] string ) string {
return pathWithQuery ( "/some/path" , modifiedHappyGetRequestQueryMap ( queryOverrides ) )
2020-11-04 17:58:40 +00:00
}
2020-11-20 21:14:45 +00:00
expectedUpstreamStateParam := func ( queryOverrides map [ string ] string , csrfValueOverride , upstreamNameOverride string ) string {
2020-11-12 23:36:59 +00:00
csrf := happyCSRF
if csrfValueOverride != "" {
csrf = csrfValueOverride
}
2020-11-20 21:14:45 +00:00
upstreamName := upstreamOIDCIdentityProvider . Name
if upstreamNameOverride != "" {
upstreamName = upstreamNameOverride
}
2020-11-12 23:36:59 +00:00
encoded , err := happyStateEncoder . Encode ( "s" ,
2020-11-20 01:57:07 +00:00
oidctestutil . ExpectedUpstreamStateParamFormat {
2020-11-11 21:13:57 +00:00
P : encodeQuery ( modifiedHappyGetRequestQueryMap ( queryOverrides ) ) ,
2020-11-20 21:14:45 +00:00
U : upstreamName ,
2020-11-11 20:29:14 +00:00
N : happyNonce ,
2020-11-12 23:36:59 +00:00
C : csrf ,
2020-11-11 20:29:14 +00:00
K : happyPKCE ,
V : "1" ,
} ,
)
require . NoError ( t , err )
return encoded
}
2020-11-11 01:58:00 +00:00
2021-04-08 00:05:25 +00:00
expectedRedirectLocationForUpstreamOIDC := func ( expectedUpstreamState string , expectedPrompt string ) string {
2020-12-12 01:13:27 +00:00
query := map [ string ] string {
2020-11-04 17:58:40 +00:00
"response_type" : "code" ,
"access_type" : "offline" ,
"scope" : "scope1 scope2" ,
"client_id" : "some-client-id" ,
2020-11-11 21:13:57 +00:00
"state" : expectedUpstreamState ,
2020-11-11 01:58:00 +00:00
"nonce" : happyNonce ,
2020-11-04 20:19:07 +00:00
"code_challenge" : expectedUpstreamCodeChallenge ,
2021-04-09 00:28:01 +00:00
"code_challenge_method" : downstreamPKCEChallengeMethod ,
2020-11-20 21:14:45 +00:00
"redirect_uri" : downstreamIssuer + "/callback" ,
2020-12-12 01:13:27 +00:00
}
if expectedPrompt != "" {
query [ "prompt" ] = expectedPrompt
}
return urlWithQuery ( upstreamAuthURL . String ( ) , query )
2020-11-11 20:29:14 +00:00
}
2020-11-04 00:17:38 +00:00
2021-04-09 00:28:01 +00:00
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + ` \?code=([^&]+)&scope=openid&state= ` + happyState
2020-11-12 23:36:59 +00:00
incomingCookieCSRFValue := "csrf-value-from-cookie"
encodedIncomingCookieCSRFValue , err := happyCookieEncoder . Encode ( "csrf" , incomingCookieCSRFValue )
require . NoError ( t , err )
2020-11-11 01:58:00 +00:00
2020-11-04 00:17:38 +00:00
type testCase struct {
name string
2021-04-09 00:28:01 +00:00
idpLister provider . DynamicUpstreamIDPProvider
generateCSRF func ( ) ( csrftoken . CSRFToken , error )
generatePKCE func ( ) ( pkce . Code , error )
generateNonce func ( ) ( nonce . Nonce , error )
stateEncoder oidc . Codec
cookieEncoder oidc . Codec
method string
path string
contentType string
body string
csrfCookie string
customUsernameHeader * string // nil means do not send header, empty means send header with empty value
customPasswordHeader * string // nil means do not send header, empty means send header with empty value
wantStatus int
wantContentType string
wantBodyString string
wantBodyJSON string
wantCSRFValueInCookieHeader string
2020-11-12 17:13:58 +00:00
wantBodyStringWithLocationInHref bool
2021-04-09 00:28:01 +00:00
wantLocationHeader string
wantUpstreamStateParamInLocationHeader bool
// For when the request was authenticated by an upstream LDAP provider and an authcode is being returned.
wantRedirectLocationRegexp string
wantDownstreamRedirectURI string
wantDownstreamGrantedScopes [ ] string
wantDownstreamIDTokenSubject string
wantDownstreamIDTokenUsername string
wantDownstreamIDTokenGroups [ ] string
wantDownstreamRequestedScopes [ ] string
wantDownstreamPKCEChallenge string
wantDownstreamPKCEChallengeMethod string
wantDownstreamNonce string
wantUnnecessaryStoredRecords int
2020-11-11 20:29:14 +00:00
}
2020-11-04 00:17:38 +00:00
tests := [ ] testCase {
{
2021-04-08 00:05:25 +00:00
name : "OIDC upstream happy path using GET without a CSRF cookie" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-12 17:13:58 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-12 17:13:58 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2020-11-12 23:36:59 +00:00
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( nil , "" , "" ) , "" ) ,
2020-11-12 23:36:59 +00:00
wantUpstreamStateParamInLocationHeader : true ,
wantBodyStringWithLocationInHref : true ,
} ,
2021-04-09 00:28:01 +00:00
{
name : "LDAP upstream happy path using GET" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : happyGetRequestPath ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : htmlContentType ,
wantRedirectLocationRegexp : happyAuthcodeDownstreamRedirectLocationRegexp ,
wantBodyStringWithLocationInHref : false ,
wantDownstreamIDTokenSubject : upstreamLDAPURL + "?sub=" + happyLDAPUID ,
wantDownstreamIDTokenUsername : happyLDAPUsernameFromAuthenticator ,
wantDownstreamIDTokenGroups : happyLDAPGroups ,
wantDownstreamRequestedScopes : happyDownstreamScopesRequested ,
wantDownstreamRedirectURI : downstreamRedirectURI ,
wantDownstreamGrantedScopes : happyDownstreamScopesGranted ,
wantDownstreamNonce : downstreamNonce ,
wantDownstreamPKCEChallenge : downstreamPKCEChallenge ,
wantDownstreamPKCEChallengeMethod : downstreamPKCEChallengeMethod ,
} ,
2020-11-12 23:36:59 +00:00
{
2021-04-08 00:05:25 +00:00
name : "OIDC upstream happy path using GET with a CSRF cookie" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-12 23:36:59 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
method : http . MethodGet ,
path : happyGetRequestPath ,
2020-12-01 23:01:22 +00:00
csrfCookie : "__Host-pinniped-csrf=" + encodedIncomingCookieCSRFValue + " " ,
2020-11-12 23:36:59 +00:00
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( nil , incomingCookieCSRFValue , "" ) , "" ) ,
2020-11-11 20:29:14 +00:00
wantUpstreamStateParamInLocationHeader : true ,
2020-11-12 17:13:58 +00:00
wantBodyStringWithLocationInHref : true ,
2020-11-11 20:29:14 +00:00
} ,
{
2021-04-08 00:05:25 +00:00
name : "OIDC upstream happy path using POST" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 20:29:14 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-11 20:29:14 +00:00
method : http . MethodPost ,
path : "/some/path" ,
contentType : "application/x-www-form-urlencoded" ,
2020-11-11 21:13:57 +00:00
body : encodeQuery ( happyGetRequestQueryMap ) ,
2020-11-11 20:29:14 +00:00
wantStatus : http . StatusFound ,
wantContentType : "" ,
wantBodyString : "" ,
2020-11-12 23:36:59 +00:00
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( nil , "" , "" ) , "" ) ,
2020-12-12 01:13:27 +00:00
wantUpstreamStateParamInLocationHeader : true ,
} ,
2021-04-09 00:28:01 +00:00
{
name : "LDAP upstream happy path using POST" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodPost ,
path : "/some/path" ,
contentType : "application/x-www-form-urlencoded" ,
body : encodeQuery ( happyGetRequestQueryMap ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : htmlContentType ,
wantRedirectLocationRegexp : happyAuthcodeDownstreamRedirectLocationRegexp ,
wantBodyStringWithLocationInHref : false ,
wantDownstreamIDTokenSubject : upstreamLDAPURL + "?sub=" + happyLDAPUID ,
wantDownstreamIDTokenUsername : happyLDAPUsernameFromAuthenticator ,
wantDownstreamIDTokenGroups : happyLDAPGroups ,
wantDownstreamRequestedScopes : happyDownstreamScopesRequested ,
wantDownstreamRedirectURI : downstreamRedirectURI ,
wantDownstreamGrantedScopes : happyDownstreamScopesGranted ,
wantDownstreamNonce : downstreamNonce ,
wantDownstreamPKCEChallenge : downstreamPKCEChallenge ,
wantDownstreamPKCEChallengeMethod : downstreamPKCEChallengeMethod ,
} ,
2020-12-12 01:13:27 +00:00
{
2021-04-08 00:05:25 +00:00
name : "OIDC upstream happy path with prompt param login passed through to redirect uri" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-12-12 01:13:27 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "prompt" : "login" } ) ,
contentType : "application/x-www-form-urlencoded" ,
body : encodeQuery ( happyGetRequestQueryMap ) ,
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2020-12-12 01:13:27 +00:00
wantBodyStringWithLocationInHref : true ,
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( map [ string ] string { "prompt" : "login" } , "" , "" ) , "login" ) ,
2020-11-11 20:29:14 +00:00
wantUpstreamStateParamInLocationHeader : true ,
2020-11-04 00:17:38 +00:00
} ,
2020-11-20 21:56:35 +00:00
{
2021-04-09 00:28:01 +00:00
name : "OIDC upstream with error while decoding CSRF cookie just generates a new cookie and succeeds as usual" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-20 21:56:35 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
method : http . MethodGet ,
path : happyGetRequestPath ,
csrfCookie : "__Host-pinniped-csrf=this-value-was-not-signed-by-pinniped" ,
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2020-11-20 21:56:35 +00:00
// Generated a new CSRF cookie and set it in the response.
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( nil , "" , "" ) , "" ) ,
2020-11-20 21:56:35 +00:00
wantUpstreamStateParamInLocationHeader : true ,
wantBodyStringWithLocationInHref : true ,
} ,
2020-11-04 00:17:38 +00:00
{
2021-04-08 00:05:25 +00:00
name : "OIDC upstream happy path when downstream redirect uri matches what is configured for client except for the port number" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 00:17:38 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-11 20:29:14 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string {
"redirect_uri" : downstreamRedirectURIWithDifferentPort , // not the same port number that is registered for the client
} ) ,
2020-11-12 23:36:59 +00:00
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2020-11-12 23:36:59 +00:00
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( map [ string ] string {
2020-11-11 21:13:57 +00:00
"redirect_uri" : downstreamRedirectURIWithDifferentPort , // not the same port number that is registered for the client
2020-12-12 01:13:27 +00:00
} , "" , "" ) , "" ) ,
2020-11-11 20:29:14 +00:00
wantUpstreamStateParamInLocationHeader : true ,
2020-11-12 17:13:58 +00:00
wantBodyStringWithLocationInHref : true ,
2020-11-04 15:30:53 +00:00
} ,
2021-04-09 00:28:01 +00:00
{
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 ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string {
"redirect_uri" : downstreamRedirectURIWithDifferentPort , // not the same port number that is registered for the client
} ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : htmlContentType ,
wantRedirectLocationRegexp : downstreamRedirectURIWithDifferentPort + ` \?code=([^&]+)&scope=openid&state= ` + happyState ,
wantBodyStringWithLocationInHref : false ,
wantDownstreamIDTokenSubject : upstreamLDAPURL + "?sub=" + happyLDAPUID ,
wantDownstreamIDTokenUsername : happyLDAPUsernameFromAuthenticator ,
wantDownstreamIDTokenGroups : happyLDAPGroups ,
wantDownstreamRequestedScopes : happyDownstreamScopesRequested ,
wantDownstreamRedirectURI : downstreamRedirectURIWithDifferentPort ,
wantDownstreamGrantedScopes : happyDownstreamScopesGranted ,
wantDownstreamNonce : downstreamNonce ,
wantDownstreamPKCEChallenge : downstreamPKCEChallenge ,
wantDownstreamPKCEChallengeMethod : downstreamPKCEChallengeMethod ,
} ,
2020-12-08 01:22:34 +00:00
{
2021-04-08 00:05:25 +00:00
name : "OIDC upstream happy path when downstream requested scopes include offline_access" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-12-08 01:22:34 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "scope" : "openid offline_access" } ) ,
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2020-12-08 01:22:34 +00:00
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam ( map [ string ] string {
2020-12-08 01:22:34 +00:00
"scope" : "openid offline_access" ,
2020-12-12 01:13:27 +00:00
} , "" , "" ) , "" ) ,
2020-12-08 01:22:34 +00:00
wantUpstreamStateParamInLocationHeader : true ,
wantBodyStringWithLocationInHref : true ,
} ,
2020-11-04 15:30:53 +00:00
{
2021-04-09 00:28:01 +00:00
name : "error during upstream LDAP authentication" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & erroringUpstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : happyGetRequestPath ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusBadGateway ,
wantContentType : htmlContentType ,
wantBodyString : "Bad Gateway: unexpected error during upstream authentication\n" ,
} ,
{
name : "wrong upstream password for LDAP authentication" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : happyGetRequestPath ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( "wrong-password" ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "wrong upstream username for LDAP authentication" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : happyGetRequestPath ,
customUsernameHeader : stringPtr ( "wrong-username" ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "missing upstream username on request for LDAP authentication" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : happyGetRequestPath ,
customUsernameHeader : nil , // do not send header
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "missing upstream password on request for LDAP authentication" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : happyGetRequestPath ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : nil , // do not send header
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "downstream redirect uri does not match what is configured for client when using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 15:30:53 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 15:30:53 +00:00
method : http . MethodGet ,
2020-11-04 17:58:40 +00:00
path : modifiedHappyGetRequestPath ( map [ string ] string {
"redirect_uri" : "http://127.0.0.1/does-not-match-what-is-configured-for-pinniped-cli-client" ,
} ) ,
2020-11-04 15:30:53 +00:00
wantStatus : http . StatusBadRequest ,
wantContentType : "application/json; charset=utf-8" ,
wantBodyJSON : fositeInvalidRedirectURIErrorBody ,
2020-11-04 15:15:19 +00:00
} ,
2020-11-04 23:04:50 +00:00
{
2021-04-09 00:28:01 +00:00
name : "downstream redirect uri does not match what is configured for client when using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string {
"redirect_uri" : "http://127.0.0.1/does-not-match-what-is-configured-for-pinniped-cli-client" ,
} ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusBadRequest ,
wantContentType : "application/json; charset=utf-8" ,
wantBodyJSON : fositeInvalidRedirectURIErrorBody ,
} ,
{
name : "downstream client does not exist when using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 20:29:14 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-11 20:29:14 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "client_id" : "invalid-client" } ) ,
wantStatus : http . StatusUnauthorized ,
wantContentType : "application/json; charset=utf-8" ,
wantBodyJSON : fositeInvalidClientErrorBody ,
2020-11-04 23:04:50 +00:00
} ,
2020-11-04 16:12:26 +00:00
{
2021-04-09 00:28:01 +00:00
name : "downstream client does not exist when using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "client_id" : "invalid-client" } ) ,
wantStatus : http . StatusUnauthorized ,
wantContentType : "application/json; charset=utf-8" ,
wantBodyJSON : fositeInvalidClientErrorBody ,
} ,
{
name : "response type is unsupported when using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 17:58:40 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 17:58:40 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "response_type" : "unsupported" } ) ,
2020-11-04 16:12:26 +00:00
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
2020-11-04 17:58:40 +00:00
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeUnsupportedResponseTypeErrorQuery ) ,
2020-11-04 20:19:07 +00:00
wantBodyString : "" ,
2020-11-04 16:12:26 +00:00
} ,
{
2021-04-09 00:28:01 +00:00
name : "response type is unsupported when using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "response_type" : "unsupported" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeUnsupportedResponseTypeErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "downstream scopes do not match what is configured for client using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 17:58:40 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 17:58:40 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "scope" : "openid profile email tuna" } ) ,
2020-11-04 16:12:26 +00:00
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
2020-11-04 17:58:40 +00:00
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeInvalidScopeErrorQuery ) ,
2020-11-04 20:19:07 +00:00
wantBodyString : "" ,
2020-11-04 16:12:26 +00:00
} ,
{
2021-04-09 00:28:01 +00:00
name : "downstream scopes do not match what is configured for client using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "scope" : "openid tuna" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeInvalidScopeErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "missing response type in request using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 17:58:40 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 17:58:40 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "response_type" : "" } ) ,
2020-11-04 16:12:26 +00:00
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
2020-11-04 17:58:40 +00:00
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingResponseTypeErrorQuery ) ,
2020-11-04 20:19:07 +00:00
wantBodyString : "" ,
2020-11-04 16:12:26 +00:00
} ,
{
2021-04-09 00:28:01 +00:00
name : "missing response type in request using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "response_type" : "" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingResponseTypeErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "missing client id in request using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 17:58:40 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 17:58:40 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "client_id" : "" } ) ,
2020-11-04 16:12:26 +00:00
wantStatus : http . StatusUnauthorized ,
wantContentType : "application/json; charset=utf-8" ,
wantBodyJSON : fositeInvalidClientErrorBody ,
} ,
2020-11-04 20:19:07 +00:00
{
2021-04-09 00:28:01 +00:00
name : "missing client id in request using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "client_id" : "" } ) ,
wantStatus : http . StatusUnauthorized ,
wantContentType : "application/json; charset=utf-8" ,
wantBodyJSON : fositeInvalidClientErrorBody ,
} ,
{
name : "missing PKCE code_challenge in request using OIDC upstream" , // See https://tools.ietf.org/html/rfc7636#section-4.4.1
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 20:19:07 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 20:19:07 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge" : "" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingCodeChallengeErrorQuery ) ,
wantBodyString : "" ,
} ,
2020-11-04 20:29:43 +00:00
{
2021-04-09 00:28:01 +00:00
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 ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge" : "" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingCodeChallengeErrorQuery ) ,
wantBodyString : "" ,
wantUnnecessaryStoredRecords : 2 , // fosite already stored the authcode and oidc session before it noticed the error
} ,
{
name : "invalid value for PKCE code_challenge_method in request using OIDC upstream" , // https://tools.ietf.org/html/rfc7636#section-4.3
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 20:29:43 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 20:29:43 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge_method" : "this-is-not-a-valid-pkce-alg" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeInvalidCodeChallengeErrorQuery ) ,
wantBodyString : "" ,
} ,
{
2021-04-09 00:28:01 +00:00
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 ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge_method" : "this-is-not-a-valid-pkce-alg" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeInvalidCodeChallengeErrorQuery ) ,
wantBodyString : "" ,
wantUnnecessaryStoredRecords : 2 , // fosite already stored the authcode and oidc session before it noticed the error
} ,
{
name : "when PKCE code_challenge_method in request is `plain` using OIDC upstream" , // https://tools.ietf.org/html/rfc7636#section-4.3
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 20:29:43 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 20:29:43 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge_method" : "plain" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingCodeChallengeMethodErrorQuery ) ,
wantBodyString : "" ,
} ,
2020-11-04 20:19:07 +00:00
{
2021-04-09 00:28:01 +00:00
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 ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge_method" : "plain" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingCodeChallengeMethodErrorQuery ) ,
wantBodyString : "" ,
wantUnnecessaryStoredRecords : 2 , // fosite already stored the authcode and oidc session before it noticed the error
} ,
{
name : "missing PKCE code_challenge_method in request using OIDC upstream" , // See https://tools.ietf.org/html/rfc7636#section-4.4.1
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 20:19:07 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 20:19:07 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge_method" : "" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingCodeChallengeMethodErrorQuery ) ,
wantBodyString : "" ,
} ,
2021-04-09 00:28:01 +00:00
{
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 ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "code_challenge_method" : "" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeMissingCodeChallengeMethodErrorQuery ) ,
wantBodyString : "" ,
wantUnnecessaryStoredRecords : 2 , // fosite already stored the authcode and oidc session before it noticed the error
} ,
2020-11-06 22:44:58 +00:00
{
// This is just one of the many OIDC validations run by fosite. This test is to ensure that we are running
2021-04-09 00:28:01 +00:00
// 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" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-06 22:44:58 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-06 22:44:58 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "prompt" : "none login" } ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositePromptHasNoneAndOtherValueErrorQuery ) ,
wantBodyString : "" ,
} ,
2020-11-11 21:13:57 +00:00
{
2021-04-09 00:28:01 +00:00
// 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.
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 ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "prompt" : "none login" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositePromptHasNoneAndOtherValueErrorQuery ) ,
wantBodyString : "" ,
wantUnnecessaryStoredRecords : 1 , // fosite already stored the authcode before it noticed the error
} ,
{
name : "happy path: downstream OIDC validations are skipped when the openid scope was not requested using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 21:13:57 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-11 21:13:57 +00:00
method : http . MethodGet ,
// The following prompt value is illegal when openid is requested, but note that openid is not requested.
2020-11-12 23:36:59 +00:00
path : modifiedHappyGetRequestPath ( map [ string ] string { "prompt" : "none login" , "scope" : "email" } ) ,
wantStatus : http . StatusFound ,
2021-04-09 00:28:01 +00:00
wantContentType : htmlContentType ,
2020-11-12 23:36:59 +00:00
wantCSRFValueInCookieHeader : happyCSRF ,
2021-04-08 00:05:25 +00:00
wantLocationHeader : expectedRedirectLocationForUpstreamOIDC ( expectedUpstreamStateParam (
2020-11-20 21:14:45 +00:00
map [ string ] string { "prompt" : "none login" , "scope" : "email" } , "" , "" ,
2020-12-12 01:13:27 +00:00
) , "" ) ,
2020-11-11 21:13:57 +00:00
wantUpstreamStateParamInLocationHeader : true ,
2020-11-12 17:13:58 +00:00
wantBodyStringWithLocationInHref : true ,
2020-11-11 21:13:57 +00:00
} ,
2020-11-04 16:12:26 +00:00
{
2021-04-09 00:28:01 +00:00
name : "happy path: downstream OIDC validations are skipped when the openid scope was not requested using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
// 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" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : htmlContentType ,
wantRedirectLocationRegexp : downstreamRedirectURI + ` \?code=([^&]+)&scope=&state= ` + happyState , // no scopes granted
wantBodyStringWithLocationInHref : false ,
wantDownstreamIDTokenSubject : upstreamLDAPURL + "?sub=" + happyLDAPUID ,
wantDownstreamIDTokenUsername : happyLDAPUsernameFromAuthenticator ,
wantDownstreamIDTokenGroups : happyLDAPGroups ,
wantDownstreamRequestedScopes : [ ] string { "email" } , // only email was requested
wantDownstreamRedirectURI : downstreamRedirectURI ,
wantDownstreamGrantedScopes : [ ] string { } , // no scopes granted
wantDownstreamNonce : downstreamNonce ,
wantDownstreamPKCEChallenge : downstreamPKCEChallenge ,
wantDownstreamPKCEChallengeMethod : downstreamPKCEChallengeMethod ,
} ,
{
name : "downstream state does not have enough entropy using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 17:58:40 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 17:58:40 +00:00
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "state" : "short" } ) ,
2020-11-04 16:12:26 +00:00
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
2020-11-04 17:58:40 +00:00
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeInvalidStateErrorQuery ) ,
2020-11-04 20:19:07 +00:00
wantBodyString : "" ,
2020-11-04 16:12:26 +00:00
} ,
2020-11-11 20:29:14 +00:00
{
2021-04-09 00:28:01 +00:00
name : "downstream state does not have enough entropy using LDAP upstream" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) ,
method : http . MethodGet ,
path : modifiedHappyGetRequestPath ( map [ string ] string { "state" : "short" } ) ,
customUsernameHeader : stringPtr ( happyLDAPUsername ) ,
customPasswordHeader : stringPtr ( happyLDAPPassword ) ,
wantStatus : http . StatusFound ,
wantContentType : "application/json; charset=utf-8" ,
wantLocationHeader : urlWithQuery ( downstreamRedirectURI , fositeInvalidStateErrorQuery ) ,
wantBodyString : "" ,
} ,
{
name : "error while encoding upstream state param using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 20:29:14 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : & errorReturningEncoder { } ,
cookieEncoder : happyCookieEncoder ,
2020-11-11 20:29:14 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusInternalServerError ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Internal Server Error: error encoding upstream state param\n" ,
} ,
2020-11-12 23:36:59 +00:00
{
2021-04-09 00:28:01 +00:00
name : "error while encoding CSRF cookie value for new cookie using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-12 23:36:59 +00:00
generateCSRF : happyCSRFGenerator ,
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
stateEncoder : happyStateEncoder ,
cookieEncoder : & errorReturningEncoder { } ,
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusInternalServerError ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Internal Server Error: error encoding CSRF cookie\n" ,
} ,
2020-11-04 00:17:38 +00:00
{
2021-04-09 00:28:01 +00:00
name : "error while generating CSRF token using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2021-04-08 00:05:25 +00:00
generateCSRF : sadCSRFGenerator ,
2020-11-04 00:17:38 +00:00
generatePKCE : happyPKCEGenerator ,
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 00:17:38 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusInternalServerError ,
wantContentType : "text/plain; charset=utf-8" ,
2020-11-11 01:58:00 +00:00
wantBodyString : "Internal Server Error: error generating CSRF token\n" ,
2020-11-04 00:17:38 +00:00
} ,
{
2021-04-09 00:28:01 +00:00
name : "error while generating nonce using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2020-11-04 00:17:38 +00:00
generatePKCE : happyPKCEGenerator ,
2021-04-08 00:05:25 +00:00
generateNonce : sadNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 00:17:38 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusInternalServerError ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Internal Server Error: error generating nonce param\n" ,
} ,
{
2021-04-09 00:28:01 +00:00
name : "error while generating PKCE using OIDC upstream" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-11 01:58:00 +00:00
generateCSRF : happyCSRFGenerator ,
2021-04-08 00:05:25 +00:00
generatePKCE : sadPKCEGenerator ,
2020-11-04 00:17:38 +00:00
generateNonce : happyNonceGenerator ,
2020-11-12 23:36:59 +00:00
stateEncoder : happyStateEncoder ,
cookieEncoder : happyCookieEncoder ,
2020-11-04 00:17:38 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusInternalServerError ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Internal Server Error: error generating PKCE param\n" ,
} ,
{
name : "no upstream providers are configured" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( ) . Build ( ) , // empty
2020-11-04 00:17:38 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusUnprocessableEntity ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Unprocessable Entity: No upstream providers are configured\n" ,
} ,
{
2021-04-09 00:28:01 +00:00
name : "too many upstream providers are configured: multiple OIDC" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider , & upstreamOIDCIdentityProvider ) . Build ( ) , // more than one not allowed
2020-11-04 00:17:38 +00:00
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusUnprocessableEntity ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n" ,
} ,
2021-04-09 00:28:01 +00:00
{
name : "too many upstream providers are configured: multiple LDAP" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithLDAP ( & upstreamLDAPIdentityProvider , & upstreamLDAPIdentityProvider ) . Build ( ) , // more than one not allowed
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusUnprocessableEntity ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n" ,
} ,
{
name : "too many upstream providers are configured: both OIDC and LDAP" ,
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . WithLDAP ( & upstreamLDAPIdentityProvider ) . Build ( ) , // more than one not allowed
method : http . MethodGet ,
path : happyGetRequestPath ,
wantStatus : http . StatusUnprocessableEntity ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n" ,
} ,
2020-11-04 00:17:38 +00:00
{
name : "PUT is a bad method" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-04 00:17:38 +00:00
method : http . MethodPut ,
path : "/some/path" ,
wantStatus : http . StatusMethodNotAllowed ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Method Not Allowed: PUT (try GET or POST)\n" ,
} ,
{
name : "PATCH is a bad method" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-04 00:17:38 +00:00
method : http . MethodPatch ,
path : "/some/path" ,
wantStatus : http . StatusMethodNotAllowed ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Method Not Allowed: PATCH (try GET or POST)\n" ,
} ,
{
name : "DELETE is a bad method" ,
2021-04-07 23:12:13 +00:00
idpLister : oidctestutil . NewUpstreamIDPListerBuilder ( ) . WithOIDC ( & upstreamOIDCIdentityProvider ) . Build ( ) ,
2020-11-04 00:17:38 +00:00
method : http . MethodDelete ,
path : "/some/path" ,
wantStatus : http . StatusMethodNotAllowed ,
wantContentType : "text/plain; charset=utf-8" ,
wantBodyString : "Method Not Allowed: DELETE (try GET or POST)\n" ,
} ,
}
2021-04-09 00:28:01 +00:00
runOneTestCase := func ( t * testing . T , test testCase , subject http . Handler , kubeOauthStore * oidc . KubeStorage , kubeClient * fake . Clientset , secretsClient v1 . SecretInterface ) {
2020-11-04 00:17:38 +00:00
req := httptest . NewRequest ( test . method , test . path , strings . NewReader ( test . body ) )
2020-11-04 15:15:19 +00:00
req . Header . Set ( "Content-Type" , test . contentType )
2020-11-12 23:36:59 +00:00
if test . csrfCookie != "" {
req . Header . Set ( "Cookie" , test . csrfCookie )
}
2021-04-09 00:28:01 +00:00
if test . customUsernameHeader != nil {
2021-04-27 19:43:09 +00:00
req . Header . Set ( "X-Pinniped-Idp-Username" , * test . customUsernameHeader )
2021-04-09 00:28:01 +00:00
}
if test . customPasswordHeader != nil {
2021-04-27 19:43:09 +00:00
req . Header . Set ( "X-Pinniped-Idp-Password" , * test . customPasswordHeader )
2021-04-09 00:28:01 +00:00
}
2020-11-04 00:17:38 +00:00
rsp := httptest . NewRecorder ( )
subject . ServeHTTP ( rsp , req )
2020-11-19 14:28:56 +00:00
t . Logf ( "response: %#v" , rsp )
t . Logf ( "response body: %q" , rsp . Body . String ( ) )
2020-11-04 00:17:38 +00:00
require . Equal ( t , test . wantStatus , rsp . Code )
2020-12-04 15:06:55 +00:00
testutil . RequireEqualContentType ( t , rsp . Header ( ) . Get ( "Content-Type" ) , test . wantContentType )
2020-12-14 23:28:32 +00:00
testutil . RequireSecurityHeaders ( t , rsp )
2020-11-04 00:17:38 +00:00
2020-11-12 17:13:58 +00:00
actualLocation := rsp . Header ( ) . Get ( "Location" )
2021-04-09 00:28:01 +00:00
switch {
case test . wantLocationHeader != "" :
2020-11-11 20:29:14 +00:00
if test . wantUpstreamStateParamInLocationHeader {
2020-11-12 23:36:59 +00:00
requireEqualDecodedStateParams ( t , actualLocation , test . wantLocationHeader , test . stateEncoder )
2020-11-11 20:29:14 +00:00
}
2020-11-12 17:13:58 +00:00
// The upstream state param is encoded using a timestamp at the beginning so we don't want to
// compare those states since they may be different, but we do want to compare the downstream
// state param that should be exactly the same.
requireEqualURLs ( t , actualLocation , test . wantLocationHeader , test . wantUpstreamStateParamInLocationHeader )
2021-04-09 00:28:01 +00:00
// Authorization requests for either a successful OIDC upstream or for an error with any upstream
// should never use Kube storage. There is only one exception to this rule, which is that certain
// OIDC validations are checked in fosite after the OAuth authcode (and sometimes the OIDC session)
// is stored, so it is possible with an LDAP upstream to store objects and then return an error to
// the client anyway (which makes the stored objects useless, but oh well).
require . Len ( t , kubeClient . Actions ( ) , test . wantUnnecessaryStoredRecords )
case test . wantRedirectLocationRegexp != "" :
require . Len ( t , rsp . Header ( ) . Values ( "Location" ) , 1 )
oidctestutil . RequireAuthcodeRedirectLocation (
t ,
rsp . Header ( ) . Get ( "Location" ) ,
test . wantRedirectLocationRegexp ,
kubeClient ,
secretsClient ,
kubeOauthStore ,
test . wantDownstreamGrantedScopes ,
test . wantDownstreamIDTokenSubject ,
test . wantDownstreamIDTokenUsername ,
test . wantDownstreamIDTokenGroups ,
test . wantDownstreamRequestedScopes ,
test . wantDownstreamPKCEChallenge ,
test . wantDownstreamPKCEChallengeMethod ,
test . wantDownstreamNonce ,
downstreamClientID ,
test . wantDownstreamRedirectURI ,
)
default :
2020-11-11 01:58:00 +00:00
require . Empty ( t , rsp . Header ( ) . Values ( "Location" ) )
}
2020-11-12 17:24:40 +00:00
switch {
case test . wantBodyJSON != "" :
2020-11-11 20:29:14 +00:00
require . JSONEq ( t , test . wantBodyJSON , rsp . Body . String ( ) )
2020-11-12 17:24:40 +00:00
case test . wantBodyStringWithLocationInHref :
2020-11-12 17:13:58 +00:00
anchorTagWithLocationHref := fmt . Sprintf ( "<a href=\"%s\">Found</a>.\n\n" , html . EscapeString ( actualLocation ) )
require . Equal ( t , anchorTagWithLocationHref , rsp . Body . String ( ) )
2020-11-12 17:24:40 +00:00
default :
2020-11-11 20:29:14 +00:00
require . Equal ( t , test . wantBodyString , rsp . Body . String ( ) )
}
2020-11-12 23:36:59 +00:00
if test . wantCSRFValueInCookieHeader != "" {
2020-11-11 01:58:00 +00:00
require . Len ( t , rsp . Header ( ) . Values ( "Set-Cookie" ) , 1 )
actualCookie := rsp . Header ( ) . Get ( "Set-Cookie" )
2020-12-04 03:23:58 +00:00
regex := regexp . MustCompile ( "__Host-pinniped-csrf=([^;]+); Path=/; HttpOnly; Secure; SameSite=Lax" )
2020-11-12 23:36:59 +00:00
submatches := regex . FindStringSubmatch ( actualCookie )
require . Len ( t , submatches , 2 )
captured := submatches [ 1 ]
var decodedCSRFCookieValue string
err := test . cookieEncoder . Decode ( "csrf" , captured , & decodedCSRFCookieValue )
require . NoError ( t , err )
require . Equal ( t , test . wantCSRFValueInCookieHeader , decodedCSRFCookieValue )
2020-11-11 01:58:00 +00:00
} else {
require . Empty ( t , rsp . Header ( ) . Values ( "Set-Cookie" ) )
2020-11-04 00:17:38 +00:00
}
}
for _ , test := range tests {
test := test
t . Run ( test . name , func ( t * testing . T ) {
2021-04-09 00:28:01 +00:00
kubeClient := fake . NewSimpleClientset ( )
secretsClient := kubeClient . CoreV1 ( ) . Secrets ( "some-namespace" )
oauthHelperWithRealStorage , kubeOauthStore := createOauthHelperWithRealStorage ( secretsClient )
2021-04-08 00:05:25 +00:00
subject := NewHandler (
downstreamIssuer ,
test . idpLister ,
oauthHelperWithNullStorage , oauthHelperWithRealStorage ,
test . generateCSRF , test . generatePKCE , test . generateNonce ,
test . stateEncoder , test . cookieEncoder ,
)
2021-04-09 00:28:01 +00:00
runOneTestCase ( t , test , subject , kubeOauthStore , kubeClient , secretsClient )
2020-11-04 00:17:38 +00:00
} )
}
t . Run ( "allows upstream provider configuration to change between requests" , func ( t * testing . T ) {
test := tests [ 0 ]
2021-04-08 00:05:25 +00:00
require . Equal ( t , "OIDC upstream happy path using GET without a CSRF cookie" , test . name ) // re-use the happy path test case
2021-04-09 00:28:01 +00:00
kubeClient := fake . NewSimpleClientset ( )
secretsClient := kubeClient . CoreV1 ( ) . Secrets ( "some-namespace" )
oauthHelperWithRealStorage , kubeOauthStore := createOauthHelperWithRealStorage ( secretsClient )
2021-04-08 00:05:25 +00:00
subject := NewHandler (
downstreamIssuer ,
test . idpLister ,
oauthHelperWithNullStorage , oauthHelperWithRealStorage ,
test . generateCSRF , test . generatePKCE , test . generateNonce ,
test . stateEncoder , test . cookieEncoder ,
)
2020-11-04 00:17:38 +00:00
2021-04-09 00:28:01 +00:00
runOneTestCase ( t , test , subject , kubeOauthStore , kubeClient , secretsClient )
2020-11-04 00:17:38 +00:00
// Call the setter to change the upstream IDP settings.
2020-11-20 01:57:07 +00:00
newProviderSettings := oidctestutil . TestUpstreamOIDCIdentityProvider {
2020-11-04 00:17:38 +00:00
Name : "some-other-idp" ,
ClientID : "some-other-client-id" ,
AuthorizationURL : * upstreamAuthURL ,
Scopes : [ ] string { "other-scope1" , "other-scope2" } ,
}
2021-04-07 23:12:13 +00:00
test . idpLister . SetOIDCIdentityProviders ( [ ] provider . UpstreamOIDCIdentityProviderI { provider . UpstreamOIDCIdentityProviderI ( & newProviderSettings ) } )
2020-11-04 00:17:38 +00:00
// Update the expectations of the test case to match the new upstream IDP settings.
2020-11-04 17:58:40 +00:00
test . wantLocationHeader = urlWithQuery ( upstreamAuthURL . String ( ) ,
map [ string ] string {
"response_type" : "code" ,
"access_type" : "offline" ,
"scope" : "other-scope1 other-scope2" ,
"client_id" : "some-other-client-id" ,
2020-11-20 21:14:45 +00:00
"state" : expectedUpstreamStateParam ( nil , "" , newProviderSettings . Name ) ,
2020-11-11 01:58:00 +00:00
"nonce" : happyNonce ,
2020-11-04 20:19:07 +00:00
"code_challenge" : expectedUpstreamCodeChallenge ,
2021-04-09 00:28:01 +00:00
"code_challenge_method" : downstreamPKCEChallengeMethod ,
2020-11-20 21:14:45 +00:00
"redirect_uri" : downstreamIssuer + "/callback" ,
2020-11-04 17:58:40 +00:00
} ,
2020-11-04 00:17:38 +00:00
)
2020-11-04 20:19:07 +00:00
test . wantBodyString = fmt . Sprintf ( ` <a href="%s">Found</a>.%s ` ,
html . EscapeString ( test . wantLocationHeader ) ,
"\n\n" ,
)
2020-11-04 00:17:38 +00:00
// Run again on the same instance of the subject with the modified upstream IDP settings and the
// modified expectations. This should ensure that the implementation is using the in-memory cache
// of upstream IDP settings appropriately in terms of always getting the values from the cache
// on every request.
2021-04-09 00:28:01 +00:00
runOneTestCase ( t , test , subject , kubeOauthStore , kubeClient , secretsClient )
2020-11-04 00:17:38 +00:00
} )
}
2020-11-11 20:29:14 +00:00
type errorReturningEncoder struct {
2020-11-16 19:41:00 +00:00
oidc . Codec
2020-11-11 20:29:14 +00:00
}
func ( * errorReturningEncoder ) Encode ( _ string , _ interface { } ) ( string , error ) {
return "" , fmt . Errorf ( "some encoding error" )
}
2020-11-16 19:41:00 +00:00
func requireEqualDecodedStateParams ( t * testing . T , actualURL string , expectedURL string , stateParamDecoder oidc . Codec ) {
2020-11-11 20:29:14 +00:00
t . Helper ( )
actualLocationURL , err := url . Parse ( actualURL )
require . NoError ( t , err )
expectedLocationURL , err := url . Parse ( expectedURL )
require . NoError ( t , err )
expectedQueryStateParam := expectedLocationURL . Query ( ) . Get ( "state" )
require . NotEmpty ( t , expectedQueryStateParam )
2020-11-20 01:57:07 +00:00
var expectedDecodedStateParam oidctestutil . ExpectedUpstreamStateParamFormat
2020-11-11 20:29:14 +00:00
err = stateParamDecoder . Decode ( "s" , expectedQueryStateParam , & expectedDecodedStateParam )
require . NoError ( t , err )
actualQueryStateParam := actualLocationURL . Query ( ) . Get ( "state" )
require . NotEmpty ( t , actualQueryStateParam )
2020-11-20 01:57:07 +00:00
var actualDecodedStateParam oidctestutil . ExpectedUpstreamStateParamFormat
2020-11-11 20:29:14 +00:00
err = stateParamDecoder . Decode ( "s" , actualQueryStateParam , & actualDecodedStateParam )
require . NoError ( t , err )
require . Equal ( t , expectedDecodedStateParam , actualDecodedStateParam )
}
2020-11-12 17:13:58 +00:00
func requireEqualURLs ( t * testing . T , actualURL string , expectedURL string , ignoreState bool ) {
2020-11-04 15:15:19 +00:00
t . Helper ( )
2020-11-04 00:17:38 +00:00
actualLocationURL , err := url . Parse ( actualURL )
require . NoError ( t , err )
expectedLocationURL , err := url . Parse ( expectedURL )
require . NoError ( t , err )
2020-11-20 21:56:35 +00:00
require . Equal ( t , expectedLocationURL . Scheme , actualLocationURL . Scheme ,
"schemes were not equal: expected %s but got %s" , expectedURL , actualURL ,
)
require . Equal ( t , expectedLocationURL . User , actualLocationURL . User ,
"users were not equal: expected %s but got %s" , expectedURL , actualURL ,
)
require . Equal ( t , expectedLocationURL . Host , actualLocationURL . Host ,
"hosts were not equal: expected %s but got %s" , expectedURL , actualURL ,
)
require . Equal ( t , expectedLocationURL . Path , actualLocationURL . Path ,
"paths were not equal: expected %s but got %s" , expectedURL , actualURL ,
)
2020-11-12 17:13:58 +00:00
expectedLocationQuery := expectedLocationURL . Query ( )
actualLocationQuery := actualLocationURL . Query ( )
// Let the caller ignore the state, since it may contain a digest at the end that is difficult to
// predict because it depends on a time.Now() timestamp.
if ignoreState {
expectedLocationQuery . Del ( "state" )
actualLocationQuery . Del ( "state" )
}
require . Equal ( t , expectedLocationQuery , actualLocationQuery )
2020-11-04 00:17:38 +00:00
}
2021-04-09 00:28:01 +00:00
func stringPtr ( s string ) * string {
return & s
}