config: Add the foundation for GDPR privacy configuration

See #4616
This commit is contained in:
Bjørn Erik Pedersen 2018-05-04 10:18:45 +02:00
parent 9bd4236e1b
commit 0bbdef986d
7 changed files with 239 additions and 5 deletions

View file

@ -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
@ -25,3 +31,14 @@ type Provider 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
}

View file

@ -0,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
}

View file

@ -0,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)
}

View file

@ -365,3 +365,25 @@ map[string]interface {}{
}`, 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)
}

View file

@ -168,7 +168,9 @@ func (h *HugoSites) assemble(config *BuildCfg) error {
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
}
}
}

View file

@ -27,6 +27,8 @@ import (
"strings"
"time"
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/resource"
"golang.org/x/sync/errgroup"
@ -386,6 +388,12 @@ type SiteInfo struct {
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,14 +1036,13 @@ func (s *Site) Initialise() (err error) {
}
func (s *Site) initialize() (err error) {
defer s.initializeSiteInfo()
s.Menus = Menus{}
if err = s.checkDirectories(); err != nil {
return err
}
return
return s.initializeSiteInfo()
}
// HomeAbsURL is a convenience method giving the absolute URL to the home page.
@ -1058,7 +1065,7 @@ func (s *SiteInfo) SitemapAbsURL() string {
return p
}
func (s *Site) initializeSiteInfo() {
func (s *Site) initializeSiteInfo() error {
var (
lang = s.Language
languages helpers.Languages
@ -1113,6 +1120,11 @@ func (s *Site) initializeSiteInfo() {
}
}
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 @@ func (s *Site) initializeSiteInfo() {
Data: &s.Data,
owner: s.owner,
s: s,
PrivacyConfig: privacyConfig,
}
rssOutputFormat, found := s.outputFormats[KindHome].GetByName(output.RSSFormat.Name)
@ -1146,6 +1159,8 @@ func (s *Site) initializeSiteInfo() {
if found {
s.Info.RSSLink = s.permalink(rssOutputFormat.BaseFilename())
}
return nil
}
func (s *Site) dataDir() string {

View file

@ -55,7 +55,7 @@ func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) {
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)