From 20818dca935cd8b7c922e1bb04ffdaf0f10a2022 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 20 Aug 2017 09:21:36 +0100 Subject: [PATCH] FS as an interface, close #205 --- bolt/users.go | 15 ++++++++++++--- file.go | 2 +- filemanager.go | 39 +++++++++++++++++++++++++++++++-------- http/auth.go | 4 ++-- http/resource.go | 4 ++-- http/share.go | 4 ++-- http/users.go | 28 ++++++++++++---------------- http/websockets.go | 4 ++-- 8 files changed, 64 insertions(+), 36 deletions(-) diff --git a/bolt/users.go b/bolt/users.go index b92ce1a4..2c12f69a 100644 --- a/bolt/users.go +++ b/bolt/users.go @@ -11,7 +11,7 @@ type UsersStore struct { DB *storm.DB } -func (u UsersStore) Get(id int) (*fm.User, error) { +func (u UsersStore) Get(id int, builder fm.FSBuilder) (*fm.User, error) { var us *fm.User err := u.DB.One("ID", id, us) if err == storm.ErrNotFound { @@ -22,12 +22,21 @@ func (u UsersStore) Get(id int) (*fm.User, error) { return nil, err } - return &fm.User{}, nil + us.FileSystem = builder(us.Scope) + return us, nil } -func (u UsersStore) Gets() ([]*fm.User, error) { +func (u UsersStore) Gets(builder fm.FSBuilder) ([]*fm.User, error) { var us []*fm.User err := u.DB.All(us) + if err != nil { + return us, err + } + + for _, user := range us { + user.FileSystem = builder(user.Scope) + } + return us, err } diff --git a/file.go b/file.go index 8d87cfa5..3d167bb5 100644 --- a/file.go +++ b/file.go @@ -79,7 +79,7 @@ func GetInfo(url *url.URL, c *FileManager, u *User) (*File, error) { i := &File{ URL: "/files" + url.String(), VirtualPath: url.Path, - Path: filepath.Join(string(u.FileSystem), url.Path), + Path: filepath.Join(u.Scope, url.Path), } info, err := u.FileSystem.Stat(url.Path) diff --git a/filemanager.go b/filemanager.go index 31306539..87f398aa 100644 --- a/filemanager.go +++ b/filemanager.go @@ -89,7 +89,7 @@ var ( // FileManager is a file manager instance. It should be creating using the // 'New' function and not directly. type FileManager struct { - // Job cron. + // Cron job to manage schedulings. Cron *cron.Cron // The key used to sign the JWT tokens. @@ -98,6 +98,10 @@ type FileManager struct { // The static assets. Assets *rice.Box + // The Store is used to manage users, shareable links and + // other stuff that is saved on the database. + Store *Store + // PrefixURL is a part of the URL that is already trimmed from the request URL before it // arrives to our handlers. It may be useful when using File Manager as a middleware // such as in caddy-filemanager plugin. It is only useful in certain situations. @@ -121,14 +125,20 @@ type FileManager struct { // A map of events to a slice of commands. Commands map[string][]string - Store *Store + // NewFS should build a new file system for a given path. + NewFS FSBuilder } // Command is a command function. type Command func(r *http.Request, m *FileManager, u *User) error -// Load loads the configuration from the database. -func (m *FileManager) Load() error { +// FSBuilder is the File System Builder. +type FSBuilder func(scope string) FileSystem + +// Setup loads the configuration from the database and configures +// the Assets and the Cron job. It must always be run after +// creating a File Manager object. +func (m *FileManager) Setup() error { // Creates a new File Manager instance with the Users // map and Assets box. m.Assets = rice.MustFindBox("./assets/dist") @@ -170,7 +180,7 @@ func (m *FileManager) Load() error { } // Tries to fetch the users from the database. - users, err := m.Store.Users.Gets() + users, err := m.Store.Users.Gets(m.NewFS) if err != nil { return err } @@ -353,8 +363,11 @@ type User struct { // Tells if this user is an admin. Admin bool `json:"admin"` + // Scope is the path the user has access to. + Scope string `json:"filesystem"` + // FileSystem is the virtual file system the user has access. - FileSystem fileutils.Dir `json:"filesystem"` + FileSystem FileSystem `json:"-"` // Rules is an array of access and deny rules. Rules []*Rule `json:"rules"` @@ -445,8 +458,8 @@ type Store struct { // UsersStore is the interface to manage users. type UsersStore interface { - Get(id int) (*User, error) - Gets() ([]*User, error) + Get(id int, builder FSBuilder) (*User, error) + Gets(builder FSBuilder) ([]*User, error) Save(u *User) error Update(u *User, fields ...string) error Delete(id int) error @@ -479,6 +492,16 @@ type StaticGen interface { Publish(c *Context, w http.ResponseWriter, r *http.Request) (int, error) } +// FileSystem is the interface to work with the file system. +type FileSystem interface { + Mkdir(name string, perm os.FileMode) error + OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) + RemoveAll(name string) error + Rename(oldName, newName string) error + Stat(name string) (os.FileInfo, error) + Copy(src, dst string) error +} + // Context contains the needed information to make handlers work. type Context struct { *FileManager diff --git a/http/auth.go b/http/auth.go index ffade7ae..cf23d07c 100644 --- a/http/auth.go +++ b/http/auth.go @@ -30,7 +30,7 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er } // Checks if the user exists. - u, err := c.Store.Users.Get(cred.ID) + u, err := c.Store.Users.Get(cred.ID, c.NewFS) if err != nil { return http.StatusForbidden, nil } @@ -137,7 +137,7 @@ func validateAuth(c *fm.Context, r *http.Request) (bool, *fm.User) { return false, nil } - u, err := c.Store.Users.Get(claims.User.ID) + u, err := c.Store.Users.Get(claims.User.ID, c.NewFS) if err != nil { return false, nil } diff --git a/http/resource.go b/http/resource.go index f983f05f..39e9d0de 100644 --- a/http/resource.go +++ b/http/resource.go @@ -37,7 +37,7 @@ func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int return resourceDeleteHandler(c, w, r) case http.MethodPut: // Before save command handler. - path := filepath.Join(string(c.User.FileSystem), r.URL.Path) + path := filepath.Join(c.User.Scope, r.URL.Path) if err := c.Runner("before_save", path); err != nil { return http.StatusInternalServerError, err } @@ -253,7 +253,7 @@ func resourcePublishSchedule(c *fm.Context, w http.ResponseWriter, r *http.Reque } func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) { - path := filepath.Join(string(c.User.FileSystem), r.URL.Path) + path := filepath.Join(c.User.Scope, r.URL.Path) // Before save command handler. if err := c.Runner("before_publish", path); err != nil { diff --git a/http/share.go b/http/share.go index 6a610e76..d8321b95 100644 --- a/http/share.go +++ b/http/share.go @@ -28,7 +28,7 @@ func shareHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, e } func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) { - path := filepath.Join(string(c.User.FileSystem), r.URL.Path) + path := filepath.Join(c.User.Scope, r.URL.Path) s, err := c.Store.Share.GetByPath(path) if err == storm.ErrNotFound { return http.StatusNotFound, nil @@ -49,7 +49,7 @@ func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int } func sharePostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) { - path := filepath.Join(string(c.User.FileSystem), r.URL.Path) + path := filepath.Join(c.User.Scope, r.URL.Path) var s *fm.ShareLink expire := r.URL.Query().Get("expires") diff --git a/http/users.go b/http/users.go index 58fb0ed6..c2daf077 100644 --- a/http/users.go +++ b/http/users.go @@ -64,7 +64,7 @@ func getUserID(r *http.Request) (int, error) { // getUser returns the user which is present in the request // body. If the body is empty or the JSON is invalid, it // returns an fm.Error. -func getUser(r *http.Request) (*fm.User, string, error) { +func getUser(c *fm.Context, r *http.Request) (*fm.User, string, error) { // Checks if the request body is empty. if r.Body == nil { return nil, "", fm.ErrEmptyRequest @@ -82,6 +82,7 @@ func getUser(r *http.Request) (*fm.User, string, error) { return nil, "", fm.ErrWrongDataType } + mod.Data.FileSystem = c.NewFS(mod.Data.Scope) return mod.Data, mod.Which, nil } @@ -93,7 +94,7 @@ func usersGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int // Request for the listing of users. if r.URL.Path == "/" { - users, err := c.Store.Users.Gets() + users, err := c.Store.Users.Gets(c.NewFS) if err != nil { return http.StatusInternalServerError, err } @@ -116,7 +117,7 @@ func usersGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int return http.StatusInternalServerError, err } - u, err := c.Store.Users.Get(id) + u, err := c.Store.Users.Get(id, c.NewFS) if err == fm.ErrExist { return http.StatusNotFound, err } @@ -134,7 +135,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in return http.StatusMethodNotAllowed, nil } - u, _, err := getUser(r) + u, _, err := getUser(c, r) if err != nil { return http.StatusBadRequest, err } @@ -144,8 +145,8 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in return http.StatusBadRequest, fm.ErrEmptyUsername } - // Checks if filesystem isn't empty. - if u.FileSystem == "" { + // Checks if scope isn't empty. + if u.Scope == "" { return http.StatusBadRequest, fm.ErrEmptyScope } @@ -154,11 +155,6 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in return http.StatusBadRequest, fm.ErrEmptyPassword } - // The username, password and scope cannot be empty. - if u.Username == "" || u.Password == "" || u.FileSystem == "" { - return http.StatusBadRequest, errors.New("username, password or scope is empty") - } - // Initialize rules if they're not initialized. if u.Rules == nil { u.Rules = []*fm.Rule{} @@ -175,7 +171,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in } // Checks if the scope exists. - if code, err := checkFS(string(u.FileSystem)); err != nil { + if code, err := checkFS(u.Scope); err != nil { return code, err } @@ -267,7 +263,7 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int } // Gets the user from the request body. - u, which, err := getUser(r) + u, which, err := getUser(c, r) if err != nil { return http.StatusBadRequest, err } @@ -315,12 +311,12 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int } // Checks if filesystem isn't empty. - if u.FileSystem == "" { + if u.Scope == "" { return http.StatusBadRequest, fm.ErrEmptyScope } // Checks if the scope exists. - if code, err := checkFS(string(u.FileSystem)); err != nil { + if code, err := checkFS(u.Scope); err != nil { return code, err } @@ -335,7 +331,7 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int } // Gets the current saved user from the in-memory map. - suser, err := c.Store.Users.Get(id) + suser, err := c.Store.Users.Get(id, c.NewFS) if err == fm.ErrNotExist { return http.StatusNotFound, nil } diff --git a/http/websockets.go b/http/websockets.go index c12cc4ba..8f6d867f 100644 --- a/http/websockets.go +++ b/http/websockets.go @@ -82,7 +82,7 @@ func command(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) } // Gets the path and initializes a buffer. - path := string(c.User.FileSystem) + "/" + r.URL.Path + path := c.User.Scope + "/" + r.URL.Path path = filepath.Clean(path) buff := new(bytes.Buffer) @@ -270,7 +270,7 @@ func search(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) search = parseSearch(value) scope := strings.TrimPrefix(r.URL.Path, "/") scope = "/" + scope - scope = string(c.User.FileSystem) + scope + scope = c.User.Scope + scope scope = strings.Replace(scope, "\\", "/", -1) scope = filepath.Clean(scope)