shithub: hugo

Download patch

ref: 0bbdef986d8eecf4fabe9a372e33626dbdfeb36b
parent: 9bd4236e1b3bee332439eef50e12d4481340c3eb
author: Bjørn Erik Pedersen <[email protected]>
date: Fri May 4 06:18:45 EDT 2018

config: Add the foundation for GDPR privacy configuration

See #4616

--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -13,6 +13,12 @@
 
 package config
 
+import (
+	"strings"
+
+	"github.com/spf13/viper"
+)
+
 // Provider provides the configuration settings for Hugo.
 type Provider interface {
 	GetString(key string) string
@@ -24,4 +30,15 @@
 	Get(key string) interface{}
 	Set(key string, value interface{})
 	IsSet(key string) bool
+}
+
+// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
+func FromConfigString(config, configType string) (Provider, error) {
+	v := viper.New()
+	v.SetConfigType(configType)
+	if err := v.ReadConfig(strings.NewReader(config)); err != nil {
+		return nil, err
+	}
+	return v, nil
+
 }
--- /dev/null
+++ b/config/privacy/privacyConfig.go
@@ -1,0 +1,85 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package privacy
+
+import (
+	"github.com/gohugoio/hugo/config"
+	"github.com/mitchellh/mapstructure"
+)
+
+const privacyConfigKey = "privacy"
+
+// Service is the common values for a service in a policy definition.
+type Service struct {
+	Disable bool
+}
+
+// Config is a privacy configuration for all the relevant services in Hugo.
+type Config struct {
+	Disqus          Disqus
+	GoogleAnalytics GoogleAnalytics
+	Instagram       Instagram
+	SpeakerDeck     SpeakerDeck
+	Tweet           Tweet
+	Vimeo           Vimeo
+	YouTube         YouTube
+}
+
+// Disqus holds the privacy configuration settings related to the Disqus template.
+type Disqus struct {
+	Service `mapstructure:",squash"`
+}
+
+// GoogleAnalytics holds the privacy configuration settings related to the Google Analytics template.
+type GoogleAnalytics struct {
+	Service `mapstructure:",squash"`
+}
+
+// Instagram holds the privacy configuration settings related to the Instagram shortcode.
+type Instagram struct {
+	Service `mapstructure:",squash"`
+}
+
+// SpeakerDeck holds the privacy configuration settings related to the SpeakerDeck shortcode.
+type SpeakerDeck struct {
+	Service `mapstructure:",squash"`
+}
+
+// Tweet holds the privacy configuration settingsrelated to the Tweet shortcode.
+type Tweet struct {
+	Service `mapstructure:",squash"`
+}
+
+// Vimeo holds the privacy configuration settingsrelated to the Vimeo shortcode.
+type Vimeo struct {
+	Service `mapstructure:",squash"`
+}
+
+// YouTube holds the privacy configuration settingsrelated to the YouTube shortcode.
+type YouTube struct {
+	Service  `mapstructure:",squash"`
+	NoCookie bool
+}
+
+func DecodeConfig(cfg config.Provider) (pc Config, err error) {
+	if !cfg.IsSet(privacyConfigKey) {
+		return
+	}
+
+	m := cfg.GetStringMap(privacyConfigKey)
+
+	err = mapstructure.WeakDecode(m, &pc)
+
+	return
+}
--- /dev/null
+++ b/config/privacy/privacyConfig_test.go
@@ -1,0 +1,93 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package privacy
+
+import (
+	"testing"
+
+	"github.com/gohugoio/hugo/config"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDecodeConfigFromTOML(t *testing.T) {
+	assert := require.New(t)
+
+	tomlConfig := `
+
+someOtherValue = "foo"
+
+[privacy]
+[privacy.disqus]
+disable = true
+[privacy.googleAnalytics]
+disable = true
+[privacy.instagram]
+disable = true
+[privacy.speakerDeck]
+disable = true
+[privacy.tweet]
+disable = true
+[privacy.vimeo]
+disable = true
+[privacy.youtube]
+disable = true
+noCookie = true
+`
+	cfg, err := config.FromConfigString(tomlConfig, "toml")
+	assert.NoError(err)
+
+	pc, err := DecodeConfig(cfg)
+	assert.NoError(err)
+	assert.NotNil(pc)
+
+	assert.True(pc.Disqus.Disable)
+	assert.True(pc.GoogleAnalytics.Disable)
+	assert.True(pc.Instagram.Disable)
+	assert.True(pc.SpeakerDeck.Disable)
+	assert.True(pc.Tweet.Disable)
+	assert.True(pc.Vimeo.Disable)
+
+	assert.True(pc.YouTube.NoCookie)
+	assert.True(pc.YouTube.Disable)
+}
+
+func TestDecodeConfigFromTOMLCaseInsensitive(t *testing.T) {
+	assert := require.New(t)
+
+	tomlConfig := `
+
+someOtherValue = "foo"
+
+[Privacy]
+[Privacy.YouTube]
+NoCOOKIE = true
+`
+	cfg, err := config.FromConfigString(tomlConfig, "toml")
+	assert.NoError(err)
+
+	pc, err := DecodeConfig(cfg)
+	assert.NoError(err)
+	assert.NotNil(pc)
+	assert.True(pc.YouTube.NoCookie)
+}
+
+func TestDecodeConfigDefault(t *testing.T) {
+	assert := require.New(t)
+
+	pc, err := DecodeConfig(viper.New())
+	assert.NoError(err)
+	assert.NotNil(pc)
+	assert.False(pc.YouTube.NoCookie)
+}
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -365,3 +365,25 @@
 }`, got["menu"])
 
 }
+
+func TestPrivacyConfig(t *testing.T) {
+	t.Parallel()
+
+	assert := require.New(t)
+
+	tomlConfig := `
+
+someOtherValue = "foo"
+
+[privacy]
+[privacy.youtube]
+noCookie = true
+`
+
+	b := newTestSitesBuilder(t)
+	b.WithConfigFile("toml", tomlConfig)
+	b.Build(BuildCfg{SkipRender: true})
+
+	assert.True(b.H.Sites[0].Info.PrivacyConfig.YouTube.NoCookie)
+
+}
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -168,7 +168,9 @@
 	if len(h.Sites) > 1 {
 		// The first is initialized during process; initialize the rest
 		for _, site := range h.Sites[1:] {
-			site.initializeSiteInfo()
+			if err := site.initializeSiteInfo(); err != nil {
+				return err
+			}
 		}
 	}
 
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -27,6 +27,8 @@
 	"strings"
 	"time"
 
+	"github.com/gohugoio/hugo/config/privacy"
+
 	"github.com/gohugoio/hugo/resource"
 
 	"golang.org/x/sync/errgroup"
@@ -386,6 +388,12 @@
 	preserveTaxonomyNames bool
 	Data                  *map[string]interface{}
 
+	// This contains all privacy related settings that can be used to
+	// make the YouTube template etc.GDPR compliant.
+	// It is mostly in use by Hugo's built-in, but is also available
+	// for end users with {{ .Site.PrivacyConfig.YouTube.NoCookie }} etc.
+	PrivacyConfig privacy.Config
+
 	owner                          *HugoSites
 	s                              *Site
 	multilingual                   *Multilingual
@@ -1028,7 +1036,6 @@
 }
 
 func (s *Site) initialize() (err error) {
-	defer s.initializeSiteInfo()
 	s.Menus = Menus{}
 
 	if err = s.checkDirectories(); err != nil {
@@ -1035,7 +1042,7 @@
 		return err
 	}
 
-	return
+	return s.initializeSiteInfo()
 }
 
 // HomeAbsURL is a convenience method giving the absolute URL to the home page.
@@ -1058,7 +1065,7 @@
 	return p
 }
 
-func (s *Site) initializeSiteInfo() {
+func (s *Site) initializeSiteInfo() error {
 	var (
 		lang      = s.Language
 		languages helpers.Languages
@@ -1113,6 +1120,11 @@
 		}
 	}
 
+	privacyConfig, err := privacy.DecodeConfig(lang)
+	if err != nil {
+		return err
+	}
+
 	s.Info = SiteInfo{
 		Title:                          lang.GetString("title"),
 		Author:                         lang.GetStringMap("author"),
@@ -1139,6 +1151,7 @@
 		Data:                           &s.Data,
 		owner:                          s.owner,
 		s:                              s,
+		PrivacyConfig:                  privacyConfig,
 	}
 
 	rssOutputFormat, found := s.outputFormats[KindHome].GetByName(output.RSSFormat.Name)
@@ -1146,6 +1159,8 @@
 	if found {
 		s.Info.RSSLink = s.permalink(rssOutputFormat.BaseFilename())
 	}
+
+	return nil
 }
 
 func (s *Site) dataDir() string {
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -55,7 +55,7 @@
 		d := deps.DepsCfg{Cfg: cfg, Fs: fs}
 		s, err := NewSiteForCfg(d)
 		require.NoError(t, err)
-		s.initializeSiteInfo()
+		require.NoError(t, s.initializeSiteInfo())
 
 		if s.Info.BaseURL() != template.URL(this.expected) {
 			t.Errorf("[%d] got %s expected %s", i, s.Info.BaseURL(), this.expected)