Updates on auth and db
							parent
							
								
									1d235c9852
								
							
						
					
					
						commit
						6312c60b2e
					
				|  | @ -13,6 +13,7 @@ | |||
|     "filesize": "^3.5.10", | ||||
|     "moment": "^2.18.1", | ||||
|     "vue": "^2.3.3", | ||||
|     "vue-router": "^2.7.0", | ||||
|     "vuex": "^2.3.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|  |  | |||
|  | @ -0,0 +1,4 @@ | |||
| export default function (name) { | ||||
|   let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$') | ||||
|   return document.cookie.replace(re, '$1') | ||||
| } | ||||
							
								
								
									
										27
									
								
								api.go
								
								
								
								
							
							
						
						
									
										27
									
								
								api.go
								
								
								
								
							|  | @ -25,20 +25,19 @@ func cleanURL(path string) (string, string) { | |||
| } | ||||
| 
 | ||||
| func serveAPI(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	if r.URL.Path == "/auth" { | ||||
| 		return getTokenHandler(c, w, r) | ||||
| 	if r.URL.Path == "/auth/get" { | ||||
| 		return authHandler(c, w, r) | ||||
| 	} | ||||
| 
 | ||||
| 	/* valid, user := validAuth(c, r) | ||||
| 	if r.URL.Path == "/auth/renew" { | ||||
| 		return renewAuthHandler(c, w, r) | ||||
| 	} | ||||
| 
 | ||||
| 	valid, _ := validateAuth(c, r) | ||||
| 	if !valid { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Println(user) | ||||
| 	c.us = user */ | ||||
| 
 | ||||
| 	c.us = c.fm.User | ||||
| 
 | ||||
| 	var router string | ||||
| 	router, r.URL.Path = cleanURL(r.URL.Path) | ||||
| 
 | ||||
|  | @ -175,7 +174,7 @@ func deleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (i | |||
| 	} | ||||
| 
 | ||||
| 	// Remove the file or folder.
 | ||||
| 	err := c.us.fileSystem.RemoveAll(context.TODO(), r.URL.Path) | ||||
| 	err := c.us.FileSystem.RemoveAll(context.TODO(), r.URL.Path) | ||||
| 	if err != nil { | ||||
| 		return errorToHTTP(err, true), err | ||||
| 	} | ||||
|  | @ -185,11 +184,11 @@ func deleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (i | |||
| 
 | ||||
| func putHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	if strings.HasSuffix(r.URL.Path, "/") { | ||||
| 		err := c.us.fileSystem.Mkdir(context.TODO(), r.URL.Path, 0666) | ||||
| 		err := c.us.FileSystem.Mkdir(context.TODO(), r.URL.Path, 0666) | ||||
| 		return errorToHTTP(err, false), err | ||||
| 	} | ||||
| 
 | ||||
| 	f, err := c.us.fileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) | ||||
| 	f, err := c.us.FileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) | ||||
| 	defer f.Close() | ||||
| 
 | ||||
| 	if err != nil { | ||||
|  | @ -214,8 +213,12 @@ func putHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, | |||
| func postHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	dst := r.Header.Get("Destination") | ||||
| 	src := r.URL.Path | ||||
| 	err := c.us.fileSystem.Rename(context.TODO(), src, dst) | ||||
| 
 | ||||
| 	if dst == "/" || src == "/" { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	err := c.us.FileSystem.Rename(context.TODO(), src, dst) | ||||
| 	return errorToHTTP(err, true), err | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										125
									
								
								auth.go
								
								
								
								
							
							
						
						
									
										125
									
								
								auth.go
								
								
								
								
							|  | @ -1,36 +1,56 @@ | |||
| package filemanager | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
| 
 | ||||
| 	jwt "github.com/dgrijalva/jwt-go" | ||||
| 	"github.com/dgrijalva/jwt-go/request" | ||||
| ) | ||||
| 
 | ||||
| /* Set up a global string for our secret */ | ||||
| var key = []byte("secret") | ||||
| 
 | ||||
| type claims struct { | ||||
| 	*User | ||||
| 	jwt.StandardClaims | ||||
| } | ||||
| 
 | ||||
| func getTokenHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	// TODO: get user and password info from the request
 | ||||
| 	// check if the password is correct for that user using a DB or JSOn
 | ||||
| 	// or something.
 | ||||
| // authHandler proccesses the authentication for the user.
 | ||||
| func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	// Receive the credentials from the request and unmarshal them.
 | ||||
| 	var cred User | ||||
| 	if r.Body == nil { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	err := json.NewDecoder(r.Body).Decode(&cred) | ||||
| 	if err != nil { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Checks if the user exists.
 | ||||
| 	u, ok := c.fm.Users[cred.Username] | ||||
| 	if !ok { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Checks if the password is correct.
 | ||||
| 	if !checkPasswordHash(cred.Password, u.Password) { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	claims := claims{ | ||||
| 		c.fm.User, | ||||
| 		c.fm.Users["admin"], | ||||
| 		jwt.StandardClaims{ | ||||
| 			ExpiresAt: time.Now().Add(time.Minute * 5).Unix(), | ||||
| 			Issuer:    "test", | ||||
| 			ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), | ||||
| 			Issuer:    "File Manager", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | ||||
| 	string, err := token.SignedString(key) | ||||
| 	string, err := token.SignedString(c.fm.key) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return http.StatusInternalServerError, err | ||||
|  | @ -40,15 +60,80 @@ func getTokenHandler(c *requestContext, w http.ResponseWriter, r *http.Request) | |||
| 	return 0, nil | ||||
| } | ||||
| 
 | ||||
| func validAuth(c *requestContext, r *http.Request) (bool, *User) { | ||||
| 	token, err := request.ParseFromRequestWithClaims(r, request.AuthorizationHeaderExtractor, &claims{}, | ||||
| 		func(token *jwt.Token) (interface{}, error) { | ||||
| 			return key, nil | ||||
| 		}) | ||||
| 
 | ||||
| 	if err == nil && token.Valid { | ||||
| 		return true, c.fm.User | ||||
| // renewAuthHandler is used when the front-end already has a JWT token
 | ||||
| // and is checking if it is up to date. If so, updates its info.
 | ||||
| func renewAuthHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	ok, u := validateAuth(c, r) | ||||
| 	if !ok { | ||||
| 		return http.StatusForbidden, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return false, nil | ||||
| 	claims := claims{ | ||||
| 		u, | ||||
| 		jwt.StandardClaims{ | ||||
| 			ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), | ||||
| 			Issuer:    "File Manager", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | ||||
| 	string, err := token.SignedString(c.fm.key) | ||||
| 	if err != nil { | ||||
| 		return http.StatusInternalServerError, err | ||||
| 	} | ||||
| 
 | ||||
| 	w.Write([]byte(string)) | ||||
| 	return 0, nil | ||||
| } | ||||
| 
 | ||||
| // validateAuth is used to validate the authentication and returns the
 | ||||
| // User if it is valid.
 | ||||
| func validateAuth(c *requestContext, r *http.Request) (bool, *User) { | ||||
| 	keyFunc := func(token *jwt.Token) (interface{}, error) { | ||||
| 		return c.fm.key, nil | ||||
| 	} | ||||
| 
 | ||||
| 	var claims claims | ||||
| 	token, err := request.ParseFromRequestWithClaims(r, | ||||
| 		request.AuthorizationHeaderExtractor, | ||||
| 		&claims, | ||||
| 		keyFunc, | ||||
| 	) | ||||
| 
 | ||||
| 	if err != nil || !token.Valid { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	u, ok := c.fm.Users[claims.User.Username] | ||||
| 	if !ok { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	c.us = u | ||||
| 	return true, u | ||||
| } | ||||
| 
 | ||||
| // hashPassword generates an hash from a password using bcrypt.
 | ||||
| func hashPassword(password string) (string, error) { | ||||
| 	bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||||
| 	return string(bytes), err | ||||
| } | ||||
| 
 | ||||
| // checkPasswordHash compares a password with an hash to check if they match.
 | ||||
| func checkPasswordHash(password, hash string) bool { | ||||
| 	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) | ||||
| 	return err == nil | ||||
| } | ||||
| 
 | ||||
| const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | ||||
| 
 | ||||
| // randomString creates a string with a defined length using the above charset.
 | ||||
| func randomString(length int) string { | ||||
| 	seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) | ||||
| 
 | ||||
| 	b := make([]byte, length) | ||||
| 	for i := range b { | ||||
| 		b[i] = charset[seededRand.Intn(len(charset))] | ||||
| 	} | ||||
| 	return string(b) | ||||
| } | ||||
|  |  | |||
|  | @ -4,17 +4,15 @@ | |||
| package filemanager | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"golang.org/x/net/webdav" | ||||
| 
 | ||||
| 	. "github.com/hacdias/filemanager" | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/mholt/caddy/caddyhttp/httpserver" | ||||
|  | @ -34,8 +32,7 @@ type plugin struct { | |||
| 
 | ||||
| type config struct { | ||||
| 	*FileManager | ||||
| 	baseURL   string | ||||
| 	webDavURL string | ||||
| 	baseURL string | ||||
| } | ||||
| 
 | ||||
| // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
 | ||||
|  | @ -69,152 +66,55 @@ func setup(c *caddy.Controller) error { | |||
| func parse(c *caddy.Controller) ([]*config, error) { | ||||
| 	var ( | ||||
| 		configs []*config | ||||
| 		err     error | ||||
| 	) | ||||
| 
 | ||||
| 	for c.Next() { | ||||
| 		var ( | ||||
| 			m    = &config{FileManager: New(".")} | ||||
| 			u    = m.User | ||||
| 			name = "" | ||||
| 		) | ||||
| 		// TODO:
 | ||||
| 		// filemanager [baseurl] [baseScope] {
 | ||||
| 		//     database	path
 | ||||
| 		// }
 | ||||
| 
 | ||||
| 		baseURL := "/" | ||||
| 		baseScope := "." | ||||
| 
 | ||||
| 		// Get the baseURL and baseScope
 | ||||
| 		args := c.RemainingArgs() | ||||
| 
 | ||||
| 		if len(args) == 1 { | ||||
| 			baseURL = args[0] | ||||
| 		} | ||||
| 
 | ||||
| 		if len(args) > 1 { | ||||
| 			baseScope = args[1] | ||||
| 		} | ||||
| 
 | ||||
| 		fm, err := New("./this.db", User{ | ||||
| 			Username:      "admin", | ||||
| 			Password:      "admin", | ||||
| 			AllowCommands: true, | ||||
| 			AllowEdit:     true, | ||||
| 			AllowNew:      true, | ||||
| 			Commands:      []string{"git", "svn", "hg"}, | ||||
| 			Rules: []*Rule{{ | ||||
| 				Regex:  true, | ||||
| 				Allow:  false, | ||||
| 				Regexp: &Regexp{Raw: "\\/\\..+"}, | ||||
| 			}}, | ||||
| 			CSS:        "", | ||||
| 			FileSystem: webdav.Dir(baseScope), | ||||
| 		}) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		caddyConf := httpserver.GetConfig(c) | ||||
| 
 | ||||
| 		m := &config{FileManager: fm} | ||||
| 		m.SetBaseURL(baseURL) | ||||
| 		m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/")) | ||||
| 		m.Commands = []string{"git", "svn", "hg"} | ||||
| 		m.Rules = append(m.Rules, &Rule{ | ||||
| 			Regex:  true, | ||||
| 			Allow:  false, | ||||
| 			Regexp: regexp.MustCompile("\\/\\..+"), | ||||
| 		}) | ||||
| 		m.baseURL = strings.TrimSuffix(baseURL, "/") | ||||
| 
 | ||||
| 		// Get the baseURL
 | ||||
| 		args := c.RemainingArgs() | ||||
| 
 | ||||
| 		if len(args) > 0 { | ||||
| 			m.baseURL = args[0] | ||||
| 			m.SetBaseURL(args[0]) | ||||
| 		} | ||||
| 
 | ||||
| 		for c.NextBlock() { | ||||
| 			switch c.Val() { | ||||
| 			case "before_save": | ||||
| 				if m.BeforeSave, err = makeCommand(c, m); err != nil { | ||||
| 					return configs, err | ||||
| 				} | ||||
| 			case "after_save": | ||||
| 				if m.AfterSave, err = makeCommand(c, m); err != nil { | ||||
| 					return configs, err | ||||
| 				} | ||||
| 			case "show": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				m.SetScope(c.Val(), name) | ||||
| 			case "styles": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				var tplBytes []byte | ||||
| 				tplBytes, err = ioutil.ReadFile(c.Val()) | ||||
| 				if err != nil { | ||||
| 					return configs, err | ||||
| 				} | ||||
| 
 | ||||
| 				u.StyleSheet = string(tplBytes) | ||||
| 			case "allow_new": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				u.AllowNew, err = strconv.ParseBool(c.Val()) | ||||
| 				if err != nil { | ||||
| 					return configs, err | ||||
| 				} | ||||
| 			case "allow_edit": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				u.AllowEdit, err = strconv.ParseBool(c.Val()) | ||||
| 				if err != nil { | ||||
| 					return configs, err | ||||
| 				} | ||||
| 			case "allow_commands": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				u.AllowCommands, err = strconv.ParseBool(c.Val()) | ||||
| 				if err != nil { | ||||
| 					return configs, err | ||||
| 				} | ||||
| 			case "allow_command": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				u.Commands = append(u.Commands, c.Val()) | ||||
| 			case "block_command": | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				index := 0 | ||||
| 
 | ||||
| 				for i, val := range u.Commands { | ||||
| 					if val == c.Val() { | ||||
| 						index = i | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				u.Commands = append(u.Commands[:index], u.Commands[index+1:]...) | ||||
| 			case "allow", "allow_r", "block", "block_r": | ||||
| 				ruleType := c.Val() | ||||
| 
 | ||||
| 				if !c.NextArg() { | ||||
| 					return configs, c.ArgErr() | ||||
| 				} | ||||
| 
 | ||||
| 				if c.Val() == "dotfiles" && !strings.HasSuffix(ruleType, "_r") { | ||||
| 					ruleType += "_r" | ||||
| 				} | ||||
| 
 | ||||
| 				rule := &Rule{ | ||||
| 					Allow: ruleType == "allow" || ruleType == "allow_r", | ||||
| 					Regex: ruleType == "allow_r" || ruleType == "block_r", | ||||
| 				} | ||||
| 
 | ||||
| 				if rule.Regex && c.Val() == "dotfiles" { | ||||
| 					rule.Regexp = regexp.MustCompile("\\/\\..+") | ||||
| 				} else if rule.Regex { | ||||
| 					rule.Regexp = regexp.MustCompile(c.Val()) | ||||
| 				} else { | ||||
| 					rule.Path = c.Val() | ||||
| 				} | ||||
| 
 | ||||
| 				u.Rules = append(u.Rules, rule) | ||||
| 			default: | ||||
| 				// Is it a new user? Is it?
 | ||||
| 				val := c.Val() | ||||
| 
 | ||||
| 				// Checks if it's a new user!
 | ||||
| 				if !strings.HasSuffix(val, ":") { | ||||
| 					fmt.Println("Unknown option " + val) | ||||
| 				} | ||||
| 
 | ||||
| 				// Get the username, sets the current user, and initializes it
 | ||||
| 				val = strings.TrimSuffix(val, ":") | ||||
| 				m.NewUser(val) | ||||
| 				name = val | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		m.baseURL = strings.TrimSuffix(m.baseURL, "/") | ||||
| 		m.webDavURL = strings.TrimSuffix(m.webDavURL, "/") | ||||
| 		configs = append(configs, m) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -242,8 +142,8 @@ func makeCommand(c *caddy.Controller, m *config) (Command, error) { | |||
| 	} | ||||
| 
 | ||||
| 	fn = func(r *http.Request, c *FileManager, u *User) error { | ||||
| 		path := strings.Replace(r.URL.Path, m.baseURL+m.webDavURL, "", 1) | ||||
| 		path = u.Scope + "/" + path | ||||
| 		path := strings.Replace(r.URL.Path, m.baseURL+"/files", "", 1) | ||||
| 		path = string(u.FileSystem) + "/" + path | ||||
| 		path = filepath.Clean(path) | ||||
| 
 | ||||
| 		for i := range args { | ||||
|  |  | |||
|  | @ -77,7 +77,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er | |||
| 	} | ||||
| 
 | ||||
| 	// Gets the path and initializes a buffer.
 | ||||
| 	path := c.us.Scope + "/" + r.URL.Path | ||||
| 	path := string(c.us.FileSystem) + "/" + r.URL.Path | ||||
| 	path = filepath.Clean(path) | ||||
| 	buff := new(bytes.Buffer) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										6
									
								
								file.go
								
								
								
								
							
							
						
						
									
										6
									
								
								file.go
								
								
								
								
							|  | @ -85,10 +85,10 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) { | |||
| 	i := &file{ | ||||
| 		URL:         c.RootURL() + "/files" + url.Path, | ||||
| 		VirtualPath: url.Path, | ||||
| 		Path:        filepath.Join(u.Scope, url.Path), | ||||
| 		Path:        filepath.Join(string(u.FileSystem), url.Path), | ||||
| 	} | ||||
| 
 | ||||
| 	info, err := u.fileSystem.Stat(context.TODO(), url.Path) | ||||
| 	info, err := u.FileSystem.Stat(context.TODO(), url.Path) | ||||
| 	if err != nil { | ||||
| 		return i, err | ||||
| 	} | ||||
|  | @ -106,7 +106,7 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) { | |||
| func (i *file) getListing(c *requestContext, r *http.Request) error { | ||||
| 	// Gets the directory information using the Virtual File System of
 | ||||
| 	// the user configuration.
 | ||||
| 	f, err := c.us.fileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0) | ||||
| 	f, err := c.us.FileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										212
									
								
								filemanager.go
								
								
								
								
							
							
						
						
									
										212
									
								
								filemanager.go
								
								
								
								
							|  | @ -7,6 +7,7 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	rice "github.com/GeertJohan/go.rice" | ||||
| 	"github.com/asdine/storm" | ||||
| 	"golang.org/x/net/webdav" | ||||
| ) | ||||
| 
 | ||||
|  | @ -18,26 +19,22 @@ var ( | |||
| // FileManager is a file manager instance. It should be creating using the
 | ||||
| // 'New' function and not directly.
 | ||||
| type FileManager struct { | ||||
| 	*User | ||||
| 	db  *storm.DB | ||||
| 	key []byte | ||||
| 
 | ||||
| 	// prefixURL is a part of the URL that is already trimmed from the request URL before it
 | ||||
| 	// 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.
 | ||||
| 	prefixURL string | ||||
| 	PrefixURL string | ||||
| 
 | ||||
| 	// baseURL is the path where the GUI will be accessible. It musn't end with
 | ||||
| 	// a trailing slash and mustn't contain prefixURL, if set.
 | ||||
| 	baseURL string | ||||
| 	// BaseURL is the path where the GUI will be accessible. It musn't end with
 | ||||
| 	// a trailing slash and mustn't contain PrefixURL, if set. It shouldn't be
 | ||||
| 	// edited directly. Use SetBaseURL.
 | ||||
| 	BaseURL string | ||||
| 
 | ||||
| 	// Users is a map with the different configurations for each user.
 | ||||
| 	Users map[string]*User | ||||
| 
 | ||||
| 	// BeforeSave is a function that is called before saving a file.
 | ||||
| 	BeforeSave Command | ||||
| 
 | ||||
| 	// AfterSave is a function that is called before saving a file.
 | ||||
| 	AfterSave Command | ||||
| 
 | ||||
| 	assets *rice.Box | ||||
| } | ||||
| 
 | ||||
|  | @ -47,17 +44,24 @@ type Command func(r *http.Request, m *FileManager, u *User) error | |||
| // User contains the configuration for each user. It should be created
 | ||||
| // using NewUser on a File Manager instance.
 | ||||
| type User struct { | ||||
| 	// Scope is the physical path the user has access to.
 | ||||
| 	Scope string | ||||
| 	// ID is the required primary key with auto increment0
 | ||||
| 	ID int `storm:"id,increment"` | ||||
| 
 | ||||
| 	// fileSystem is the virtual file system the user has access.
 | ||||
| 	fileSystem webdav.FileSystem | ||||
| 	// Username is the user username used to login.
 | ||||
| 	Username string `json:"username" storm:"index,unique"` | ||||
| 
 | ||||
| 	// The hashed password. This never reaches the front-end because it's temporarily
 | ||||
| 	// emptied during JSON marshall.
 | ||||
| 	Password string `json:"password"` | ||||
| 
 | ||||
| 	// FileSystem is the virtual file system the user has access.
 | ||||
| 	FileSystem webdav.Dir `json:"filesystem"` | ||||
| 
 | ||||
| 	// Rules is an array of access and deny rules.
 | ||||
| 	Rules []*Rule `json:"-"` | ||||
| 	Rules []*Rule `json:"rules"` | ||||
| 
 | ||||
| 	// TODO: this MUST be done in another way
 | ||||
| 	StyleSheet string `json:"-"` | ||||
| 	// Costum styles for this user.
 | ||||
| 	CSS string `json:"css"` | ||||
| 
 | ||||
| 	// These indicate if the user can perform certain actions.
 | ||||
| 	AllowNew      bool `json:"allowNew"`      // Create files and folders
 | ||||
|  | @ -80,42 +84,101 @@ type Rule struct { | |||
| 	Path string | ||||
| 
 | ||||
| 	// Regexp is the regular expression. Only use this when 'Regex' was set to true.
 | ||||
| 	Regexp *regexp.Regexp | ||||
| 	Regexp *Regexp | ||||
| } | ||||
| 
 | ||||
| // New creates a new File Manager instance with the needed
 | ||||
| // configuration to work.
 | ||||
| func New(scope string) *FileManager { | ||||
| // Regexp is a regular expression wrapper around native regexp.
 | ||||
| type Regexp struct { | ||||
| 	Raw    string | ||||
| 	regexp *regexp.Regexp | ||||
| } | ||||
| 
 | ||||
| // DefaultUser is used on New, when no 'base' user is provided.
 | ||||
| var DefaultUser = User{ | ||||
| 	Username:      "admin", | ||||
| 	Password:      "admin", | ||||
| 	AllowCommands: true, | ||||
| 	AllowEdit:     true, | ||||
| 	AllowNew:      true, | ||||
| 	Commands:      []string{}, | ||||
| 	Rules:         []*Rule{}, | ||||
| 	CSS:           "", | ||||
| 	FileSystem:    webdav.Dir("."), | ||||
| } | ||||
| 
 | ||||
| // New creates a new File Manager instance. If 'database' file already
 | ||||
| // exists, it will load the users from there. Otherwise, a new user
 | ||||
| // will be created using the 'base' variable. The 'base' User should
 | ||||
| // not have the Password field hashed.
 | ||||
| // TODO: should it ask for a baseURL on New????
 | ||||
| func New(database string, base User) (*FileManager, error) { | ||||
| 	// Creates a new File Manager instance with the Users
 | ||||
| 	// map and Assets box.
 | ||||
| 	m := &FileManager{ | ||||
| 		User: &User{ | ||||
| 			AllowCommands: true, | ||||
| 			AllowEdit:     true, | ||||
| 			AllowNew:      true, | ||||
| 			Commands:      []string{}, | ||||
| 			Rules:         []*Rule{}, | ||||
| 		}, | ||||
| 		Users:      map[string]*User{}, | ||||
| 		BeforeSave: func(r *http.Request, m *FileManager, u *User) error { return nil }, | ||||
| 		AfterSave:  func(r *http.Request, m *FileManager, u *User) error { return nil }, | ||||
| 		assets:     rice.MustFindBox("./_assets/dist"), | ||||
| 		Users:  map[string]*User{}, | ||||
| 		assets: rice.MustFindBox("./_assets/dist"), | ||||
| 	} | ||||
| 
 | ||||
| 	m.SetScope(scope, "") | ||||
| 	m.SetBaseURL("/") | ||||
| 	// Tries to open a database on the location provided. This
 | ||||
| 	// function will automatically create a new one if it doesn't
 | ||||
| 	// exist.
 | ||||
| 	db, err := storm.Open(database) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return m | ||||
| 	// Tries to get the encryption key from the database.
 | ||||
| 	// If it doesn't exist, create a new one of 256 bits.
 | ||||
| 	err = db.Get("config", "key", &m.key) | ||||
| 	if err != nil && err == storm.ErrNotFound { | ||||
| 		m.key = []byte(randomString(64)) | ||||
| 		err = db.Set("config", "key", m.key) | ||||
| 	} | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Tries to fetch the users from the database and if there are
 | ||||
| 	// any, add them to the current File Manager instance.
 | ||||
| 	var users []User | ||||
| 	err = db.All(&users) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range users { | ||||
| 		m.Users[users[i].Username] = &users[i] | ||||
| 	} | ||||
| 
 | ||||
| 	// If there are no users in the database, it creates a new one
 | ||||
| 	// based on 'base' User that must be provided by the function caller.
 | ||||
| 	if len(users) == 0 { | ||||
| 		// Hashes the password.
 | ||||
| 		pw, err := hashPassword(base.Password) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		base.Password = pw | ||||
| 
 | ||||
| 		// Saves the user to the database.
 | ||||
| 		if err := db.Save(&base); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		m.Users[base.Username] = &base | ||||
| 	} | ||||
| 
 | ||||
| 	// Attaches db to this File Manager instance.
 | ||||
| 	m.db = db | ||||
| 	return m, nil | ||||
| } | ||||
| 
 | ||||
| // RootURL returns the actual URL where
 | ||||
| // File Manager interface can be accessed.
 | ||||
| func (m FileManager) RootURL() string { | ||||
| 	return m.prefixURL + m.baseURL | ||||
| } | ||||
| 
 | ||||
| // WebDavURL returns the actual URL
 | ||||
| // where WebDAV can be accessed.
 | ||||
| func (m FileManager) WebDavURL() string { | ||||
| 	return m.prefixURL + m.baseURL + "/api/webdav" | ||||
| 	return m.PrefixURL + m.BaseURL | ||||
| } | ||||
| 
 | ||||
| // SetPrefixURL updates the prefixURL of a File
 | ||||
|  | @ -124,7 +187,7 @@ func (m *FileManager) SetPrefixURL(url string) { | |||
| 	url = strings.TrimPrefix(url, "/") | ||||
| 	url = strings.TrimSuffix(url, "/") | ||||
| 	url = "/" + url | ||||
| 	m.prefixURL = strings.TrimSuffix(url, "/") | ||||
| 	m.PrefixURL = strings.TrimSuffix(url, "/") | ||||
| } | ||||
| 
 | ||||
| // SetBaseURL updates the baseURL of a File Manager
 | ||||
|  | @ -133,48 +196,7 @@ func (m *FileManager) SetBaseURL(url string) { | |||
| 	url = strings.TrimPrefix(url, "/") | ||||
| 	url = strings.TrimSuffix(url, "/") | ||||
| 	url = "/" + url | ||||
| 	m.baseURL = strings.TrimSuffix(url, "/") | ||||
| } | ||||
| 
 | ||||
| // SetScope updates a user scope and its virtual file system.
 | ||||
| // If the user string is blank, it will change the base scope.
 | ||||
| func (m *FileManager) SetScope(scope string, username string) error { | ||||
| 	var u *User | ||||
| 
 | ||||
| 	if username == "" { | ||||
| 		u = m.User | ||||
| 	} else { | ||||
| 		var ok bool | ||||
| 		u, ok = m.Users[username] | ||||
| 		if !ok { | ||||
| 			return errors.New("Inexistent user") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	u.Scope = strings.TrimSuffix(scope, "/") | ||||
| 	u.fileSystem = webdav.Dir(u.Scope) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // NewUser creates a new user on a File Manager struct
 | ||||
| // which inherits its configuration from the main user.
 | ||||
| func (m *FileManager) NewUser(username string) error { | ||||
| 	if _, ok := m.Users[username]; ok { | ||||
| 		return ErrDuplicated | ||||
| 	} | ||||
| 
 | ||||
| 	m.Users[username] = &User{ | ||||
| 		fileSystem:    m.User.fileSystem, | ||||
| 		Scope:         m.User.Scope, | ||||
| 		Rules:         m.User.Rules, | ||||
| 		AllowNew:      m.User.AllowNew, | ||||
| 		AllowEdit:     m.User.AllowEdit, | ||||
| 		AllowCommands: m.User.AllowCommands, | ||||
| 		Commands:      m.User.Commands, | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| 	m.BaseURL = strings.TrimSuffix(url, "/") | ||||
| } | ||||
| 
 | ||||
| // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
 | ||||
|  | @ -208,3 +230,19 @@ func (u User) Allowed(url string) bool { | |||
| 
 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // SetScope updates a user scope and its virtual file system.
 | ||||
| // If the user string is blank, it will change the base scope.
 | ||||
| func (u *User) SetScope(scope string) { | ||||
| 	scope = strings.TrimSuffix(scope, "/") | ||||
| 	u.FileSystem = webdav.Dir(scope) | ||||
| } | ||||
| 
 | ||||
| // MatchString checks if this string matches the regular expression.
 | ||||
| func (r *Regexp) MatchString(s string) bool { | ||||
| 	if r.regexp == nil { | ||||
| 		r.regexp = regexp.MustCompile(r.Raw) | ||||
| 	} | ||||
| 
 | ||||
| 	return r.regexp.MatchString(s) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										4
									
								
								http.go
								
								
								
								
							
							
						
						
									
										4
									
								
								http.go
								
								
								
								
							|  | @ -19,9 +19,9 @@ type requestContext struct { | |||
| func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	// Checks if the URL contains the baseURL and strips it. Otherwise, it just
 | ||||
| 	// returns a 404 error because we're not supposed to be here!
 | ||||
| 	p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL) | ||||
| 	p := strings.TrimPrefix(r.URL.Path, c.fm.BaseURL) | ||||
| 
 | ||||
| 	if len(p) >= len(r.URL.Path) && c.fm.baseURL != "" { | ||||
| 	if len(p) >= len(r.URL.Path) && c.fm.BaseURL != "" { | ||||
| 		return http.StatusNotFound, nil | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -73,7 +73,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err | |||
| 	search = parseSearch(value) | ||||
| 	scope := strings.TrimPrefix(r.URL.Path, "/") | ||||
| 	scope = "/" + scope | ||||
| 	scope = c.us.Scope + scope | ||||
| 	scope = string(c.us.FileSystem) + scope | ||||
| 	scope = strings.Replace(scope, "\\", "/", -1) | ||||
| 	scope = filepath.Clean(scope) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Henrique Dias
						Henrique Dias