diff --git a/assets/embed/templates/listing.tmpl b/assets/embed/templates/listing.tmpl index 71250589..8178b22e 100644 --- a/assets/embed/templates/listing.tmpl +++ b/assets/embed/templates/listing.tmpl @@ -3,6 +3,7 @@
{{- range .Items}} + {{ if .UserAllowed }} + {{ end }} {{- end}}
diff --git a/config/config.go b/config/config.go index dedac978..1706f6c1 100644 --- a/config/config.go +++ b/config/config.go @@ -14,37 +14,22 @@ import ( // Config is a configuration for browsing in a particualr path. type Config struct { - *UserConfig + *User BaseURL string AbsoluteURL string AddrPath string Token string // Anti CSRF token HugoEnabled bool // Enables the Hugo plugin for File Manager - Users map[string]*UserConfig - CurrentUser *UserConfig + Users map[string]*User + CurrentUser *User } -// UserConfig contains the configuration for each user -type UserConfig struct { - PathScope string `json:"-"` // Path the user have access - Root http.FileSystem `json:"-"` // The virtual file system the user have access - StyleSheet string `json:"-"` // Costum stylesheet - FrontMatter string `json:"-"` // Default frontmatter to save files in - AllowNew bool // Can create files and folders - AllowEdit bool // Can edit/rename files - AllowCommands bool // Can execute commands - Commands []string // Available Commands - Rules []*Rule `json:"-"` // Access rules -} - -// REVIEW: USE USER ROOT - // Rule is a dissalow/allow rule type Rule struct { Regex bool Allow bool Path string - Rexexp *regexp.Regexp + Regexp *regexp.Regexp } // Parse parses the configuration set by the user so it can @@ -63,24 +48,29 @@ func Parse(c *caddy.Controller) ([]Config, error) { } var err error - var cCfg *UserConfig + var cCfg *User var baseURL string for c.Next() { - var cfg = Config{UserConfig: &UserConfig{}} + var cfg = Config{User: &User{}} cfg.PathScope = "." cfg.Root = http.Dir(cfg.PathScope) cfg.BaseURL = "" cfg.FrontMatter = "yaml" cfg.HugoEnabled = false - cfg.Users = map[string]*UserConfig{} + cfg.Users = map[string]*User{} cfg.AllowCommands = true cfg.AllowEdit = true cfg.AllowNew = true cfg.Commands = []string{"git", "svn", "hg"} + cfg.Rules = []*Rule{&Rule{ + Regex: true, + Allow: false, + Regexp: regexp.MustCompile("\\/\\..+"), + }} baseURL = "" - cCfg = cfg.UserConfig + cCfg = cfg.User for c.NextBlock() { switch c.Val() { @@ -167,7 +157,7 @@ func Parse(c *caddy.Controller) ([]Config, error) { Regex: false, Allow: true, Path: c.Val(), - Rexexp: nil, + Regexp: nil, }) case "allow_r": if !c.NextArg() { @@ -178,7 +168,7 @@ func Parse(c *caddy.Controller) ([]Config, error) { Regex: true, Allow: true, Path: "", - Rexexp: regexp.MustCompile(c.Val()), + Regexp: regexp.MustCompile(c.Val()), }) case "block": if !c.NextArg() { @@ -189,7 +179,7 @@ func Parse(c *caddy.Controller) ([]Config, error) { Regex: false, Allow: false, Path: c.Val(), - Rexexp: nil, + Regexp: nil, }) case "block_r": if !c.NextArg() { @@ -200,7 +190,7 @@ func Parse(c *caddy.Controller) ([]Config, error) { Regex: true, Allow: false, Path: "", - Rexexp: regexp.MustCompile(c.Val()), + Regexp: regexp.MustCompile(c.Val()), }) // NEW USER BLOCK? default: @@ -212,7 +202,7 @@ func Parse(c *caddy.Controller) ([]Config, error) { // Get the username, sets the current user, and initializes it val = strings.TrimSuffix(val, ":") - cfg.Users[val] = &UserConfig{} + cfg.Users[val] = &User{} // Initialize the new user cCfg = cfg.Users[val] diff --git a/config/user.go b/config/user.go new file mode 100644 index 00000000..2e7e85ac --- /dev/null +++ b/config/user.go @@ -0,0 +1,43 @@ +package config + +import ( + "net/http" + "strings" +) + +// User contains the configuration for each user +type User struct { + PathScope string `json:"-"` // Path the user have access + Root http.FileSystem `json:"-"` // The virtual file system the user have access + StyleSheet string `json:"-"` // Costum stylesheet + FrontMatter string `json:"-"` // Default frontmatter to save files in + AllowNew bool // Can create files and folders + AllowEdit bool // Can edit/rename files + AllowCommands bool // Can execute commands + Commands []string // Available Commands + Rules []*Rule `json:"-"` // Access rules +} + +// REVIEW: USE USER ROOT + +// Allowed is +func (u User) Allowed(url string) bool { + var rule *Rule + i := len(u.Rules) - 1 + + for i >= 0 { + rule = u.Rules[i] + + if rule.Regex { + if rule.Regexp.MatchString(url) { + return rule.Allow + } + } else if strings.HasPrefix(url, rule.Path) { + return rule.Allow + } + + i-- + } + + return true +} diff --git a/directory/file.go b/directory/file.go index 106e75d3..cfcd728f 100644 --- a/directory/file.go +++ b/directory/file.go @@ -21,23 +21,24 @@ import ( // Info is the information about a particular file or directory type Info struct { - IsDir bool - Name string - Size int64 - URL string - Path string // The relative Path of the file/directory relative to Caddyfile. - RootPath string // The Path of the file/directory on http.FileSystem. - ModTime time.Time - Mode os.FileMode - Mimetype string - Content string - Raw []byte - Type string + IsDir bool + Name string + Size int64 + URL string + Path string // The relative Path of the file/directory relative to Caddyfile. + RootPath string // The Path of the file/directory on http.FileSystem. + ModTime time.Time + Mode os.FileMode + Mimetype string + Content string + Raw []byte + Type string + UserAllowed bool // Indicates if the user has permissions to open this directory } // GetInfo gets the file information and, in case of error, returns the // respective HTTP error code -func GetInfo(url *url.URL, c *config.Config, u *config.UserConfig) (*Info, int, error) { +func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error) { var err error rootPath := strings.Replace(url.Path, c.BaseURL, "", 1) @@ -142,7 +143,7 @@ func (i *Info) Rename(w http.ResponseWriter, r *http.Request) (int, error) { } // ServeAsHTML is used to serve single file pages -func (i *Info) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.UserConfig) (int, error) { +func (i *Info) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { if i.IsDir { return i.serveListing(w, r, c, u) } @@ -150,7 +151,7 @@ func (i *Info) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *config.Con return i.serveSingleFile(w, r, c, u) } -func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.UserConfig) (int, error) { +func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { err := i.GetExtendedInfo() if err != nil { return errors.ToHTTPCode(err), err @@ -185,7 +186,7 @@ func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config return page.PrintAsHTML(w, "single") } -func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.UserConfig) (int, error) { +func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { var err error file, err := u.Root.Open(i.RootPath) @@ -194,7 +195,7 @@ func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Co } defer file.Close() - listing, err := i.loadDirectoryContents(file, c) + listing, err := i.loadDirectoryContents(file, r.URL.Path, u) if err != nil { fmt.Println(err) switch { @@ -259,17 +260,17 @@ func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Co return page.PrintAsHTML(w, "listing") } -func (i Info) loadDirectoryContents(file http.File, c *config.Config) (*Listing, error) { +func (i Info) loadDirectoryContents(file http.File, path string, u *config.User) (*Listing, error) { files, err := file.Readdir(-1) if err != nil { return nil, err } - listing := directoryListing(files, i.RootPath) + listing := directoryListing(files, i.RootPath, path, u) return &listing, nil } -func directoryListing(files []os.FileInfo, urlPath string) Listing { +func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *config.User) Listing { var ( fileinfos []Info dirCount, fileCount int @@ -285,15 +286,16 @@ func directoryListing(files []os.FileInfo, urlPath string) Listing { fileCount++ } - url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name - + // Absolute URL + url := url.URL{Path: basePath + name} fileinfos = append(fileinfos, Info{ - IsDir: f.IsDir(), - Name: f.Name(), - Size: f.Size(), - URL: url.String(), - ModTime: f.ModTime().UTC(), - Mode: f.Mode(), + IsDir: f.IsDir(), + Name: f.Name(), + Size: f.Size(), + URL: url.String(), + ModTime: f.ModTime().UTC(), + Mode: f.Mode(), + UserAllowed: u.Allowed(url.String()), }) } diff --git a/directory/update.go b/directory/update.go index ec2d8ce5..7a0f5c3a 100644 --- a/directory/update.go +++ b/directory/update.go @@ -15,7 +15,7 @@ import ( ) // Update is used to update a file that was edited -func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.UserConfig) (int, error) { +func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { var data map[string]interface{} kind := r.Header.Get("kind") diff --git a/filemanager.go b/filemanager.go index ffbb7674..6d48f212 100644 --- a/filemanager.go +++ b/filemanager.go @@ -8,6 +8,7 @@ package filemanager import ( + e "errors" "io" "io/ioutil" "log" @@ -41,7 +42,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err code int err error serveAssets bool - user *config.UserConfig + user *config.User ) for i := range f.Configs { @@ -56,6 +57,14 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err user = c.Users[username] } + if !user.Allowed(r.URL.Path) { + if r.Method == http.MethodGet { + return errors.PrintHTML(w, http.StatusForbidden, e.New("You don't have permission to access this page.")) + } + + return http.StatusForbidden, nil + } + if r.Method != http.MethodPost && !serveAssets { fi, code, err = directory.GetInfo(r.URL, c, user) if err != nil { @@ -241,7 +250,7 @@ func newDirectory(w http.ResponseWriter, r *http.Request, c *config.Config) (int } // command handles the requests for VCS related commands: git, svn and mercurial -func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.UserConfig) (int, error) { +func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { command := strings.Split(r.Header.Get("command"), " ") // Check if the command is allowed diff --git a/page/page.go b/page/page.go index 10d70c39..0481c970 100644 --- a/page/page.go +++ b/page/page.go @@ -24,7 +24,7 @@ type Info struct { Name string Path string IsDir bool - User *config.UserConfig + User *config.User Config *config.Config Data interface{} }