From ce4ee6450990671410814e396642a5e40e274ef7 Mon Sep 17 00:00:00 2001 From: Gabriel Simmer Date: Fri, 24 Apr 2020 09:01:53 +0100 Subject: [PATCH] Minor linting fixes & move to CircleCI/UPX orb. Some minor linting fixes and general style/scope changes, which should have no impact on the overall application. Also moved to the offical CircleCI UPX Orb, rather than my own (which is still maintained by me). --- .circleci/config.yml | 2 +- Makefile | 2 +- authentication/keycloak.go | 11 +++++--- files/backblaze.go | 2 +- files/disk.go | 6 ++--- files/fileprovider.go | 41 ++++++++++++++++-------------- router/authrouter.go | 17 +++++++------ router/filerouter.go | 32 ++++++++++++------------ router/router.go | 51 +++++++++++++++++++------------------- 9 files changed, 89 insertions(+), 75 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bf14b5e..a835281 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - upx: gmem/upx@1.0.1 + upx: circleci/upx@1.0.1 jobs: build: docker: diff --git a/Makefile b/Makefile index 6a2c6e5..0b222a0 100644 --- a/Makefile +++ b/Makefile @@ -32,4 +32,4 @@ dist: clean make_build_dir small small_pi tar -czf build/tars/nas-$(NAS_VERSION)-x86.tar.gz build/assets build/bin/nas README.md LICENSE clean: - rm -rf build \ No newline at end of file + rm -rf build diff --git a/authentication/keycloak.go b/authentication/keycloak.go index d026b53..5b9408a 100644 --- a/authentication/keycloak.go +++ b/authentication/keycloak.go @@ -2,11 +2,15 @@ package authentication import ( "fmt" + "github.com/Nerzal/gocloak/v5" ) +// AuthConfig contains the configuration for the IdP. var AuthConfig map[string]string +// HasAuth checks the passed token against the IdP, and returns true +// if the IdP can return the user's info, false if not. func HasAuth(accessToken string) (success bool) { client := gocloak.NewClient(AuthConfig["provider_url"]) _, err := client.GetUserInfo(accessToken, AuthConfig["realm"]) @@ -16,8 +20,9 @@ func HasAuth(accessToken string) (success bool) { return true } +// GetLoginLink generates a redirect link to the IdP login page. func GetLoginLink() (url string) { baseString := "%v/auth/realms/%v/protocol/openid-connect/auth?client_id=account&response_mode=fragment&response_type=token&login=true&redirect_uri=%v/api/auth/callback" - authUrl := fmt.Sprintf(baseString, AuthConfig["provider_url"], AuthConfig["realm"], AuthConfig["redirect_base_url"]) - return authUrl -} \ No newline at end of file + authURL := fmt.Sprintf(baseString, AuthConfig["provider_url"], AuthConfig["realm"], AuthConfig["redirect_base_url"]) + return authURL +} diff --git a/files/backblaze.go b/files/backblaze.go index 3c8b7fd..881ffb2 100644 --- a/files/backblaze.go +++ b/files/backblaze.go @@ -262,7 +262,7 @@ func (bp *BackblazeProvider) ObjectInfo(path string) (bool, bool, string) { // Therefore, we can assume everything is a file ;) // TODO: Return true value. isDir := path == "" - return true, isDir, FILE_IS_REMOTE + return true, isDir, FileIsRemote } func (bp *BackblazeProvider) CreateDirectory(path string) bool { diff --git a/files/disk.go b/files/disk.go index b2f44e5..3b3e048 100644 --- a/files/disk.go +++ b/files/disk.go @@ -73,13 +73,13 @@ func (dp *DiskProvider) ObjectInfo(path string) (bool, bool, string) { fileStat, err := os.Stat(rp) if err != nil { fmt.Printf("error gather stats for file %v: %v", rp, err.Error()) - return false, false, FILE_IS_LOCAL + return false, false, FileIsLocal } if fileStat.IsDir() { - return true, true, FILE_IS_LOCAL + return true, true, FileIsLocal } - return true, false, FILE_IS_LOCAL + return true, false, FileIsLocal } func (dp *DiskProvider) CreateDirectory(path string) bool { diff --git a/files/fileprovider.go b/files/fileprovider.go index 42108e4..94aea3d 100644 --- a/files/fileprovider.go +++ b/files/fileprovider.go @@ -4,9 +4,14 @@ import ( "io" ) -const FILE_IS_REMOTE = "remote" -const FILE_IS_LOCAL = "local" +// FileIsRemote denotes whether file is a remote file. +const FileIsRemote = "remote" +// FileIsLocal denotes whether file is a local file. +const FileIsLocal = "local" + +// FileProvider aggregates some very basic properties for authentication and +// provider decoding. type FileProvider struct { Name string `yaml:"name"` Provider string `yaml:"provider"` @@ -15,72 +20,72 @@ type FileProvider struct { Config map[string]string `yaml:"config"` } +// Directory contains the path and a collection of FileInfos. type Directory struct { Path string Files []FileInfo } +// FileInfo describes a single file or directory, doing it's best to +// figure out the extension as well. type FileInfo struct { IsDirectory bool Name string Extension string } -type FileContents struct { - Content []byte - IsUrl bool -} - +// FileProviderInterface provides some sane default functions. type FileProviderInterface interface { - // Called on initial startup of the application. Setup(args map[string]string) (ok bool) - // Fetches the contents of a "directory". GetDirectory(path string) (directory Directory) - // Builds a path to a file, for serving. FilePath(path string) (realpath string) - // Fetch and pass along a remote file directly to the response writer. RemoteFile(path string, writer io.Writer) - // Save a file from an io.Reader. SaveFile(file io.Reader, filename string, path string) (ok bool) - // Fetch the info for an object given a path to if the file exists and location. - // Should return whether the path exists, if the path is a directory, and if it lives on disk. - // (see constants defined: `FILE_IS_REMOTE` and `FILE_IS_LOCAL`) ObjectInfo(path string) (exists bool, isDir bool, location string) - // Create a directory if possible, returns the result. CreateDirectory(path string) (ok bool) - // Delete a file or directory. Delete(path string) (ok bool) } /** DO NOT USE THESE DEFAULTS **/ + +// Setup runs when the application starts up, and allows for things like authentication. func (f FileProvider) Setup(args map[string]string) bool { return false } +// GetDirectory fetches a directory's contents. func (f FileProvider) GetDirectory(path string) Directory { return Directory{} } +// FilePath returns the path to the file, whether it be a URL or local file path. func (f FileProvider) FilePath(path string) string { return "" } +// RemoteFile will bypass http.ServeContent() and instead write directly to the response. func (f FileProvider) RemoteFile(path string, writer io.Writer) { return } +// SaveFile will save a file with the contents of the io.Reader at the path specified. func (f FileProvider) SaveFile(file io.Reader, filename string, path string) bool { return false } +// ObjectInfo will return the info for an object given a path to if the file exists and location. +// Should return whether the path exists, if the path is a directory, and if it lives on disk. +// (see constants defined: `FILE_IS_REMOTE` and `FILE_IS_LOCAL`) func (f FileProvider) ObjectInfo(path string) (bool, bool, string) { return false, false, "" } +// CreateDirectory will create a directory on services that support it. func (f FileProvider) CreateDirectory(path string) bool { return false } +// Delete simply deletes a file. This is expected to be a destructive action by default. func (f FileProvider) Delete(path string) bool { return false -} \ No newline at end of file +} diff --git a/router/authrouter.go b/router/authrouter.go index 934fe39..7657626 100644 --- a/router/authrouter.go +++ b/router/authrouter.go @@ -2,15 +2,18 @@ package router import ( "fmt" - "github.com/gmemstr/nas/authentication" "io/ioutil" "net/http" + + "github.com/gmemstr/nas/authentication" ) +// AuthEnabled is a global variable that determines whether we were +// able to set up an authentication method (e.g Keycloak). var AuthEnabled bool = true -func requiresAuth() Handler { - return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { +func requiresAuth() handler { + return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError { if !AuthEnabled { return nil } @@ -20,7 +23,7 @@ func requiresAuth() Handler { fmt.Println("Error", err.Error()) } http.Redirect(w, r, authentication.GetLoginLink(), 307) - return &HTTPError{ + return &httpError{ Message: "Unauthorized! Redirecting to /login", StatusCode: http.StatusTemporaryRedirect, } @@ -29,8 +32,8 @@ func requiresAuth() Handler { } } -func callbackAuth() Handler { - return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { +func callbackAuth() handler { + return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError { // Translate callback GET to POST to set cookie, then redirect. if r.Method == "GET" { javascript := ` @@ -51,4 +54,4 @@ func callbackAuth() Handler { return nil } -} \ No newline at end of file +} diff --git a/router/filerouter.go b/router/filerouter.go index 66143ac..f127f47 100644 --- a/router/filerouter.go +++ b/router/filerouter.go @@ -13,8 +13,8 @@ import ( "time" ) -func HandleProvider() Handler { - return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { +func handleProvider() handler { + return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError { vars := mux.Vars(r) providerCodename := vars["provider"] providerCodename = strings.Replace(providerCodename, "/", "", -1) @@ -24,14 +24,14 @@ func HandleProvider() Handler { if r.Method == "GET" { filename, err := url.QueryUnescape(vars["file"]) if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error determining filetype for %s\n", filename), StatusCode: http.StatusInternalServerError, } } ok, isDir, location := provider.ObjectInfo(filename) if !ok { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error locating file %s\n", filename), StatusCode: http.StatusNotFound, } @@ -42,7 +42,7 @@ func HandleProvider() Handler { data, err := json.Marshal(fileList) if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error fetching filelisting for %s\n", vars["file"]), StatusCode: http.StatusNotFound, } @@ -52,12 +52,12 @@ func HandleProvider() Handler { } // If the file is local, attempt to use http.ServeContent for correct headers. - if location == files.FILE_IS_LOCAL { + if location == files.FileIsLocal { rp := provider.FilePath(filename) if rp != "" { f, err := os.Open(rp) if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error opening file %s\n", rp), StatusCode: http.StatusInternalServerError, } @@ -67,7 +67,7 @@ func HandleProvider() Handler { } // If the file is remote, then delegate the writing to the response to the provider. // This isn't a great workaround, but avoids caching the whole file in mem or on disk. - if location == files.FILE_IS_REMOTE { + if location == files.FileIsRemote { provider.RemoteFile(filename, w) } return nil @@ -81,7 +81,7 @@ func HandleProvider() Handler { dirname := vars["file"] success := provider.CreateDirectory(dirname) if !success { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error creating directory %s\n", dirname), StatusCode: http.StatusInternalServerError, } @@ -92,7 +92,7 @@ func HandleProvider() Handler { err := r.ParseMultipartForm(32 << 20) if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error parsing form for %s\n", vars["file"]), StatusCode: http.StatusInternalServerError, } @@ -102,7 +102,7 @@ func HandleProvider() Handler { success := provider.SaveFile(file, handler.Filename, vars["file"]) if !success { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error saving file %s\n", vars["file"]), StatusCode: http.StatusInternalServerError, } @@ -115,7 +115,7 @@ func HandleProvider() Handler { path := vars["file"] success := provider.Delete(path) if !success { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error deleting %s\n", path), StatusCode: http.StatusInternalServerError, } @@ -127,16 +127,16 @@ func HandleProvider() Handler { } } -func ListProviders() Handler { - return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { +func listProviders() handler { + return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError { var providers []string - for v, _ := range files.ProviderConfig { + for v := range files.ProviderConfig { providers = append(providers, v) } sort.Strings(providers) data, err := json.Marshal(providers) if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error provider listing"), StatusCode: http.StatusInternalServerError, } diff --git a/router/router.go b/router/router.go index 18961ae..3ad6d88 100644 --- a/router/router.go +++ b/router/router.go @@ -2,27 +2,29 @@ package router import ( "fmt" - "github.com/gorilla/mux" "io" "log" "net/http" "os" "strconv" + + "github.com/gorilla/mux" ) -type Handler func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError +type handler func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError -type HTTPError struct { - Message string +type httpError struct { + Message string StatusCode int } -// Context contains any information to be shared with middlewares. -type Context struct {} +type requestContext struct{} -func Handle(handlers ...Handler) http.Handler { +// Loop through passed functions and execute them, passing through the current +// requestContext, response writer and request reader. +func handle(handlers ...handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - context := &Context{} + context := &requestContext{} for _, handler := range handlers { err := handler(context, w, r) if err != nil { @@ -34,8 +36,7 @@ func Handle(handlers ...Handler) http.Handler { }) } - -// Actual router, define endpoints here. +// Init initializes the main router and all routes for the application. func Init() *mux.Router { r := mux.NewRouter() @@ -46,29 +47,29 @@ func Init() *mux.Router { r.PathPrefix("/icons/").Handler(http.StripPrefix("/icons/", http.FileServer(http.Dir("assets/web/icons")))) // Paths that require specific handlers - r.Handle("/", Handle( + r.Handle("/", handle( requiresAuth(), rootHandler(), )).Methods("GET") // File & Provider API - r.Handle("/api/providers", Handle( + r.Handle("/api/providers", handle( requiresAuth(), - ListProviders(), + listProviders(), )).Methods("GET") - - r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle( + + r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, handle( requiresAuth(), - HandleProvider(), + handleProvider(), )).Methods("GET", "POST") - r.Handle(`/api/files/{provider:[a-zA-Z0-9]+}/{file:.+}`, Handle( + r.Handle(`/api/files/{provider:[a-zA-Z0-9]+}/{file:.+}`, handle( requiresAuth(), - HandleProvider(), + handleProvider(), )).Methods("GET", "POST", "DELETE") // Auth API & Endpoints - r.Handle(`/api/auth/callback`, Handle( + r.Handle(`/api/auth/callback`, handle( callbackAuth(), )).Methods("GET", "POST") @@ -76,11 +77,11 @@ func Init() *mux.Router { } // Handles serving index page. -func rootHandler() Handler { - return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { +func rootHandler() handler { + return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError { f, err := os.Open("assets/web/index.html") if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error serving index page from assets/web"), StatusCode: http.StatusInternalServerError, } @@ -89,7 +90,7 @@ func rootHandler() Handler { defer f.Close() stats, err := f.Stat() if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error serving index page from assets/web"), StatusCode: http.StatusInternalServerError, } @@ -99,11 +100,11 @@ func rootHandler() Handler { _, err = io.Copy(w, f) if err != nil { - return &HTTPError{ + return &httpError{ Message: fmt.Sprintf("error serving index page from assets/web"), StatusCode: http.StatusInternalServerError, } } return nil } -} \ No newline at end of file +}