Skip to content

Commit

Permalink
ensure cfg.SiteDomain ends any URL we redirect to
Browse files Browse the repository at this point in the history
  • Loading branch information
gedge committed Jul 25, 2024
1 parent a7c4cd6 commit 1949479
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ run:
timeout: 5m
linters-settings:
govet:
check-shadowing: true
shadow: true
golint:
min-confidence: 0
gocyclo:
Expand Down
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ build: generate-prod

.PHONY: lint
lint:
go install github.com/golangci/golangci-lint/cmd/[email protected]
golangci-lint run ./...

.PHONY: debug
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion ci/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ci/component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ci/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions ci/scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash -eux

go install github.com/kevinburke/go-bindata/v4/[email protected]

pushd dp-frontend-feedback-controller
make build
cp build/dp-frontend-feedback-controller Dockerfile.concourse ../build
Expand Down
2 changes: 2 additions & 0 deletions ci/scripts/lint.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash -eux

go install github.com/golangci/golangci-lint/cmd/[email protected]

pushd dp-frontend-feedback-controller
make lint
popd
2 changes: 2 additions & 0 deletions ci/scripts/unit.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash -eux

go install github.com/kevinburke/go-bindata/v4/[email protected]

pushd dp-frontend-feedback-controller
make test
popd
2 changes: 1 addition & 1 deletion ci/unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 28 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package config

import (
"net/url"
"strings"
"time"

"github.com/kelseyhightower/envconfig"
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
}
31 changes: 30 additions & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down Expand Up @@ -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)
})
})
})
}
72 changes: 29 additions & 43 deletions handlers/feedback.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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{
Expand All @@ -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
}
}
}

Expand Down
Loading

0 comments on commit 1949479

Please sign in to comment.