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).
This commit is contained in:
Gabriel Simmer 2020-04-24 09:01:53 +01:00
parent c559f28ebb
commit ce4ee64509
No known key found for this signature in database
GPG key ID: 33BA4D83B160A0A9
9 changed files with 89 additions and 75 deletions

View file

@ -1,6 +1,6 @@
version: 2.1 version: 2.1
orbs: orbs:
upx: gmem/upx@1.0.1 upx: circleci/upx@1.0.1
jobs: jobs:
build: build:
docker: docker:

View file

@ -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 tar -czf build/tars/nas-$(NAS_VERSION)-x86.tar.gz build/assets build/bin/nas README.md LICENSE
clean: clean:
rm -rf build rm -rf build

View file

@ -2,11 +2,15 @@ package authentication
import ( import (
"fmt" "fmt"
"github.com/Nerzal/gocloak/v5" "github.com/Nerzal/gocloak/v5"
) )
// AuthConfig contains the configuration for the IdP.
var AuthConfig map[string]string 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) { func HasAuth(accessToken string) (success bool) {
client := gocloak.NewClient(AuthConfig["provider_url"]) client := gocloak.NewClient(AuthConfig["provider_url"])
_, err := client.GetUserInfo(accessToken, AuthConfig["realm"]) _, err := client.GetUserInfo(accessToken, AuthConfig["realm"])
@ -16,8 +20,9 @@ func HasAuth(accessToken string) (success bool) {
return true return true
} }
// GetLoginLink generates a redirect link to the IdP login page.
func GetLoginLink() (url string) { 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" 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"]) authURL := fmt.Sprintf(baseString, AuthConfig["provider_url"], AuthConfig["realm"], AuthConfig["redirect_base_url"])
return authUrl return authURL
} }

View file

