Files
pinniped/internal/federationdomain/endpoints/chooseidp/choose_idp_handler_test.go
T
Ryan Richard 1bc13e94f7 Refactor to extract interface for upstream IDP interactions
Create an interface to abstract the upstream IDP from the
authorize, IDP discovery, callback, choose IDP, and login
endpoints. This commit does not refactor the token endpoint,
which will be refactored in a similar way in the next commit.
2024-02-20 09:26:34 -08:00

152 lines
6.7 KiB
Go

// Copyright 2023-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package chooseidp
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/federationdomain/endpoints/chooseidp/chooseidphtml"
"go.pinniped.dev/internal/federationdomain/federationdomainproviders"
"go.pinniped.dev/internal/federationdomain/oidc"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/internal/testutil/oidctestutil"
"go.pinniped.dev/internal/testutil/testidplister"
)
func TestChooseIDPHandler(t *testing.T) {
const testIssuer = "https://pinniped.dev/issuer"
testReqQuery := url.Values{
"client_id": []string{"foo"},
"redirect_uri": []string{"bar"},
"scope": []string{"baz"},
"response_type": []string{"bat"},
}
testIssuerWithTestReqQuery := testIssuer + "?" + testReqQuery.Encode()
tests := []struct {
name string
method string
reqTarget string
idps federationdomainproviders.FederationDomainIdentityProvidersListerI
wantStatus int
wantContentType string
wantBodyString string
}{
{
name: "happy path",
method: http.MethodGet,
reqTarget: "/some/path" + oidc.ChooseIDPEndpointPath + "?" + testReqQuery.Encode(),
idps: testidplister.NewUpstreamIDPListerBuilder().
WithOIDC(oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().WithName("oidc2").Build()).
WithLDAP(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().WithName("ldap1").Build()).
WithActiveDirectory(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().WithName("z-ad1").Build()).
WithLDAP(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().WithName("ldap2").Build()).
WithOIDC(oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().WithName("oidc1").Build()).
WithActiveDirectory(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().WithName("ad2").Build()).
BuildFederationDomainIdentityProvidersListerFinder(),
wantStatus: http.StatusOK,
wantContentType: "text/html; charset=utf-8",
wantBodyString: testutil.ExpectedChooseIDPPageHTML(chooseidphtml.CSS(), chooseidphtml.JS(), []testutil.ChooseIDPPageExpectedValue{
// Should be sorted alphabetically by displayName.
{DisplayName: "ad2", URL: testIssuerWithTestReqQuery + "&pinniped_idp_name=ad2"},
{DisplayName: "ldap1", URL: testIssuerWithTestReqQuery + "&pinniped_idp_name=ldap1"},
{DisplayName: "ldap2", URL: testIssuerWithTestReqQuery + "&pinniped_idp_name=ldap2"},
{DisplayName: "oidc1", URL: testIssuerWithTestReqQuery + "&pinniped_idp_name=oidc1"},
{DisplayName: "oidc2", URL: testIssuerWithTestReqQuery + "&pinniped_idp_name=oidc2"},
{DisplayName: "z-ad1", URL: testIssuerWithTestReqQuery + "&pinniped_idp_name=z-ad1"},
}),
},
{
name: "happy path when there are special characters in the IDP name",
method: http.MethodGet,
reqTarget: "/some/path" + oidc.ChooseIDPEndpointPath + "?" + testReqQuery.Encode(),
idps: testidplister.NewUpstreamIDPListerBuilder().
WithOIDC(oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().WithName(`This is Ryan's IDP 👍\~!@#$%^&*()-+[]{}\|;'"<>,.?`).Build()).
WithLDAP(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().WithName(`This is Josh's IDP 🦭`).Build()).
BuildFederationDomainIdentityProvidersListerFinder(),
wantStatus: http.StatusOK,
wantContentType: "text/html; charset=utf-8",
wantBodyString: testutil.ExpectedChooseIDPPageHTML(chooseidphtml.CSS(), chooseidphtml.JS(), []testutil.ChooseIDPPageExpectedValue{
// Should be sorted alphabetically by displayName.
{
DisplayName: `This is Josh's IDP 🦭`,
URL: testIssuerWithTestReqQuery + `&pinniped_idp_name=` + url.QueryEscape(`This is Josh's IDP 🦭`),
},
{
DisplayName: `This is Ryan's IDP 👍\~!@#$%^&*()-+[]{}\|;'"<>,.?`,
URL: testIssuerWithTestReqQuery + `&pinniped_idp_name=` + url.QueryEscape(`This is Ryan's IDP 👍\~!@#$%^&*()-+[]{}\|;'"<>,.?`),
},
}),
},
{
name: "no valid IDPs are configured on the FederationDomain",
method: http.MethodGet,
reqTarget: "/some/path" + oidc.ChooseIDPEndpointPath + "?" + testReqQuery.Encode(),
idps: testidplister.NewUpstreamIDPListerBuilder().
BuildFederationDomainIdentityProvidersListerFinder(),
wantStatus: http.StatusInternalServerError,
wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Internal Server Error: please check the server's configuration: no valid identity providers found for this FederationDomain\n",
},
{
name: "no query params on the request",
method: http.MethodGet,
reqTarget: "/some/path" + oidc.ChooseIDPEndpointPath,
idps: testidplister.NewUpstreamIDPListerBuilder().
WithOIDC(oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().WithName("x-some-idp").Build()).
BuildFederationDomainIdentityProvidersListerFinder(),
wantStatus: http.StatusBadRequest,
wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Bad Request: missing required query params (must include client_id, redirect_uri, scope, and response_type)\n",
},
{
name: "missing required query param(s) on the request",
method: http.MethodGet,
reqTarget: "/some/path" + oidc.ChooseIDPEndpointPath + "?client_id=foo",
idps: testidplister.NewUpstreamIDPListerBuilder().
WithOIDC(oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().WithName("x-some-idp").Build()).
BuildFederationDomainIdentityProvidersListerFinder(),
wantStatus: http.StatusBadRequest,
wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Bad Request: missing required query params (must include client_id, redirect_uri, scope, and response_type)\n",
},
{
name: "bad request method",
method: http.MethodPost,
reqTarget: oidc.ChooseIDPEndpointPath,
idps: testidplister.NewUpstreamIDPListerBuilder().
WithOIDC(oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().WithName("x-some-idp").Build()).
BuildFederationDomainIdentityProvidersListerFinder(),
wantStatus: http.StatusMethodNotAllowed,
wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Method Not Allowed: POST (try GET)\n",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
handler := NewHandler(testIssuer, test.idps)
req := httptest.NewRequest(test.method, test.reqTarget, nil)
rsp := httptest.NewRecorder()
handler.ServeHTTP(rsp, req)
require.Equal(t, test.wantStatus, rsp.Code)
require.Equal(t, test.wantContentType, rsp.Header().Get("Content-Type"))
require.Equal(t, test.wantBodyString, rsp.Body.String())
testutil.RequireSecurityHeadersWithIDPChooserPageCSPs(t, rsp)
})
}
}