From 1949479ca5ddee09b3434bd98d395f1e4146da0d Mon Sep 17 00:00:00 2001 From: Geraint Edwards Date: Tue, 23 Jul 2024 15:44:41 +0100 Subject: [PATCH] ensure cfg.SiteDomain ends any URL we redirect to --- .golangci.yml | 2 +- Makefile | 5 ++- ci/build.yml | 2 +- ci/component.yml | 2 +- ci/lint.yml | 2 +- ci/scripts/build.sh | 2 ++ ci/scripts/lint.sh | 2 ++ ci/scripts/unit.sh | 2 ++ ci/unit.yml | 2 +- config/config.go | 33 +++++++++++++++--- config/config_test.go | 31 ++++++++++++++++- handlers/feedback.go | 72 ++++++++++++++++----------------------- handlers/feedback_test.go | 57 ++++++++++++++++++++++++------- mapper/mapper.go | 12 +++++-- 14 files changed, 153 insertions(+), 73 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a184b86..4207d57 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,7 +8,7 @@ run: timeout: 5m linters-settings: govet: - check-shadowing: true + shadow: true golint: min-confidence: 0 gocyclo: diff --git a/Makefile b/Makefile index d2ccaa4..ecaef65 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,6 @@ build: generate-prod .PHONY: lint lint: - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2 golangci-lint run ./... .PHONY: debug @@ -42,13 +41,13 @@ convey: .PHONY: generate-debug generate-debug: fetch-renderer-lib - cd assets; go run github.com/kevinburke/go-bindata/go-bindata -prefix $(CORE_ASSETS_PATH)/assets -debug -o data.go -pkg assets locales/... templates/... $(CORE_ASSETS_PATH)/assets/locales/... $(CORE_ASSETS_PATH)/assets/templates/... + cd assets; go-bindata -prefix $(CORE_ASSETS_PATH)/assets -debug -o data.go -pkg assets locales/... templates/... $(CORE_ASSETS_PATH)/assets/locales/... $(CORE_ASSETS_PATH)/assets/templates/... { echo "// +build debug\n"; cat assets/data.go; } > assets/debug.go.new mv assets/debug.go.new assets/data.go .PHONY: generate-prod generate-prod: fetch-renderer-lib - cd assets; go run github.com/kevinburke/go-bindata/go-bindata -prefix $(CORE_ASSETS_PATH)/assets -o data.go -pkg assets locales/... templates/... $(CORE_ASSETS_PATH)/assets/locales/... $(CORE_ASSETS_PATH)/assets/templates/... + cd assets; go-bindata -prefix $(CORE_ASSETS_PATH)/assets -o data.go -pkg assets locales/... templates/... $(CORE_ASSETS_PATH)/assets/locales/... $(CORE_ASSETS_PATH)/assets/templates/... { echo "// +build production\n"; cat assets/data.go; } > assets/data.go.new mv assets/data.go.new assets/data.go diff --git a/ci/build.yml b/ci/build.yml index fe15890..da22a70 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -6,7 +6,7 @@ image_resource: type: docker-image source: repository: golang - tag: 1.22.2-bullseye + tag: 1.22.5-bullseye inputs: - name: dp-frontend-feedback-controller diff --git a/ci/component.yml b/ci/component.yml index 0c00241..9435cd8 100644 --- a/ci/component.yml +++ b/ci/component.yml @@ -6,7 +6,7 @@ image_resource: type: docker-image source: repository: golang - tag: 1.22.2-bullseye + tag: 1.22.5-bullseye inputs: - name: dp-frontend-feedback-controller diff --git a/ci/lint.yml b/ci/lint.yml index 8843934..d0360f6 100644 --- a/ci/lint.yml +++ b/ci/lint.yml @@ -6,7 +6,7 @@ image_resource: type: docker-image source: repository: golang - tag: 1.22.2-bullseye + tag: 1.22.5-bullseye inputs: - name: dp-frontend-feedback-controller diff --git a/ci/scripts/build.sh b/ci/scripts/build.sh index 824d7c2..9d12a66 100755 --- a/ci/scripts/build.sh +++ b/ci/scripts/build.sh @@ -1,5 +1,7 @@ #!/bin/bash -eux +go install github.com/kevinburke/go-bindata/v4/...@v4.0.2 + pushd dp-frontend-feedback-controller make build cp build/dp-frontend-feedback-controller Dockerfile.concourse ../build diff --git a/ci/scripts/lint.sh b/ci/scripts/lint.sh index b0013ca..e9e85b7 100755 --- a/ci/scripts/lint.sh +++ b/ci/scripts/lint.sh @@ -1,5 +1,7 @@ #!/bin/bash -eux +go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1 + pushd dp-frontend-feedback-controller make lint popd diff --git a/ci/scripts/unit.sh b/ci/scripts/unit.sh index a19a3d4..7ab6ecb 100755 --- a/ci/scripts/unit.sh +++ b/ci/scripts/unit.sh @@ -1,5 +1,7 @@ #!/bin/bash -eux +go install github.com/kevinburke/go-bindata/v4/...@v4.0.2 + pushd dp-frontend-feedback-controller make test popd diff --git a/ci/unit.yml b/ci/unit.yml index 71e8b95..c2a9ea2 100644 --- a/ci/unit.yml +++ b/ci/unit.yml @@ -6,7 +6,7 @@ image_resource: type: docker-image source: repository: golang - tag: 1.22.2-bullseye + tag: 1.22.5-bullseye inputs: - name: dp-frontend-feedback-controller diff --git a/config/config.go b/config/config.go index 0aa878f..3a3fe95 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,8 @@ package config import ( + "net/url" + "strings" "time" "github.com/kelseyhightower/envconfig" @@ -40,6 +42,10 @@ var cfg *Config // Get returns the default config with any modifications through environment // variables func Get() (*Config, error) { + if cfg != nil { + return cfg, nil + } + envCfg, err := get() if err != nil { return nil, err @@ -50,14 +56,12 @@ func Get() (*Config, error) { } else { envCfg.PatternLibraryAssetsPath = "//cdn.ons.gov.uk/dp-design-system/e0a75c3" } - return envCfg, nil + + cfg = envCfg + return cfg, nil } func get() (*Config, error) { - if cfg != nil { - return cfg, nil - } - cfg := &Config{ APIRouterURL: "http://localhost:23200/v1", BindAddr: "localhost:25200", @@ -86,3 +90,22 @@ func get() (*Config, error) { return cfg, envconfig.Process("", cfg) } + +// IsSiteDomainURL is true when urlString is a URL and its host ends with `.`+siteDomain (when siteDomain is blank, uses cfg.SiteDomain) +func IsSiteDomainURL(urlString, siteDomain string) bool { + if urlString == "" { + return false + } + if siteDomain == "" { + siteDomain = cfg.SiteDomain + } + urlObject, err := url.ParseRequestURI(urlString) + if err != nil { + return false + } + hostName := urlObject.Hostname() + if hostName != siteDomain && !strings.HasSuffix(hostName, "."+siteDomain) { + return false + } + return true +} diff --git a/config/config_test.go b/config/config_test.go index 19f7d3a..98f41c4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -9,8 +9,8 @@ import ( func TestConfig(t *testing.T) { Convey("Given an environment with no environment variables set", t, func() { - cfg, err := Get() Convey("When the config values are retrieved", func() { + cfg, err := Get() Convey("Then there should be no error returned", func() { So(err, ShouldBeNil) }) @@ -40,6 +40,35 @@ func TestConfig(t *testing.T) { So(newErr, ShouldBeNil) So(newCfg, ShouldResemble, cfg) }) + Convey("Then a sub-domain off an explicit site domain is recognised", func() { + isAllowedURL := IsSiteDomainURL("https://anything.ons.gov.uk:443/ook", "ons.gov.uk") + So(isAllowedURL, ShouldBeTrue) + }) + Convey("Then a non-site domain URL is not recognised for explicit site domain", func() { + isAllowedURL := IsSiteDomainURL("https://anything.example.com", "ons.gov.uk") + So(isAllowedURL, ShouldBeFalse) + }) + + Convey("Then a non-URL is not recognised for explicit site domain", func() { + isAllowedURL := IsSiteDomainURL("blah", "ons.gov.uk") + So(isAllowedURL, ShouldBeFalse) + }) + Convey("Then a URL of the config's site domain is recognised", func() { + isAllowedURL := IsSiteDomainURL("https://localhost", "") + So(isAllowedURL, ShouldBeTrue) + }) + Convey("Then a sub-domain/host of the config's site domain is recognised", func() { + isAllowedURL := IsSiteDomainURL("https://anything.localhost", "") + So(isAllowedURL, ShouldBeTrue) + }) + Convey("Then a non-site domain URL is not recognised for config's site domain", func() { + isAllowedURL := IsSiteDomainURL("https://not-site-domain.example.com", "") + So(isAllowedURL, ShouldBeFalse) + }) + Convey("Then a non-URL is not recognised for config's site domain", func() { + isAllowedURL := IsSiteDomainURL("blah", "") + So(isAllowedURL, ShouldBeFalse) + }) }) }) } diff --git a/handlers/feedback.go b/handlers/feedback.go index e67d349..8236d01 100644 --- a/handlers/feedback.go +++ b/handlers/feedback.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "net/http" - "net/url" "regexp" "strings" @@ -24,25 +23,16 @@ import ( // FeedbackThanks loads the Feedback Thank you page func (f *Feedback) FeedbackThanks() http.HandlerFunc { return dphandlers.ControllerHandler(func(w http.ResponseWriter, req *http.Request, lang, collectionID, accessToken string) { - feedbackThanks(w, req, req.Referer(), f.Render, f.CacheService, lang) + feedbackThanks(w, req, req.Referer(), f.Render, f.CacheService, lang, f.Config.SiteDomain, f.Config.EnableNewNavBar) }) } -func feedbackThanks(w http.ResponseWriter, req *http.Request, uri string, rend interfaces.Renderer, cacheHelperService *cacheHelper.Helper, lang string) { - ctx := req.Context() - var wholeSite string - - cfg, err := config.Get() - if err != nil { - log.Warn(ctx, "Unable to retrieve configuration", log.FormatErrors([]error{err})) - } else { - wholeSite = cfg.SiteDomain - } - +func feedbackThanks(w http.ResponseWriter, req *http.Request, uri string, rend interfaces.Renderer, cacheHelperService *cacheHelper.Helper, lang, siteDomain string, enableNewNavBar bool) { basePage := rend.NewBasePageModel() - p := mapper.CreateGetFeedbackThanks(req, basePage, lang, uri, wholeSite) + p := mapper.CreateGetFeedbackThanks(req, basePage, lang, uri, siteDomain) - if cfg.EnableNewNavBar { + if enableNewNavBar { + ctx := req.Context() mappedNavContent, err := cacheHelperService.GetMappedNavigationContent(ctx, lang) if err == nil { p.NavigationContent = mappedNavContent @@ -55,20 +45,16 @@ func feedbackThanks(w http.ResponseWriter, req *http.Request, uri string, rend i // GetFeedback handles the loading of a feedback page func (f *Feedback) GetFeedback() http.HandlerFunc { return dphandlers.ControllerHandler(func(w http.ResponseWriter, req *http.Request, lang, collectionID, accessToken string) { - getFeedback(w, req, []core.ErrorItem{}, model.FeedbackForm{URL: req.Referer()}, lang, f.Render, f.CacheService) + getFeedback(w, req, []core.ErrorItem{}, model.FeedbackForm{URL: req.Referer()}, lang, f.Render, f.CacheService, f.Config.EnableNewNavBar) }) } -func getFeedback(w http.ResponseWriter, req *http.Request, validationErrors []core.ErrorItem, ff model.FeedbackForm, lang string, rend interfaces.Renderer, cacheHelperService *cacheHelper.Helper) { +func getFeedback(w http.ResponseWriter, req *http.Request, validationErrors []core.ErrorItem, ff model.FeedbackForm, lang string, rend interfaces.Renderer, cacheHelperService *cacheHelper.Helper, enableNewNavBar bool) { basePage := rend.NewBasePageModel() p := mapper.CreateGetFeedback(req, basePage, validationErrors, ff, lang) - ctx := context.Background() - cfg, err := config.Get() - if err != nil { - log.Warn(ctx, "Unable to retrieve configuration", log.FormatErrors([]error{err})) - } - if cfg.EnableNewNavBar { + if enableNewNavBar { + ctx := context.Background() mappedNavContent, err := cacheHelperService.GetMappedNavigationContent(ctx, lang) if err == nil { p.NavigationContent = mappedNavContent @@ -81,11 +67,11 @@ func getFeedback(w http.ResponseWriter, req *http.Request, validationErrors []co // AddFeedback handles a users feedback request func (f *Feedback) AddFeedback() http.HandlerFunc { return dphandlers.ControllerHandler(func(w http.ResponseWriter, req *http.Request, lang, collectionID, accessToken string) { - addFeedback(w, req, f.Render, f.EmailSender, f.Config.FeedbackFrom, f.Config.FeedbackTo, lang, f.CacheService) + addFeedback(w, req, f.Render, f.EmailSender, f.Config.FeedbackFrom, f.Config.FeedbackTo, lang, f.Config.SiteDomain, f.CacheService) }) } -func addFeedback(w http.ResponseWriter, req *http.Request, rend interfaces.Renderer, emailSender email.Sender, from, to, lang string, cacheService *cacheHelper.Helper) { +func addFeedback(w http.ResponseWriter, req *http.Request, rend interfaces.Renderer, emailSender email.Sender, from, to, lang, siteDomain string, cacheService *cacheHelper.Helper) { ctx := req.Context() if err := req.ParseForm(); err != nil { log.Error(ctx, "unable to parse request form", err) @@ -103,9 +89,9 @@ func addFeedback(w http.ResponseWriter, req *http.Request, rend interfaces.Rende return } - validationErrors := validateForm(&ff) + validationErrors := validateForm(&ff, siteDomain) if len(validationErrors) > 0 { - getFeedback(w, req, validationErrors, ff, lang, rend, cacheService) + getFeedback(w, req, validationErrors, ff, lang, rend, cacheService, false) return } @@ -134,7 +120,7 @@ func addFeedback(w http.ResponseWriter, req *http.Request, rend interfaces.Rende } // validateForm is a helper function that validates a slice of FeedbackForm to determine if there are form validation errors -func validateForm(ff *model.FeedbackForm) (validationErrors []core.ErrorItem) { +func validateForm(ff *model.FeedbackForm, siteDomain string) (validationErrors []core.ErrorItem) { if ff.Type == "" && ff.FormLocation != "footer" { validationErrors = append(validationErrors, core.ErrorItem{ Description: core.Localisation{ @@ -147,28 +133,28 @@ func validateForm(ff *model.FeedbackForm) (validationErrors []core.ErrorItem) { } ff.URL = strings.TrimSpace(ff.URL) - if ff.Type == "A specific page" && ff.URL == "" { - validationErrors = append(validationErrors, core.ErrorItem{ - Description: core.Localisation{ - LocaleKey: "FeedbackWhatEnterURL", - Plural: 1, - }, - URL: "#type-error", - }) - ff.IsURLErr = true - } - - if ff.Type == "A specific page" && ff.URL != "" { - _, err := url.ParseRequestURI(ff.URL) - if err != nil { + if ff.Type == "A specific page" { + if ff.URL == "" { validationErrors = append(validationErrors, core.ErrorItem{ Description: core.Localisation{ - LocaleKey: "FeedbackValidURL", + LocaleKey: "FeedbackWhatEnterURL", Plural: 1, }, URL: "#type-error", }) ff.IsURLErr = true + + } else { + if !config.IsSiteDomainURL(ff.URL, siteDomain) { + validationErrors = append(validationErrors, core.ErrorItem{ + Description: core.Localisation{ + LocaleKey: "FeedbackValidURL", + Plural: 1, + }, + URL: "#type-error", + }) + ff.IsURLErr = true + } } } diff --git a/handlers/feedback_test.go b/handlers/feedback_test.go index 7f345b1..67d6ed2 100644 --- a/handlers/feedback_test.go +++ b/handlers/feedback_test.go @@ -13,6 +13,7 @@ import ( cacheClient "github.com/ONSdigital/dp-frontend-cache-helper/pkg/navigation/client" cacheHelper "github.com/ONSdigital/dp-frontend-cache-helper/pkg/navigation/helper" + "github.com/ONSdigital/dp-frontend-feedback-controller/config" "github.com/ONSdigital/dp-frontend-feedback-controller/email/emailtest" "github.com/ONSdigital/dp-frontend-feedback-controller/interfaces/interfacestest" "github.com/ONSdigital/dp-frontend-feedback-controller/mocks" @@ -24,6 +25,8 @@ import ( . "github.com/smartystreets/goconvey/convey" ) +const siteDomain = "ons.gov.uk" + func Test_getFeedback(t *testing.T) { helper.InitialiseLocalisationsHelper(mocks.MockAssetFunction) Convey("Given a valid request", t, func() { @@ -53,7 +56,7 @@ func Test_getFeedback(t *testing.T) { }, }} Convey("When getFeedback is called", func() { - getFeedback(w, req, []coreModel.ErrorItem{}, ff, lang, mockRenderer, mockNagivationCache) + getFeedback(w, req, []coreModel.ErrorItem{}, ff, lang, mockRenderer, mockNagivationCache, false) Convey("Then a 200 request is returned", func() { So(w.Code, ShouldEqual, http.StatusOK) }) @@ -98,7 +101,7 @@ func Test_addFeedback(t *testing.T) { }, }} Convey("When addFeedback is called", func() { - addFeedback(w, req, mockRenderer, mockSender, from, to, lang, mockNagivationCache) + addFeedback(w, req, mockRenderer, mockSender, from, to, lang, siteDomain, mockNagivationCache) Convey("Then the renderer is not called", func() { So(len(mockRenderer.BuildPageCalls()), ShouldEqual, 0) }) @@ -146,7 +149,7 @@ func Test_addFeedback(t *testing.T) { }, }} Convey("When addFeedback is called", func() { - addFeedback(w, req, mockRenderer, mockSender, from, to, lang, mockNagivationCache) + addFeedback(w, req, mockRenderer, mockSender, from, to, lang, siteDomain, mockNagivationCache) Convey("Then the renderer is not called", func() { So(len(mockRenderer.BuildPageCalls()), ShouldEqual, 0) }) @@ -194,7 +197,7 @@ func Test_addFeedback(t *testing.T) { }, }} Convey("When addFeedback is called", func() { - addFeedback(w, req, mockRenderer, mockSender, from, to, lang, mockNagivationCache) + addFeedback(w, req, mockRenderer, mockSender, from, to, lang, siteDomain, mockNagivationCache) Convey("Then the renderer is not called", func() { So(len(mockRenderer.BuildPageCalls()), ShouldEqual, 0) }) @@ -244,7 +247,7 @@ func Test_addFeedback(t *testing.T) { }, }} Convey("When addFeedback is called", func() { - addFeedback(w, req, mockRenderer, mockSender, from, to, lang, mockNagivationCache) + addFeedback(w, req, mockRenderer, mockSender, from, to, lang, siteDomain, mockNagivationCache) Convey("Then the renderer is called to render the feedback page", func() { So(len(mockRenderer.BuildPageCalls()), ShouldEqual, 1) }) @@ -261,6 +264,7 @@ func Test_addFeedback(t *testing.T) { func Test_feedbackThanks(t *testing.T) { helper.InitialiseLocalisationsHelper(mocks.MockAssetFunction) lang := "en" + config.Get() // need to seed config Convey("Given a valid request", t, func() { req := httptest.NewRequest("GET", "http://localhost", nil) w := httptest.NewRecorder() @@ -286,7 +290,7 @@ func Test_feedbackThanks(t *testing.T) { }, }} Convey("When feedbackThanks is called", func() { - feedbackThanks(w, req, url, mockRenderer, mockNagivationCache, lang) + feedbackThanks(w, req, url, mockRenderer, mockNagivationCache, lang, siteDomain, false) Convey("Then the renderer is called", func() { So(len(mockRenderer.BuildPageCalls()), ShouldEqual, 1) }) @@ -299,7 +303,7 @@ func Test_feedbackThanks(t *testing.T) { Convey("Given a reflective XSS request", t, func() { req := httptest.NewRequest("GET", "http://localhost?returnTo=", nil) w := httptest.NewRecorder() - url := "www.test.com" + url := "https://www.referrer-test.com" mockRenderer := &interfacestest.RendererMock{ BuildPageFunc: func(w io.Writer, pageModel interface{}, templateName string) {}, NewBasePageModelFunc: func() coreModel.Page { @@ -320,11 +324,11 @@ func Test_feedbackThanks(t *testing.T) { }, }} Convey("When feedbackThanks is called", func() { - feedbackThanks(w, req, url, mockRenderer, mockNagivationCache, lang) - Convey("Then the handler sanitises the request text", func() { + feedbackThanks(w, req, url, mockRenderer, mockNagivationCache, lang, siteDomain, false) + Convey("Then the handler sanitises the request text to the referrer", func() { dataSentToRender := mockRenderer.BuildPageCalls()[0].PageModel.(model.Feedback) returnToUrl := dataSentToRender.ReturnTo - So(returnToUrl, ShouldEqual, "<script>alert(1)</script>") + So(returnToUrl, ShouldEqual, url) }) }) }) @@ -399,11 +403,38 @@ func TestValidateForm(t *testing.T) { }, }, { - givenDescription: "the a specific page/url type is chosen and the url is valid", + givenDescription: "the a specific page/url type is chosen and the url is valid but not allowed", + given: &model.FeedbackForm{ + Type: "A specific page", + Description: "Some text", + URL: "https://not-site-domain.com", + }, + expectedDescription: "a url validation error is returned", + expected: []coreModel.ErrorItem{ + { + Description: coreModel.Localisation{ + LocaleKey: "FeedbackValidURL", + Plural: 1, + }, + URL: "#type-error", + }, + }}, + { + givenDescription: "the a specific page/url type is chosen and the url is valid without a path", + given: &model.FeedbackForm{ + Type: "A specific page", + Description: "Some text", + URL: "https://cy.ons.gov.uk", + }, + expectedDescription: "no validation errors are returned", + expected: []coreModel.ErrorItem(nil), + }, + { + givenDescription: "the a specific page/url type is chosen and the url is valid and has a path", given: &model.FeedbackForm{ Type: "A specific page", Description: "Some text", - URL: "https://somewhere.com", + URL: "https://cy.ons.gov.uk/path", }, expectedDescription: "no validation errors are returned", expected: []coreModel.ErrorItem(nil), @@ -525,7 +556,7 @@ func TestValidateForm(t *testing.T) { for _, t := range testCases { Convey(fmt.Sprintf("When %s", t.givenDescription), func() { Convey(fmt.Sprintf("Then %s", t.expectedDescription), func() { - So(validateForm(t.given), ShouldResemble, t.expected) + So(validateForm(t.given, siteDomain), ShouldResemble, t.expected) }) }) } diff --git a/mapper/mapper.go b/mapper/mapper.go index 01409d2..d25148a 100644 --- a/mapper/mapper.go +++ b/mapper/mapper.go @@ -4,6 +4,7 @@ import ( "html" "net/http" + "github.com/ONSdigital/dp-frontend-feedback-controller/config" "github.com/ONSdigital/dp-frontend-feedback-controller/model" "github.com/ONSdigital/dp-renderer/v2/helper" core "github.com/ONSdigital/dp-renderer/v2/model" @@ -195,23 +196,28 @@ func CreateGetFeedback(req *http.Request, basePage core.Page, validationErrors [ return p } -func CreateGetFeedbackThanks(req *http.Request, basePage core.Page, lang, url, wholeSite string) model.Feedback { +func CreateGetFeedbackThanks(req *http.Request, basePage core.Page, lang, referrer, wholeSite string) model.Feedback { p := model.Feedback{ Page: basePage, } + if referrer == "" { + referrer = wholeSite + } p.Language = lang p.Type = "feedback" p.URI = req.URL.Path p.Metadata.Title = helper.Localise("FeedbackThanks", lang, 1) - p.PreviousURL = url + p.PreviousURL = referrer // returnTo is rendered on page so needs XSS protection returnTo := html.EscapeString(req.URL.Query().Get("returnTo")) if returnTo == "Whole site" { returnTo = wholeSite } else if returnTo == "" { - returnTo = url + returnTo = referrer + } else if !config.IsSiteDomainURL(returnTo, "") { + returnTo = referrer } p.ReturnTo = returnTo