@ -262,7 +262,7 @@ func (bp *BackblazeProvider) ObjectInfo(path string) (bool, bool, string) {
// Therefore, we can assume everything is a file ;) // Therefore, we can assume everything is a file ;)
// TODO: Return true value. // TODO: Return true value.
isDir := path == "" isDir := path == ""
return true, isDir, FILE_IS_REMOTE return true, isDir, FileIsRemote
} }
func (bp *BackblazeProvider) CreateDirectory(path string) bool { func (bp *BackblazeProvider) CreateDirectory(path string) bool {

View file

@ -73,13 +73,13 @@ func (dp *DiskProvider) ObjectInfo(path string) (bool, bool, string) {
fileStat, err := os.Stat(rp) fileStat, err := os.Stat(rp)
if err != nil { if err != nil {
fmt.Printf("error gather stats for file %v: %v", rp, err.Error()) 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() { 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 { func (dp *DiskProvider) CreateDirectory(path string) bool {

View file

@ -4,9 +4,14 @@ import (
"io" "io"
) )
const FILE_IS_REMOTE = "remote" // FileIsRemote denotes whether file is a remote file.
const FILE_IS_LOCAL = "local" 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 { type FileProvider struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Provider string `yaml:"provider"` Provider string `yaml:"provider"`
@ -15,72 +20,72 @@ type FileProvider struct {
Config map[string]string `yaml:"config"` Config map[string]string `yaml:"config"`
} }
// Directory contains the path and a collection of FileInfos.
type Directory struct { type Directory struct {
Path string Path string
Files []FileInfo Files []FileInfo
} }
// FileInfo describes a single file or directory, doing it's best to
// figure out the extension as well.
type FileInfo struct { type FileInfo struct {
IsDirectory bool IsDirectory bool
Name string Name string
Extension string Extension string
} }
type FileContents struct { // FileProviderInterface provides some sane default functions.
Content []byte
IsUrl bool
}
type FileProviderInterface interface { type FileProviderInterface interface {
// Called on initial startup of the application.
Setup(args map[string]string) (ok bool) Setup(args map[string]string) (ok bool)
// Fetches the contents of a "directory".
GetDirectory(path string) (directory Directory) GetDirectory(path string) (directory Directory)
// Builds a path to a file, for serving.
FilePath(path string) (realpath string) FilePath(path string) (realpath string)
// Fetch and pass along a remote file directly to the response writer.
RemoteFile(path string, writer io.Writer) RemoteFile(path string, writer io.Writer)
// Save a file from an io.Reader.
SaveFile(file io.Reader, filename string, path string) (ok bool) 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) ObjectInfo(path string) (exists bool, isDir bool, location string)
// Create a directory if possible, returns the result.
CreateDirectory(path string) (ok bool) CreateDirectory(path string) (ok bool)
// Delete a file or directory.
Delete(path string) (ok bool) Delete(path string) (ok bool)
} }
/** DO NOT USE THESE DEFAULTS **/ /** 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 { func (f FileProvider) Setup(args map[string]string) bool {
return false return false
} }
// GetDirectory fetches a directory's contents.
func (f FileProvider) GetDirectory(path string) Directory { func (f FileProvider) GetDirectory(path string) Directory {
return 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 { func (f FileProvider) FilePath(path string) string {
return "" return ""
} }
// RemoteFile will bypass http.ServeContent() and instead write directly to the response.
func (f FileProvider) RemoteFile(path string, writer io.Writer) { func (f FileProvider) RemoteFile(path string, writer io.Writer) {
return 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 { func (f FileProvider) SaveFile(file io.Reader, filename string, path string) bool {
return false 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) { func (f FileProvider) ObjectInfo(path string) (bool, bool, string) {
return false, false, "" return false, false, ""
} }
// CreateDirectory will create a directory on services that support it.
func (f FileProvider) CreateDirectory(path string) bool { func (f FileProvider) CreateDirectory(path string) bool {
return false return false
} }
// Delete simply deletes a file. This is expected to be a destructive action by default.
func (f FileProvider) Delete(path string) bool { func (f FileProvider) Delete(path string) bool {
return false return false
} }

View file

@ -2,15 +2,18 @@ package router
import ( import (
"fmt" "fmt"
"github.com/gmemstr/nas/authentication"
"io/ioutil" "io/ioutil"
"net/http" "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 var AuthEnabled bool = true
func requiresAuth() Handler { func requiresAuth() handler {
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError {
if !AuthEnabled { if !AuthEnabled {
return nil return nil
} }
@ -20,7 +23,7 @@ func requiresAuth() Handler {
fmt.Println("Error", err.Error()) fmt.Println("Error", err.Error())
} }
http.Redirect(w, r, authentication.GetLoginLink(), 307) http.Redirect(w, r, authentication.GetLoginLink(), 307)
return &HTTPError{ return &httpError{
Message: "Unauthorized! Redirecting to /login", Message: "Unauthorized! Redirecting to /login",
StatusCode: http.StatusTemporaryRedirect, StatusCode: http.StatusTemporaryRedirect,
} }
@ -29,8 +32,8 @@ func requiresAuth() Handler {
} }
} }
func callbackAuth() Handler { func callbackAuth() handler {
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError {
// Translate callback GET to POST to set cookie, then redirect. // Translate callback GET to POST to set cookie, then redirect.
if r.Method == "GET" { if r.Method == "GET" {
javascript := ` javascript := `
@ -51,4 +54,4 @@ func callbackAuth() Handler {
return nil return nil
} }
} }

View file

@ -13,8 +13,8 @@ import (
"time" "time"
) )
func HandleProvider() Handler { func handleProvider() handler {
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError {
vars := mux.Vars(r) vars := mux.Vars(r)
providerCodename := vars["provider"] providerCodename := vars["provider"]
providerCodename = strings.Replace(providerCodename, "/", "", -1) providerCodename = strings.Replace(providerCodename, "/", "", -1)
@ -24,14 +24,14 @@ func HandleProvider() Handler {
if r.Method == "GET" { if r.Method == "GET" {
filename, err := url.QueryUnescape(vars["file"]) filename, err := url.QueryUnescape(vars["file"])
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error determining filetype for %s\n", filename), Message: fmt.Sprintf("error determining filetype for %s\n", filename),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
} }
ok, isDir, location := provider.ObjectInfo(filename) ok, isDir, location := provider.ObjectInfo(filename)
if !ok { if !ok {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error locating file %s\n", filename), Message: fmt.Sprintf("error locating file %s\n", filename),
StatusCode: http.StatusNotFound, StatusCode: http.StatusNotFound,
} }
@ -42,7 +42,7 @@ func HandleProvider() Handler {
data, err := json.Marshal(fileList) data, err := json.Marshal(fileList)
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error fetching filelisting for %s\n", vars["file"]), Message: fmt.Sprintf("error fetching filelisting for %s\n", vars["file"]),
StatusCode: http.StatusNotFound, StatusCode: http.StatusNotFound,
} }
@ -52,12 +52,12 @@ func HandleProvider() Handler {
} }
// If the file is local, attempt to use http.ServeContent for correct headers. // 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) rp := provider.FilePath(filename)
if rp != "" { if rp != "" {
f, err := os.Open(rp) f, err := os.Open(rp)
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error opening file %s\n", rp), Message: fmt.Sprintf("error opening file %s\n", rp),
StatusCode: http.StatusInternalServerError, 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. // 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. // 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) provider.RemoteFile(filename, w)
} }
return nil return nil
@ -81,7 +81,7 @@ func HandleProvider() Handler {
dirname := vars["file"] dirname := vars["file"]
success := provider.CreateDirectory(dirname) success := provider.CreateDirectory(dirname)
if !success { if !success {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error creating directory %s\n", dirname), Message: fmt.Sprintf("error creating directory %s\n", dirname),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
@ -92,7 +92,7 @@ func HandleProvider() Handler {
err := r.ParseMultipartForm(32 << 20) err := r.ParseMultipartForm(32 << 20)
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error parsing form for %s\n", vars["file"]), Message: fmt.Sprintf("error parsing form for %s\n", vars["file"]),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
@ -102,7 +102,7 @@ func HandleProvider() Handler {
success := provider.SaveFile(file, handler.Filename, vars["file"]) success := provider.SaveFile(file, handler.Filename, vars["file"])
if !success { if !success {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error saving file %s\n", vars["file"]), Message: fmt.Sprintf("error saving file %s\n", vars["file"]),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
@ -115,7 +115,7 @@ func HandleProvider() Handler {
path := vars["file"] path := vars["file"]
success := provider.Delete(path) success := provider.Delete(path)
if !success { if !success {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error deleting %s\n", path), Message: fmt.Sprintf("error deleting %s\n", path),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
@ -127,16 +127,16 @@ func HandleProvider() Handler {
} }
} }
func ListProviders() Handler { func listProviders() handler {
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError {
var providers []string var providers []string
for v, _ := range files.ProviderConfig { for v := range files.ProviderConfig {
providers = append(providers, v) providers = append(providers, v)
} }
sort.Strings(providers) sort.Strings(providers)
data, err := json.Marshal(providers) data, err := json.Marshal(providers)
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error provider listing"), Message: fmt.Sprintf("error provider listing"),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }

View file

@ -2,27 +2,29 @@ package router
import ( import (
"fmt" "fmt"
"github.com/gorilla/mux"
"io" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv" "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 { type httpError struct {
Message string Message string
StatusCode int StatusCode int
} }
// Context contains any information to be shared with middlewares. type requestContext struct{}
type Context 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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
context := &Context{} context := &requestContext{}
for _, handler := range handlers { for _, handler := range handlers {
err := handler(context, w, r) err := handler(context, w, r)
if err != nil { if err != nil {
@ -34,8 +36,7 @@ func Handle(handlers ...Handler) http.Handler {
}) })
} }
// Init initializes the main router and all routes for the application.
// Actual router, define endpoints here.
func Init() *mux.Router { func Init() *mux.Router {
r := mux.NewRouter() 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")))) r.PathPrefix("/icons/").Handler(http.StripPrefix("/icons/", http.FileServer(http.Dir("assets/web/icons"))))
// Paths that require specific handlers // Paths that require specific handlers
r.Handle("/", Handle( r.Handle("/", handle(
requiresAuth(), requiresAuth(),
rootHandler(), rootHandler(),
)).Methods("GET") )).Methods("GET")
// File & Provider API // File & Provider API
r.Handle("/api/providers", Handle( r.Handle("/api/providers", handle(
requiresAuth(), requiresAuth(),
ListProviders(), listProviders(),
)).Methods("GET") )).Methods("GET")
r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle( r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, handle(
requiresAuth(), requiresAuth(),
HandleProvider(), handleProvider(),
)).Methods("GET", "POST") )).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(), requiresAuth(),
HandleProvider(), handleProvider(),
)).Methods("GET", "POST", "DELETE") )).Methods("GET", "POST", "DELETE")
// Auth API & Endpoints // Auth API & Endpoints
r.Handle(`/api/auth/callback`, Handle( r.Handle(`/api/auth/callback`, handle(
callbackAuth(), callbackAuth(),
)).Methods("GET", "POST") )).Methods("GET", "POST")
@ -76,11 +77,11 @@ func Init() *mux.Router {
} }
// Handles serving index page. // Handles serving index page.
func rootHandler() Handler { func rootHandler() handler {
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError { return func(context *requestContext, w http.ResponseWriter, r *http.Request) *httpError {
f, err := os.Open("assets/web/index.html") f, err := os.Open("assets/web/index.html")
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error serving index page from assets/web"), Message: fmt.Sprintf("error serving index page from assets/web"),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
@ -89,7 +90,7 @@ func rootHandler() Handler {
defer f.Close() defer f.Close()
stats, err := f.Stat() stats, err := f.Stat()
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error serving index page from assets/web"), Message: fmt.Sprintf("error serving index page from assets/web"),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
@ -99,11 +100,11 @@ func rootHandler() Handler {
_, err = io.Copy(w, f) _, err = io.Copy(w, f)
if err != nil { if err != nil {
return &HTTPError{ return &httpError{
Message: fmt.Sprintf("error serving index page from assets/web"), Message: fmt.Sprintf("error serving index page from assets/web"),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
} }
return nil return nil
} }
} }