From 6070e6425cff6d3ce9828ae32a434d78f314bfd0 Mon Sep 17 00:00:00 2001 From: Julien Midedji Date: Tue, 2 Nov 2021 23:56:58 +0100 Subject: [PATCH] Add fields for basic auth (#1133) * Add fields for basic auth * Use map instead of cycling through slice * Format code Co-authored-by: Julien Midedji --- go.mod | 2 +- go.sum | 4 +- modules/feedreader/auth_test.go | 127 ++++++++++++++++++++++++++++++++ modules/feedreader/settings.go | 46 ++++++++++-- modules/feedreader/widget.go | 19 ++++- 5 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 modules/feedreader/auth_test.go diff --git a/go.mod b/go.mod index 448555177..75972f420 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/lib/pq v1.2.0 // indirect github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b github.com/microsoft/azure-devops-go-api/azuredevops v0.0.0-20191014190507-26902c1d4325 - github.com/mmcdole/gofeed v1.1.0 + github.com/mmcdole/gofeed v1.1.4-0.20211013195857-68ee9054d97b github.com/nicklaw5/helix v0.7.0 github.com/olebedev/config v0.0.0-20190528211619-364964f3a8e4 github.com/olekukonko/tablewriter v0.0.4 diff --git a/go.sum b/go.sum index 74c72e2e9..28e4627cb 100644 --- a/go.sum +++ b/go.sum @@ -438,8 +438,8 @@ github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1: github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mmcdole/gofeed v1.1.0 h1:T2WrGLVJRV04PY2qwhEJLHCt9JiCtBhb6SmC8ZvJH08= -github.com/mmcdole/gofeed v1.1.0/go.mod h1:PPiVwgDXLlz2N83KB4TrIim2lyYM5Zn7ZWH9Pi4oHUk= +github.com/mmcdole/gofeed v1.1.4-0.20211013195857-68ee9054d97b h1:l332QWu9K3Pc7SEe1iz4Sk4Q86/6vqgNLlVznagSyX0= +github.com/mmcdole/gofeed v1.1.4-0.20211013195857-68ee9054d97b/go.mod h1:QQO3maftbOu+hiVOGOZDRLymqGQCos4zxbA4j89gMrE= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8= github.com/mndrix/tap-go v0.0.0-20170113192335-56cca451570b h1:/1WAr2jAZnfxlWW3M3VJ4P3VRi57AjbrUv9U1kCRHyk= diff --git a/modules/feedreader/auth_test.go b/modules/feedreader/auth_test.go new file mode 100644 index 000000000..354a40c7b --- /dev/null +++ b/modules/feedreader/auth_test.go @@ -0,0 +1,127 @@ +package feedreader + +import ( + "net/http" + "testing" + + "github.com/olebedev/config" + "gotest.tools/assert" +) + +var listformat string = ` +enabled: true +feeds: + - https://news.ycombinator.com/rss + - https://rss.cbc.ca/lineup/topstories.xml + +feedLimit: 10 +position: + top: 1 + left: 1 + width: 2 + height: 1 + refreshInterval: 14400` + +var mapformat string = ` +enabled: true +feeds: + http://localhost:3000/feed/rss.xml: + username: johndoe + password: supersecret + +feedLimit: 10 +position: + top: 1 + left: 1 + width: 2 + height: 1 + refreshInterval: 14400` + +var globalConfig string = ` +wtf: + colors: + border: + focusable: darkslateblue + focused: orange + normal: gray` + +var sampleRSS string = ` + + + + The Stoics + https://google.com + Testing feed to test private feeds + + Marcus Aurelius + https://en.wikipedia.org/wiki/Marcus_Aurelius + + "The soul becomes dyed with the color of its thoughts." + + + + Seneca + https://en.wikipedia.org/wiki/Seneca_the_Younger + "We suffer more often in imagination than in reality" + + + + +` + +func TestMapFormatFetch(t *testing.T) { + ymlCfg, _ := config.ParseYaml(mapformat) + globalCfg, _ := config.ParseYaml(globalConfig) + + settings := NewSettingsFromYAML("feedreader", ymlCfg, globalCfg) + widget := NewWidget(nil, nil, settings) + + go setupPrivateRSSFeed() + + f, err := widget.Fetch(widget.settings.feeds) + if err != nil { + t.Error(err) + } + + assert.Equal(t, len(f), 2) +} + +func TestListFormatFetch(t *testing.T) { + ymlCfg, _ := config.ParseYaml(listformat) + globalCfg, _ := config.ParseYaml(globalConfig) + + settings := NewSettingsFromYAML("feedreader", ymlCfg, globalCfg) + widget := NewWidget(nil, nil, settings) + + go setupPrivateRSSFeed() + + f, err := widget.Fetch(widget.settings.feeds) + if err != nil { + t.Error(err) + } + + if len(f) == 0 { + t.Error("No articles fetched") + } +} + +func setupPrivateRSSFeed() { + http.HandleFunc("/feed/rss.xml", func(w http.ResponseWriter, r *http.Request) { + username, password, exists := r.BasicAuth() + if !exists || username != "johndoe" || password != "supersecret" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + w.Header().Set("Content-Type", "application/xml") + + _, err := w.Write([]byte(sampleRSS)) + if err != nil { + panic(err) + } + }) + err := http.ListenAndServe(":3000", nil) + if err != nil { + panic(err) + } +} diff --git a/modules/feedreader/settings.go b/modules/feedreader/settings.go index e4c31a389..b3d46fadd 100644 --- a/modules/feedreader/settings.go +++ b/modules/feedreader/settings.go @@ -11,21 +11,57 @@ const ( defaultTitle = "Feed Reader" ) +// auth stores [username, password]-credentials for private RSS feeds using Basic Auth +type auth struct { + username string + password string +} + // Settings defines the configuration properties for this module type Settings struct { *cfg.Common - feeds []string `help:"An array of RSS and Atom feed URLs"` - feedLimit int `help:"The maximum number of stories to display for each feed"` + feeds []string `help:"An array of RSS and Atom feed URLs"` + feedLimit int `help:"The maximum number of stories to display for each feed"` + credentials map[string]auth `help:"Map of private feed URLs with required authentication credentials"` } // NewSettingsFromYAML creates a new settings instance from a YAML config block func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { settings := &Settings{ - Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), + Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), + feeds: utils.ToStrs(ymlConfig.UList("feeds")), + feedLimit: ymlConfig.UInt("feedLimit", -1), + credentials: make(map[string]auth), + } + + // If feeds cannot be parsed as list try parsing as a map with username+password fields + if len(settings.feeds) == 0 { + credentials := make(map[string]auth) + feeds := make([]string, 0) + for url, creds := range ymlConfig.UMap("feeds") { + parsed, ok := creds.(map[string]interface{}) + if !ok { + continue + } + + user, ok := parsed["username"].(string) + if !ok { + continue + } + pass, ok := parsed["password"].(string) + if !ok { + continue + } - feeds: utils.ToStrs(ymlConfig.UList("feeds")), - feedLimit: ymlConfig.UInt("feedLimit", -1), + credentials[url] = auth{ + username: user, + password: pass, + } + feeds = append(feeds, url) + } + settings.feeds = feeds + settings.credentials = credentials } return settings diff --git a/modules/feedreader/widget.go b/modules/feedreader/widget.go index ec6b7c547..f9bedb4a4 100644 --- a/modules/feedreader/widget.go +++ b/modules/feedreader/widget.go @@ -125,7 +125,24 @@ func (widget *Widget) Render() { /* -------------------- Unexported Functions -------------------- */ func (widget *Widget) fetchForFeed(feedURL string) ([]*FeedItem, error) { - feed, err := widget.parser.ParseURL(feedURL) + var ( + feed *gofeed.Feed + err error + ) + if auth, isPrivateRSS := widget.settings.credentials[feedURL]; isPrivateRSS { + fp := gofeed.NewParser() + fp.AuthConfig = &gofeed.Auth{ + Username: auth.username, + Password: auth.password, + } + feed, err = fp.ParseURL(feedURL) + } else { + feed, err = widget.parser.ParseURL(feedURL) + } + if err != nil { + return nil, err + } + if err != nil { return nil, err }