From 346cec31300b5136aa2aa97544304a1574ec17b3 Mon Sep 17 00:00:00 2001 From: Mickael Kerjean Date: Sat, 1 Sep 2018 02:45:51 +1000 Subject: [PATCH] maintenance (server): backend refactoring --- server/common/constants.go | 6 +++ server/common/crypto.go | 51 +++++++++++++++++++ .../{router/utils.go => common/response.go} | 10 ++-- server/{router => ctrl}/files.go | 50 +++++++++--------- server/{router => ctrl}/session.go | 39 +++++++------- server/router/config.go | 2 +- server/router/index.go | 5 +- server/router/middleware.go | 50 +----------------- 8 files changed, 110 insertions(+), 103 deletions(-) create mode 100644 server/common/constants.go create mode 100644 server/common/crypto.go rename server/{router/utils.go => common/response.go} (82%) rename server/{router => ctrl}/files.go (81%) rename server/{router => ctrl}/session.go (71%) diff --git a/server/common/constants.go b/server/common/constants.go new file mode 100644 index 00000000..a86c0daa --- /dev/null +++ b/server/common/constants.go @@ -0,0 +1,6 @@ +package common + +const ( + COOKIE_NAME = "auth" + COOKIE_PATH = "/api/" +) diff --git a/server/common/crypto.go b/server/common/crypto.go new file mode 100644 index 00000000..412897b9 --- /dev/null +++ b/server/common/crypto.go @@ -0,0 +1,51 @@ +package common + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/json" + "io" +) + +func Encrypt(keystr string, text map[string]string) (string, error) { + key := []byte(keystr) + plaintext, err := json.Marshal(text) + if err != nil { + return "", NewError("json marshalling: "+err.Error(), 500) + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", NewError("encryption issue (cipher): "+err.Error(), 500) + } + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", NewError("encryption issue: "+err.Error(), 500) + } + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + return base64.URLEncoding.EncodeToString(ciphertext), nil +} + +func Decrypt(keystr string, cryptoText string) (map[string]string, error) { + var raw map[string]string + + key := []byte(keystr) + ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) + block, err := aes.NewCipher(key) + + if err != nil || len(ciphertext) < aes.BlockSize { + return raw, NewError("Cipher is too short", 500) + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(ciphertext, ciphertext) + + json.Unmarshal(ciphertext, &raw) + return raw, nil +} diff --git a/server/router/utils.go b/server/common/response.go similarity index 82% rename from server/router/utils.go rename to server/common/response.go index 338f27f3..6294b0c0 100644 --- a/server/router/utils.go +++ b/server/common/response.go @@ -1,4 +1,4 @@ -package router +package common import ( "encoding/json" @@ -27,25 +27,25 @@ type APIErrorMessage struct { Message string `json:"message,omitempty"` } -func sendSuccessResult(res http.ResponseWriter, data interface{}) { +func SendSuccessResult(res http.ResponseWriter, data interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.Encode(APISuccessResult{"ok", data}) } -func sendSuccessResults(res http.ResponseWriter, data interface{}) { +func SendSuccessResults(res http.ResponseWriter, data interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.Encode(APISuccessResults{"ok", data}) } -func sendSuccessResultsWithMetadata(res http.ResponseWriter, data interface{}, p interface{}) { +func SendSuccessResultsWithMetadata(res http.ResponseWriter, data interface{}, p interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.Encode(APISuccessResultsWithMetadata{"ok", data, p}) } -func sendErrorResult(res http.ResponseWriter, err error) { +func SendErrorResult(res http.ResponseWriter, err error) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) obj, ok := err.(interface{ Status() int }) diff --git a/server/router/files.go b/server/ctrl/files.go similarity index 81% rename from server/router/files.go rename to server/ctrl/files.go index ca75972e..22b71c8d 100644 --- a/server/router/files.go +++ b/server/ctrl/files.go @@ -1,4 +1,4 @@ -package router +package ctrl import ( . "github.com/mickael-kerjean/nuage/server/common" @@ -20,13 +20,13 @@ type FileInfo struct { func FileLs(ctx App, res http.ResponseWriter, req *http.Request) { path, err := pathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } entries, err := ctx.Backend.Ls(path) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } @@ -52,13 +52,13 @@ func FileLs(ctx App, res http.ResponseWriter, req *http.Request) { if obj, ok := ctx.Backend.(interface{ Meta(path string) *Metadata }); ok { perms = obj.Meta(path) } - sendSuccessResultsWithMetadata(res, files, perms) + SendSuccessResultsWithMetadata(res, files, perms) } func FileCat(ctx App, res http.ResponseWriter, req *http.Request) { path, err := pathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } @@ -67,7 +67,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) { defer obj.Close() } if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } @@ -80,7 +80,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) { file, err = services.ProcessFileBeforeSend(file, &ctx, req, &res) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } io.Copy(res, file) @@ -89,13 +89,13 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) { func FileSave(ctx App, res http.ResponseWriter, req *http.Request) { path, err := pathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } file, _, err := req.FormFile("file") if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } defer file.Close() @@ -105,78 +105,78 @@ func FileSave(ctx App, res http.ResponseWriter, req *http.Request) { obj.Close() } if err != nil { - sendErrorResult(res, NewError(err.Error(), 403)) + SendErrorResult(res, NewError(err.Error(), 403)) return } - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } func FileMv(ctx App, res http.ResponseWriter, req *http.Request) { from, err := pathBuilder(ctx, req.URL.Query().Get("from")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } to, err := pathBuilder(ctx, req.URL.Query().Get("to")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } if from == "" || to == "" { - sendErrorResult(res, NewError("missing path parameter", 400)) + SendErrorResult(res, NewError("missing path parameter", 400)) return } err = ctx.Backend.Mv(from, to) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } func FileRm(ctx App, res http.ResponseWriter, req *http.Request) { path, err := pathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } err = ctx.Backend.Rm(path) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } func FileMkdir(ctx App, res http.ResponseWriter, req *http.Request) { path, err := pathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } err = ctx.Backend.Mkdir(path) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } func FileTouch(ctx App, res http.ResponseWriter, req *http.Request) { path, err := pathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } err = ctx.Backend.Touch(path) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } func pathBuilder(ctx App, path string) (string, error) { diff --git a/server/router/session.go b/server/ctrl/session.go similarity index 71% rename from server/router/session.go rename to server/ctrl/session.go index 5933f719..06c02199 100644 --- a/server/router/session.go +++ b/server/ctrl/session.go @@ -1,4 +1,4 @@ -package router +package ctrl import ( "github.com/mickael-kerjean/mux" @@ -8,33 +8,28 @@ import ( "time" ) -const ( - COOKIE_NAME = "auth" - COOKIE_PATH = "/api/" -) - func SessionIsValid(ctx App, res http.ResponseWriter, req *http.Request) { if ctx.Backend == nil { - sendSuccessResult(res, false) + SendSuccessResult(res, false) return } if _, err := ctx.Backend.Ls("/"); err != nil { - sendSuccessResult(res, false) + SendSuccessResult(res, false) return } home, _ := model.GetHome(ctx.Backend) if home == "" { - sendSuccessResult(res, true) + SendSuccessResult(res, true) return } - sendSuccessResult(res, true) + SendSuccessResult(res, true) } func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) { ctx.Body["timestamp"] = time.Now().String() backend, err := model.NewBackend(&ctx, ctx.Body) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } @@ -43,25 +38,25 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) { }); ok { err := obj.OAuthToken(&ctx.Body) if err != nil { - sendErrorResult(res, NewError("Can't authenticate (OAuth error)", 401)) + SendErrorResult(res, NewError("Can't authenticate (OAuth error)", 401)) return } backend, err = model.NewBackend(&ctx, ctx.Body) if err != nil { - sendErrorResult(res, NewError("Can't authenticate", 401)) + SendErrorResult(res, NewError("Can't authenticate", 401)) return } } home, err := model.GetHome(backend) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } - obfuscate, err := encrypt(ctx.Config.General.SecretKey, ctx.Body) + obfuscate, err := Encrypt(ctx.Config.General.SecretKey, ctx.Body) if err != nil { - sendErrorResult(res, NewError(err.Error(), 500)) + SendErrorResult(res, NewError(err.Error(), 500)) return } cookie := http.Cookie{ @@ -74,9 +69,9 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) { http.SetCookie(res, &cookie) if home == "" { - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } else { - sendSuccessResult(res, home) + SendSuccessResult(res, home) } } @@ -94,7 +89,7 @@ func SessionLogout(ctx App, res http.ResponseWriter, req *http.Request) { } http.SetCookie(res, &cookie) - sendSuccessResult(res, nil) + SendSuccessResult(res, nil) } func SessionOAuthBackend(ctx App, res http.ResponseWriter, req *http.Request) { @@ -104,13 +99,13 @@ func SessionOAuthBackend(ctx App, res http.ResponseWriter, req *http.Request) { } b, err := model.NewBackend(&ctx, a) if err != nil { - sendErrorResult(res, err) + SendErrorResult(res, err) return } obj, ok := b.(interface{ OAuthURL() string }) if ok == false { - sendErrorResult(res, NewError("No backend authentication ("+b.Info()+")", 500)) + SendErrorResult(res, NewError("No backend authentication ("+b.Info()+")", 500)) return } - sendSuccessResult(res, obj.OAuthURL()) + SendSuccessResult(res, obj.OAuthURL()) } diff --git a/server/router/config.go b/server/router/config.go index b406aed9..300a8692 100644 --- a/server/router/config.go +++ b/server/router/config.go @@ -8,7 +8,7 @@ import ( func ConfigHandler(ctx App, res http.ResponseWriter, req *http.Request) { c, err := ctx.Config.Export() if err != nil { - sendErrorResult(res, err) + res.Write([]byte("window.CONFIG = {}")) return } res.Write([]byte("window.CONFIG = ")) diff --git a/server/router/index.go b/server/router/index.go index 8242ab53..2995334e 100644 --- a/server/router/index.go +++ b/server/router/index.go @@ -3,6 +3,7 @@ package router import ( "github.com/mickael-kerjean/mux" . "github.com/mickael-kerjean/nuage/server/common" + . "github.com/mickael-kerjean/nuage/server/ctrl" "log" "net/http" "strconv" @@ -28,8 +29,8 @@ func Init(a *App) *http.Server { share := r.PathPrefix("/api/share").Subrouter() share.HandleFunc("", APIHandler(ShareList, *a)).Methods("GET") - share.HandleFunc("/{id}", APIHandler(ShareInsert, *a)).Methods("POST") - share.HandleFunc("/{id}", APIHandler(ShareInsert, *a)).Methods("DELETE") + share.HandleFunc("/{id}", APIHandler(ShareUpsert, *a)).Methods("POST") + share.HandleFunc("/{id}", APIHandler(ShareDelete, *a)).Methods("DELETE") r.HandleFunc("/api/config", CtxInjector(ConfigHandler, *a)) diff --git a/server/router/middleware.go b/server/router/middleware.go index f35bcf25..e09a33fd 100644 --- a/server/router/middleware.go +++ b/server/router/middleware.go @@ -2,14 +2,9 @@ package router import ( "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" "encoding/json" . "github.com/mickael-kerjean/nuage/server/common" "github.com/mickael-kerjean/nuage/server/model" - "io" "io/ioutil" "log" "net/http" @@ -42,7 +37,7 @@ func APIHandler(fn func(App, http.ResponseWriter, *http.Request), ctx App) http. func LoggedInOnly(fn func(App, http.ResponseWriter, *http.Request)) func(ctx App, res http.ResponseWriter, req *http.Request) { return func(ctx App, res http.ResponseWriter, req *http.Request) { if ctx.Backend == nil || ctx.Session == nil { - sendErrorResult(res, NewError("Forbidden", 403)) + SendErrorResult(res, NewError("Forbidden", 403)) return } fn(ctx, res, req) @@ -75,7 +70,7 @@ func extractSession(req *http.Request, ctx *App) (map[string]string, error) { if err != nil { return make(map[string]string), err } - return decrypt(ctx.Config.General.SecretKey, cookie.Value) + return Decrypt(ctx.Config.General.SecretKey, cookie.Value) } func extractBackend(req *http.Request, ctx *App) (IBackend, error) { @@ -120,47 +115,6 @@ func logPoint(req *http.Request, res *ResponseWriter, start time.Time, backendTy } } -func encrypt(keystr string, text map[string]string) (string, error) { - key := []byte(keystr) - plaintext, err := json.Marshal(text) - if err != nil { - return "", NewError("json marshalling: "+err.Error(), 500) - } - - block, err := aes.NewCipher(key) - if err != nil { - return "", NewError("encryption issue (cipher): "+err.Error(), 500) - } - ciphertext := make([]byte, aes.BlockSize+len(plaintext)) - iv := ciphertext[:aes.BlockSize] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return "", NewError("encryption issue: "+err.Error(), 500) - } - stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) - return base64.URLEncoding.EncodeToString(ciphertext), nil -} - -func decrypt(keystr string, cryptoText string) (map[string]string, error) { - var raw map[string]string - - key := []byte(keystr) - ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) - block, err := aes.NewCipher(key) - - if err != nil || len(ciphertext) < aes.BlockSize { - return raw, NewError("Cipher is too short", 500) - } - - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - stream := cipher.NewCFBDecrypter(block, iv) - stream.XORKeyStream(ciphertext, ciphertext) - - json.Unmarshal(ciphertext, &raw) - return raw, nil -} - type ResponseWriter struct { http.ResponseWriter status int