From 7467b43853be1ab68634170333d4f14b231f719a Mon Sep 17 00:00:00 2001 From: WTFKr0 Date: Fri, 10 Nov 2017 15:50:53 +0100 Subject: [PATCH 1/5] Add read only mode --- handlers.go | 13 +++++++ main.go | 77 +++++++++++++++++++++++++----------------- templates/header.html | 9 ++--- templates/home.html | 4 ++- templates/library.html | 4 +-- web.go | 26 +++++++++----- 6 files changed, 87 insertions(+), 46 deletions(-) diff --git a/handlers.go b/handlers.go index 86bd4f9..9bcf6e6 100644 --- a/handlers.go +++ b/handlers.go @@ -31,6 +31,7 @@ type Response struct { Error string User string + IsAdmin bool Section string // Paging @@ -54,16 +55,28 @@ type Response struct { Youtubes []youtube.Video } +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + func NewResponse(r *http.Request, ps httprouter.Params) *Response { diskInfo, err := NewDiskInfo(datadir) if err != nil { panic(err) } + user, _, _ := r.BasicAuth() + isAdmin := stringInSlice(user, httpAdminUsers) return &Response{ Config: config.Get(), Request: r, Params: &ps, User: ps.ByName("user"), + IsAdmin: isAdmin, HTTPHost: httpHost, Version: version, Backlink: backlink, diff --git a/main.go b/main.go index ce0ed27..d46e4de 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,9 @@ var ( debug bool httpAddr string httpAdmins arrayFlags + httpAdminUsers []string + httpReadOnlys arrayFlags + httpReadOnlyUsers []string httpHost string httpPrefix string letsencrypt bool @@ -61,7 +64,8 @@ func init() { cli.StringVar(&datadir, "data-dir", "/data", "data directory") cli.BoolVar(&debug, "debug", false, "debug mode") cli.StringVar(&httpAddr, "http-addr", ":80", "listen address") - cli.Var(&httpAdmins, "http-admin", "HTTP basic auth user/password for admin.") + cli.Var(&httpAdmins, "http-admin", "HTTP basic auth user/password for admins.") + cli.Var(&httpReadOnlys, "http-read-only", "HTTP basic auth user/password for read only users.") cli.StringVar(&httpHost, "http-host", "", "HTTP host") cli.StringVar(&httpPrefix, "http-prefix", "/streamlist", "HTTP URL prefix (not actually supported yet!)") cli.BoolVar(&letsencrypt, "letsencrypt", false, "enable TLS using Let's Encrypt") @@ -74,6 +78,17 @@ func main() { cli.Parse(os.Args[1:]) + for _, httpUser := range httpAdmins { + split := strings.Split(httpUser, ":") + httpUsername := split[0] + httpAdminUsers = append(httpAdminUsers, httpUsername) + } + for _, httpUser := range httpReadOnlys { + split := strings.Split(httpUser, ":") + httpUsername := split[0] + httpReadOnlyUsers = append(httpReadOnlyUsers, httpUsername) + } + // logtailer logtail, err = logtailer.NewLogtailer(200 * 1024) if err != nil { @@ -159,55 +174,55 @@ func main() { r.HandleMethodNotAllowed = false // Handlers - r.GET("/", Log(Auth(index, false))) - r.GET(Prefix("/logs"), Log(Auth(logs, false))) - r.GET(Prefix("/"), Log(Auth(home, false))) + r.GET("/", Log(Auth(index, "readonly"))) + r.GET(Prefix("/logs"), Log(Auth(logs, "admin"))) + r.GET(Prefix("/"), Log(Auth(home, "readonly"))) // Library - r.GET(Prefix("/library"), Log(Auth(library, false))) + r.GET(Prefix("/library"), Log(Auth(library, "readonly"))) // Media - r.GET(Prefix("/media/thumbnail/:media"), Log(Auth(thumbnailMedia, false))) - r.GET(Prefix("/media/view/:media"), Log(Auth(viewMedia, false))) - r.GET(Prefix("/media/delete/:media"), Log(Auth(deleteMedia, false))) - r.GET(Prefix("/media/access/:filename"), Auth(streamMedia, false)) - r.GET(Prefix("/media/download/:filename"), Auth(downloadMedia, false)) + r.GET(Prefix("/media/thumbnail/:media"), Log(Auth(thumbnailMedia, "readonly"))) + r.GET(Prefix("/media/view/:media"), Log(Auth(viewMedia, "readonly"))) + r.GET(Prefix("/media/delete/:media"), Log(Auth(deleteMedia, "admin"))) + r.GET(Prefix("/media/access/:filename"), Auth(streamMedia, "readonly")) + r.GET(Prefix("/media/download/:filename"), Auth(downloadMedia, "readonly")) // Publicly accessible streaming (using playlist id as "auth") - r.GET(Prefix("/stream/:list/:filename"), Auth(streamMedia, true)) + r.GET(Prefix("/stream/:list/:filename"), Auth(streamMedia, "none")) // Import - r.GET(Prefix("/import"), Log(Auth(importHandler, false))) + r.GET(Prefix("/import"), Log(Auth(importHandler, "admin"))) // Archiver - r.GET(Prefix("/archiver/jobs"), Auth(archiverJobs, false)) - r.POST(Prefix("/archiver/save/:id"), Log(Auth(archiverSave, false))) - r.GET(Prefix("/archiver/cancel/:id"), Log(Auth(archiverCancel, false))) + r.GET(Prefix("/archiver/jobs"), Auth(archiverJobs, "admin")) + r.POST(Prefix("/archiver/save/:id"), Log(Auth(archiverSave, "admin"))) + r.GET(Prefix("/archiver/cancel/:id"), Log(Auth(archiverCancel, "admin"))) // List - r.GET(Prefix("/create"), Log(Auth(createList, false))) - r.POST(Prefix("/create"), Log(Auth(createList, false))) - r.POST(Prefix("/add/:list/:media"), Log(Auth(addMediaList, false))) - r.POST(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, false))) - r.GET(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, false))) + r.GET(Prefix("/create"), Log(Auth(createList, "admin"))) + r.POST(Prefix("/create"), Log(Auth(createList, "admin"))) + r.POST(Prefix("/add/:list/:media"), Log(Auth(addMediaList, "admin"))) + r.POST(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, "admin"))) + r.GET(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, "admin"))) - r.GET(Prefix("/edit/:id"), Log(Auth(editList, false))) - r.POST(Prefix("/edit/:id"), Log(Auth(editList, false))) - r.GET(Prefix("/shuffle/:id"), Log(Auth(shuffleList, false))) - r.GET(Prefix("/play/:id"), Log(Auth(playList, true))) - r.GET(Prefix("/m3u/:id"), Log(Auth(m3uList, true))) - r.GET(Prefix("/podcast/:id"), Log(Auth(podcastList, true))) + r.GET(Prefix("/edit/:id"), Log(Auth(editList, "admin"))) + r.POST(Prefix("/edit/:id"), Log(Auth(editList, "admin"))) + r.GET(Prefix("/shuffle/:id"), Log(Auth(shuffleList, "admin"))) + r.GET(Prefix("/play/:id"), Log(Auth(playList, "none"))) + r.GET(Prefix("/m3u/:id"), Log(Auth(m3uList, "none"))) + r.GET(Prefix("/podcast/:id"), Log(Auth(podcastList, "none"))) - r.POST(Prefix("/config"), Log(Auth(configHandler, false))) + r.POST(Prefix("/config"), Log(Auth(configHandler, "admin"))) - r.GET(Prefix("/delete/:id"), Log(Auth(deleteList, false))) + r.GET(Prefix("/delete/:id"), Log(Auth(deleteList, "admin"))) // API - r.GET(Prefix("/v1/status"), Log(Auth(v1status, true))) + r.GET(Prefix("/v1/status"), Log(Auth(v1status, "none"))) // Assets - r.GET(Prefix("/static/*path"), Auth(staticAsset, true)) // TODO: Auth() but by checking Origin/Referer for a valid playlist ID? - r.GET(Prefix("/logo.png"), Log(Auth(logo, true))) + r.GET(Prefix("/static/*path"), Auth(staticAsset, "none")) // TODO: Auth() but by checking Origin/Referer for a valid playlist ID? + r.GET(Prefix("/logo.png"), Log(Auth(logo, "none"))) // // Server diff --git a/templates/header.html b/templates/header.html index 38dd8b4..cb57c67 100644 --- a/templates/header.html +++ b/templates/header.html @@ -18,7 +18,6 @@ - {{if $.User}} {{end}} diff --git a/templates/home.html b/templates/home.html index a10f5b2..bedd6d8 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,7 +1,9 @@ {{template "header.html" .}}
- New Playlist + {{if $.IsAdmin}} + New Playlist + {{end}}

Playlists

diff --git a/templates/library.html b/templates/library.html index db89c8c..a264a6c 100644 --- a/templates/library.html +++ b/templates/library.html @@ -35,8 +35,8 @@

Library

{{range $list := $.Lists}}
{{$hasmedia := $list.HasMedia $media}} - - + +
{{end}} diff --git a/web.go b/web.go index db81389..4a4e3b2 100644 --- a/web.go +++ b/web.go @@ -117,14 +117,27 @@ func Log(h httprouter.Handle) httprouter.Handle { } } -func Auth(h httprouter.Handle, optional bool) httprouter.Handle { +func Auth(h httprouter.Handle, role string) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { user := "" + // none role is no auth required + if role == "none" { + h(w, r, ps) + return + } + // Method: Basic Auth (if we're not behind a reverse proxy, use basic auth) if httpAdmins != nil { - for _, httpAdmin := range httpAdmins { - split := strings.Split(httpAdmin, ":") + var userList []string + // Admin are always OK + userList = append(userList, httpAdmins...) + // If role readonly, we add readonly users + if role == "readonly" { + userList = append(userList, httpReadOnlys...) + } + for _, httpUser := range userList { + split := strings.Split(httpUser, ":") httpUsername := split[0] httpPassword := split[1] user, password, _ := r.BasicAuth() @@ -134,10 +147,6 @@ func Auth(h httprouter.Handle, optional bool) httprouter.Handle { return } } - if optional { - h(w, r, ps) - return - } w.Header().Set("WWW-Authenticate", `Basic realm="Sign-in Required"`) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return @@ -155,7 +164,8 @@ func Auth(h httprouter.Handle, optional bool) httprouter.Handle { user = r.Header.Get(reverseProxyAuthHeader) } - if user == "" && !optional { + //if user == "" && !optional { + if user == "" { logger.Errorf("auth failed: client %q", clientIP) if backlink != "" { http.Redirect(w, r, backlink, http.StatusFound) From 1e35a8ea713c7c7ba7ffe4ba8b5e8de5b085a270 Mon Sep 17 00:00:00 2001 From: WTFKr0 Date: Fri, 10 Nov 2017 16:08:39 +0100 Subject: [PATCH 2/5] Hide edit link for non admin --- templates/home.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/templates/home.html b/templates/home.html index bedd6d8..65703eb 100644 --- a/templates/home.html +++ b/templates/home.html @@ -24,8 +24,10 @@

Playlists

{{with $tl := $list.TotalLength}} {{duration $tl}} {{end}} -    - + {{if $.IsAdmin}} +    + + {{end}} {{end}} From 53071493ef9c76e9b2747d1cee39f3623679f997 Mon Sep 17 00:00:00 2001 From: WTFKr0 Date: Fri, 10 Nov 2017 16:58:41 +0100 Subject: [PATCH 3/5] Unexport Auth --- main.go | 60 ++++++++++++++++++++++++++++----------------------------- web.go | 2 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/main.go b/main.go index d46e4de..f4fbd51 100644 --- a/main.go +++ b/main.go @@ -174,55 +174,55 @@ func main() { r.HandleMethodNotAllowed = false // Handlers - r.GET("/", Log(Auth(index, "readonly"))) - r.GET(Prefix("/logs"), Log(Auth(logs, "admin"))) - r.GET(Prefix("/"), Log(Auth(home, "readonly"))) + r.GET("/", Log(auth(index, "readonly"))) + r.GET(Prefix("/logs"), Log(auth(logs, "admin"))) + r.GET(Prefix("/"), Log(auth(home, "readonly"))) // Library - r.GET(Prefix("/library"), Log(Auth(library, "readonly"))) + r.GET(Prefix("/library"), Log(auth(library, "readonly"))) // Media - r.GET(Prefix("/media/thumbnail/:media"), Log(Auth(thumbnailMedia, "readonly"))) - r.GET(Prefix("/media/view/:media"), Log(Auth(viewMedia, "readonly"))) - r.GET(Prefix("/media/delete/:media"), Log(Auth(deleteMedia, "admin"))) - r.GET(Prefix("/media/access/:filename"), Auth(streamMedia, "readonly")) - r.GET(Prefix("/media/download/:filename"), Auth(downloadMedia, "readonly")) + r.GET(Prefix("/media/thumbnail/:media"), Log(auth(thumbnailMedia, "readonly"))) + r.GET(Prefix("/media/view/:media"), Log(auth(viewMedia, "readonly"))) + r.GET(Prefix("/media/delete/:media"), Log(auth(deleteMedia, "admin"))) + r.GET(Prefix("/media/access/:filename"), auth(streamMedia, "readonly")) + r.GET(Prefix("/media/download/:filename"), auth(downloadMedia, "readonly")) // Publicly accessible streaming (using playlist id as "auth") - r.GET(Prefix("/stream/:list/:filename"), Auth(streamMedia, "none")) + r.GET(Prefix("/stream/:list/:filename"), auth(streamMedia, "none")) // Import - r.GET(Prefix("/import"), Log(Auth(importHandler, "admin"))) + r.GET(Prefix("/import"), Log(auth(importHandler, "admin"))) // Archiver - r.GET(Prefix("/archiver/jobs"), Auth(archiverJobs, "admin")) - r.POST(Prefix("/archiver/save/:id"), Log(Auth(archiverSave, "admin"))) - r.GET(Prefix("/archiver/cancel/:id"), Log(Auth(archiverCancel, "admin"))) + r.GET(Prefix("/archiver/jobs"), auth(archiverJobs, "admin")) + r.POST(Prefix("/archiver/save/:id"), Log(auth(archiverSave, "admin"))) + r.GET(Prefix("/archiver/cancel/:id"), Log(auth(archiverCancel, "admin"))) // List - r.GET(Prefix("/create"), Log(Auth(createList, "admin"))) - r.POST(Prefix("/create"), Log(Auth(createList, "admin"))) - r.POST(Prefix("/add/:list/:media"), Log(Auth(addMediaList, "admin"))) - r.POST(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, "admin"))) - r.GET(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, "admin"))) + r.GET(Prefix("/create"), Log(auth(createList, "admin"))) + r.POST(Prefix("/create"), Log(auth(createList, "admin"))) + r.POST(Prefix("/add/:list/:media"), Log(auth(addMediaList, "admin"))) + r.POST(Prefix("/remove/:list/:media"), Log(auth(removeMediaList, "admin"))) + r.GET(Prefix("/remove/:list/:media"), Log(auth(removeMediaList, "admin"))) - r.GET(Prefix("/edit/:id"), Log(Auth(editList, "admin"))) - r.POST(Prefix("/edit/:id"), Log(Auth(editList, "admin"))) - r.GET(Prefix("/shuffle/:id"), Log(Auth(shuffleList, "admin"))) - r.GET(Prefix("/play/:id"), Log(Auth(playList, "none"))) - r.GET(Prefix("/m3u/:id"), Log(Auth(m3uList, "none"))) - r.GET(Prefix("/podcast/:id"), Log(Auth(podcastList, "none"))) + r.GET(Prefix("/edit/:id"), Log(auth(editList, "admin"))) + r.POST(Prefix("/edit/:id"), Log(auth(editList, "admin"))) + r.GET(Prefix("/shuffle/:id"), Log(auth(shuffleList, "admin"))) + r.GET(Prefix("/play/:id"), Log(auth(playList, "none"))) + r.GET(Prefix("/m3u/:id"), Log(auth(m3uList, "none"))) + r.GET(Prefix("/podcast/:id"), Log(auth(podcastList, "none"))) - r.POST(Prefix("/config"), Log(Auth(configHandler, "admin"))) + r.POST(Prefix("/config"), Log(auth(configHandler, "admin"))) - r.GET(Prefix("/delete/:id"), Log(Auth(deleteList, "admin"))) + r.GET(Prefix("/delete/:id"), Log(auth(deleteList, "admin"))) // API - r.GET(Prefix("/v1/status"), Log(Auth(v1status, "none"))) + r.GET(Prefix("/v1/status"), Log(auth(v1status, "none"))) // Assets - r.GET(Prefix("/static/*path"), Auth(staticAsset, "none")) // TODO: Auth() but by checking Origin/Referer for a valid playlist ID? - r.GET(Prefix("/logo.png"), Log(Auth(logo, "none"))) + r.GET(Prefix("/static/*path"), auth(staticAsset, "none")) // TODO: auth() but by checking Origin/Referer for a valid playlist ID? + r.GET(Prefix("/logo.png"), Log(auth(logo, "none"))) // // Server diff --git a/web.go b/web.go index 4a4e3b2..adb7aca 100644 --- a/web.go +++ b/web.go @@ -117,7 +117,7 @@ func Log(h httprouter.Handle) httprouter.Handle { } } -func Auth(h httprouter.Handle, role string) httprouter.Handle { +func auth(h httprouter.Handle, role string) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { user := "" From ff878b492c9f2d75fb16684b6176a78af2d77544 Mon Sep 17 00:00:00 2001 From: WTFKr0 Date: Fri, 10 Nov 2017 17:08:21 +0100 Subject: [PATCH 4/5] Remove todo --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index f4fbd51..d7b2e28 100644 --- a/main.go +++ b/main.go @@ -221,7 +221,7 @@ func main() { r.GET(Prefix("/v1/status"), Log(auth(v1status, "none"))) // Assets - r.GET(Prefix("/static/*path"), auth(staticAsset, "none")) // TODO: auth() but by checking Origin/Referer for a valid playlist ID? + r.GET(Prefix("/static/*path"), auth(staticAsset, "none")) r.GET(Prefix("/logo.png"), Log(auth(logo, "none"))) // From 2383df8b28e249ff35675b08b28d9f7ee2f258fc Mon Sep 17 00:00:00 2001 From: WTFKr0 Date: Fri, 10 Nov 2017 17:09:13 +0100 Subject: [PATCH 5/5] Remove delete link for non admin users --- templates/library.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/templates/library.html b/templates/library.html index a264a6c..8bb0bc6 100644 --- a/templates/library.html +++ b/templates/library.html @@ -42,8 +42,10 @@

Library

{{duration $media.Length}} -    - + {{if $.IsAdmin}} +    + + {{end}} {{end}}