From e8edb465f49540cee1689af60218da43a7e7c73e Mon Sep 17 00:00:00 2001 From: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:32:29 -0700 Subject: [PATCH] authenticate: apply branding to sign out pages (#5044) Add support for the Enterprise branding options to the sign_out and signed_out page handlers. --- authenticate/handlers.go | 12 +++++++-- authenticate/handlers_test.go | 46 ++++++++++++++++++++++++++++++++++ internal/handlers/signedout.go | 8 ++++-- internal/handlers/signout.go | 7 ++++-- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 97a76e1f6..61b51f6f0 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -89,7 +89,7 @@ func (a *Authenticate) mountDashboard(r *mux.Router) { // routes that don't need a session: sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut)) - sr.Path("/signed_out").Handler(handlers.SignedOut(handlers.SignedOutData{})).Methods(http.MethodGet) + sr.Path("/signed_out").Handler(httputil.HandlerFunc(a.signedOut)).Methods(http.MethodGet) // routes that need a session: sr = sr.NewRoute().Subrouter() @@ -186,7 +186,8 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) error { } handlers.SignOutConfirm(handlers.SignOutConfirmData{ - URL: urlutil.SignOutURL(r, authenticateURL, a.state.Load().sharedKey), + URL: urlutil.SignOutURL(r, authenticateURL, a.state.Load().sharedKey), + BrandingOptions: a.options.Load().BrandingOptions, }).ServeHTTP(w, r) return nil } @@ -240,6 +241,13 @@ func (a *Authenticate) signOutRedirect(w http.ResponseWriter, r *http.Request) e return nil } +func (a *Authenticate) signedOut(w http.ResponseWriter, r *http.Request) error { + handlers.SignedOut(handlers.SignedOutData{ + BrandingOptions: a.options.Load().BrandingOptions, + }).ServeHTTP(w, r) + return nil +} + // reauthenticateOrFail starts the authenticate process by redirecting the // user to their respective identity provider. This function also builds the // 'state' parameter which is encrypted and includes authenticating data diff --git a/authenticate/handlers_test.go b/authenticate/handlers_test.go index 4d81168ce..d4b5f22da 100644 --- a/authenticate/handlers_test.go +++ b/authenticate/handlers_test.go @@ -16,9 +16,11 @@ import ( "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/oauth2" "google.golang.org/grpc" + "google.golang.org/protobuf/proto" "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/atomicutil" @@ -33,6 +35,7 @@ import ( "github.com/pomerium/pomerium/internal/testutil" "github.com/pomerium/pomerium/internal/urlutil" "github.com/pomerium/pomerium/pkg/cryptutil" + configproto "github.com/pomerium/pomerium/pkg/grpc/config" "github.com/pomerium/pomerium/pkg/grpc/databroker" ) @@ -619,6 +622,49 @@ func TestAuthenticate_CORS(t *testing.T) { }) } +func TestSignOutBranding(t *testing.T) { + t.Parallel() + + auth := testAuthenticate() + auth.state.Load().flow.(*stubFlow).verifySignatureErr = errors.New("unsigned URL") + auth.options.Store(&config.Options{ + BrandingOptions: &configproto.Settings{ + PrimaryColor: proto.String("red"), + SecondaryColor: proto.String("orange"), + }, + }) + + t.Run("sign_out", func(t *testing.T) { + t.Parallel() + + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/.pomerium/sign_out", nil) + err := auth.SignOut(w, r) + require.NoError(t, err) + require.Equal(t, http.StatusOK, w.Code) + + b, err := io.ReadAll(w.Body) + require.NoError(t, err) + + assert.Contains(t, string(b), `"primaryColor":"red","secondaryColor":"orange"`) + }) + + t.Run("signed_out", func(t *testing.T) { + t.Parallel() + + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/.pomerium/signed_out", nil) + err := auth.signedOut(w, r) + require.NoError(t, err) + require.Equal(t, http.StatusOK, w.Code) + + b, err := io.ReadAll(w.Body) + require.NoError(t, err) + + assert.Contains(t, string(b), `"primaryColor":"red","secondaryColor":"orange"`) + }) +} + type mockDataBrokerServiceClient struct { databroker.DataBrokerServiceClient diff --git a/internal/handlers/signedout.go b/internal/handlers/signedout.go index 0914220e8..818e6f049 100644 --- a/internal/handlers/signedout.go +++ b/internal/handlers/signedout.go @@ -8,11 +8,15 @@ import ( ) // SignedOutData is the data for the SignedOut page. -type SignedOutData struct{} +type SignedOutData struct { + BrandingOptions httputil.BrandingOptions +} // ToJSON converts the data into a JSON map. func (data SignedOutData) ToJSON() map[string]interface{} { - return map[string]interface{}{} + m := map[string]interface{}{} + httputil.AddBrandingOptionsToMap(m, data.BrandingOptions) + return m } // SignedOut returns a handler that renders the signed out page. diff --git a/internal/handlers/signout.go b/internal/handlers/signout.go index dfe37735b..14cb109ef 100644 --- a/internal/handlers/signout.go +++ b/internal/handlers/signout.go @@ -9,14 +9,17 @@ import ( // SignOutConfirmData is the data for the SignOutConfirm page. type SignOutConfirmData struct { - URL string + URL string + BrandingOptions httputil.BrandingOptions } // ToJSON converts the data into a JSON map. func (data SignOutConfirmData) ToJSON() map[string]interface{} { - return map[string]interface{}{ + m := map[string]interface{}{ "url": data.URL, } + httputil.AddBrandingOptionsToMap(m, data.BrandingOptions) + return m } // SignOutConfirm returns a handler that renders the sign out confirm page.