merge with master

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

Former-commit-id: dfac33db9ca3454cd3af02a732a64a02a8268b01 [formerly 38be015f7b3376a3bc992725c1cb1a019c1843cb] [formerly 5a4e310b96ebc5895e64f8eeb2f592890d4b63b0 [formerly 2b8bd2815859af2eed2f6f2b6f95823af1dcff5d]]
Former-commit-id: a30bfcebe6bbc115990b8f8398171b32942a8767 [formerly 553fafa22b46b2689ece093f3b0cf85b3cff1c87]
Former-commit-id: 5ce047d4e3f5217a179685f8b2850d4eb157376e
This commit is contained in:
Henrique Dias 2019-01-06 13:20:17 +00:00
commit e5c150e83f
25 changed files with 233 additions and 386 deletions

View File

@ -3,13 +3,12 @@ package auth
import ( import (
"net/http" "net/http"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
) )
// Auther is the authentication interface. // Auther is the authentication interface.
type Auther interface { type Auther interface {
// Auth is called to authenticate a request. // Auth is called to authenticate a request.
Auth(*http.Request) (*users.User, error) Auth(*http.Request, *users.Storage, *settings.Settings) (*users.User, error)
// SetStorage attaches the Storage instance.
SetStorage(*users.Storage)
} }

View File

@ -23,11 +23,10 @@ type jsonCred struct {
// JSONAuth is a json implementaion of an Auther. // JSONAuth is a json implementaion of an Auther.
type JSONAuth struct { type JSONAuth struct {
ReCaptcha *ReCaptcha ReCaptcha *ReCaptcha
storage *users.Storage
} }
// Auth authenticates the user via a json in content body. // Auth authenticates the user via a json in content body.
func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) { func (a *JSONAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) {
var cred jsonCred var cred jsonCred
if r.Body == nil { if r.Body == nil {
@ -52,7 +51,7 @@ func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) {
} }
} }
u, err := a.storage.Get(cred.Username) u, err := sto.Get(set.Scope, cred.Username)
if err != nil || !users.CheckPwd(cred.Password, u.Password) { if err != nil || !users.CheckPwd(cred.Password, u.Password) {
return nil, os.ErrPermission return nil, os.ErrPermission
} }
@ -60,11 +59,6 @@ func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) {
return u, nil return u, nil
} }
// SetStorage attaches the storage to the auther.
func (a *JSONAuth) SetStorage(s *users.Storage) {
a.storage = s
}
const reCaptchaAPI = "/recaptcha/api/siteverify" const reCaptchaAPI = "/recaptcha/api/siteverify"
// ReCaptcha identifies a recaptcha conenction. // ReCaptcha identifies a recaptcha conenction.

View File

@ -12,15 +12,9 @@ const MethodNoAuth settings.AuthMethod = "noauth"
// NoAuth is no auth implementation of auther. // NoAuth is no auth implementation of auther.
type NoAuth struct { type NoAuth struct {
storage *users.Storage
} }
// Auth uses authenticates user 1. // Auth uses authenticates user 1.
func (a *NoAuth) Auth(r *http.Request) (*users.User, error) { func (a *NoAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) {
return a.storage.Get(1) return sto.Get(set.Scope, 1)
}
// SetStorage attaches the storage to the auther.
func (a *NoAuth) SetStorage(s *users.Storage) {
a.storage = s
} }

View File

@ -15,21 +15,15 @@ const MethodProxyAuth settings.AuthMethod = "proxy"
// ProxyAuth is a proxy implementation of an auther. // ProxyAuth is a proxy implementation of an auther.
type ProxyAuth struct { type ProxyAuth struct {
Header string Header string
storage *users.Storage
} }
// Auth authenticates the user via an HTTP header. // Auth authenticates the user via an HTTP header.
func (a *ProxyAuth) Auth(r *http.Request) (*users.User, error) { func (a *ProxyAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) {
username := r.Header.Get(a.Header) username := r.Header.Get(a.Header)
user, err := a.storage.Get(username) user, err := sto.Get(set.Scope, username)
if err == errors.ErrNotExist { if err == errors.ErrNotExist {
return nil, os.ErrPermission return nil, os.ErrPermission
} }
return user, err return user, err
} }
// SetStorage attaches the storage to the auther.
func (a *ProxyAuth) SetStorage(s *users.Storage) {
a.storage = s
}

View File

@ -22,15 +22,9 @@ func NewStorage(back StorageBackend, users *users.Storage) *Storage {
return &Storage{back: back, users: users} return &Storage{back: back, users: users}
} }
// Get wraps a StorageBackend.Get and calls SetStorage on the auther. // Get wraps a StorageBackend.Get.
func (s *Storage) Get(t settings.AuthMethod) (Auther, error) { func (s *Storage) Get(t settings.AuthMethod) (Auther, error) {
auther, err := s.back.Get(t) return s.back.Get(t)
if err != nil {
return nil, err
}
auther.SetStorage(s.users)
return auther, nil
} }
// Save wraps a StorageBackend.Save. // Save wraps a StorageBackend.Save.

View File

@ -35,12 +35,6 @@ func addConfigFlags(cmd *cobra.Command) {
cmd.Flags().BoolP("signup", "s", false, "allow users to signup") cmd.Flags().BoolP("signup", "s", false, "allow users to signup")
cmd.Flags().String("shell", "", "shell command to which other commands should be appended") cmd.Flags().String("shell", "", "shell command to which other commands should be appended")
cmd.Flags().StringP("address", "a", "127.0.0.1", "default address to listen to")
cmd.Flags().StringP("log", "l", "stderr", "log output")
cmd.Flags().IntP("port", "p", 0, "default port to listen to")
cmd.Flags().String("tls.cert", "", "tls certificate path")
cmd.Flags().String("tls.key", "", "tls key path")
cmd.Flags().String("auth.method", string(auth.MethodJSONAuth), "authentication type") cmd.Flags().String("auth.method", string(auth.MethodJSONAuth), "authentication type")
cmd.Flags().String("auth.header", "", "HTTP header for auth.method=proxy") cmd.Flags().String("auth.header", "", "HTTP header for auth.method=proxy")
@ -101,12 +95,6 @@ func printSettings(s *settings.Settings, auther auth.Auther) {
fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup) fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup)
fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod) fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod)
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " ")) fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " "))
fmt.Fprintf(w, "Log:\t%s\t\n", s.Log)
fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tAddress:\t%s\n", s.Server.Address)
fmt.Fprintf(w, "\tPort:\t%d\n", s.Server.Port)
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", s.Server.TLSCert)
fmt.Fprintf(w, "\tTLS Key:\t%s\n", s.Server.TLSKey)
fmt.Fprintln(w, "\nBranding:") fmt.Fprintln(w, "\nBranding:")
fmt.Fprintf(w, "\tName:\t%s\n", s.Branding.Name) fmt.Fprintf(w, "\tName:\t%s\n", s.Branding.Name)
fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files) fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files)

View File

@ -16,7 +16,6 @@ func init() {
configCmd.AddCommand(configInitCmd) configCmd.AddCommand(configInitCmd)
rootCmd.AddCommand(configInitCmd) rootCmd.AddCommand(configInitCmd)
addConfigFlags(configInitCmd) addConfigFlags(configInitCmd)
configInitCmd.MarkFlagRequired("scope")
} }
var configInitCmd = &cobra.Command{ var configInitCmd = &cobra.Command{
@ -45,17 +44,10 @@ override the options.`,
s := &settings.Settings{ s := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit Key: generateRandomBytes(64), // 256 bit
BaseURL: mustGetString(cmd, "baseURL"), BaseURL: mustGetString(cmd, "baseURL"),
Log: mustGetString(cmd, "log"),
Signup: mustGetBool(cmd, "signup"), Signup: mustGetBool(cmd, "signup"),
Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "), Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "),
AuthMethod: authMethod, AuthMethod: authMethod,
Defaults: defaults, Defaults: defaults,
Server: settings.Server{
Address: mustGetString(cmd, "address"),
Port: mustGetInt(cmd, "port"),
TLSCert: mustGetString(cmd, "tls.cert"),
TLSKey: mustGetString(cmd, "tls.key"),
},
Branding: settings.Branding{ Branding: settings.Branding{
Name: mustGetString(cmd, "branding.name"), Name: mustGetString(cmd, "branding.name"),
DisableExternal: mustGetBool(cmd, "branding.disableExternal"), DisableExternal: mustGetBool(cmd, "branding.disableExternal"),

View File

@ -44,16 +44,6 @@ you want to change.`,
s.Branding.DisableExternal = mustGetBool(cmd, flag.Name) s.Branding.DisableExternal = mustGetBool(cmd, flag.Name)
case "branding.files": case "branding.files":
s.Branding.Files = mustGetString(cmd, flag.Name) s.Branding.Files = mustGetString(cmd, flag.Name)
case "log":
s.Log = mustGetString(cmd, flag.Name)
case "address":
s.Server.Address = mustGetString(cmd, flag.Name)
case "port":
s.Server.Port = mustGetInt(cmd, flag.Name)
case "tls.cert":
s.Server.TLSCert = mustGetString(cmd, flag.Name)
case "tls.key":
s.Server.TLSKey = mustGetString(cmd, flag.Name)
} }
}) })

View File

@ -1,30 +1,53 @@
package cmd package cmd
import ( import (
"crypto/rand"
"crypto/tls" "crypto/tls"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
fbhttp "github.com/filebrowser/filebrowser/v2/http" fbhttp "github.com/filebrowser/filebrowser/v2/http"
homedir "github.com/mitchellh/go-homedir" "github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
// "github.com/spf13/pflag"
v "github.com/spf13/viper" v "github.com/spf13/viper"
lumberjack "gopkg.in/natefinch/lumberjack.v2" lumberjack "gopkg.in/natefinch/lumberjack.v2"
) )
var (
cfgFile string
)
func init() {
f := rootCmd.Flags()
pf := rootCmd.PersistentFlags()
f.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')")
vaddP(pf, "database", "d", "./filebrowser.db", "path to the database")
vaddP(f, "address", "a", "127.0.0.1", "address to listen on")
vaddP(f, "log", "l", "stdout", "log output")
vaddP(f, "port", "p", 8080, "port to listen on")
vaddP(f, "cert", "t", "", "tls certificate")
vaddP(f, "key", "k", "", "tls key")
vaddP(f, "scope", "s", ".", "scope to prepend to a user's scope when it is relative")
if err := v.BindPFlags(f); err != nil {
panic(err)
}
if err := v.BindPFlags(pf); err != nil {
panic(err)
}
}
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "filebrowser", Use: "filebrowser",
Short: "A stylish web-based file browser", Short: "A stylish web-based file browser",
@ -33,89 +56,133 @@ manage your user and all the configurations without accessing the
web interface. web interface.
If you've never run File Browser, you will need to create the database. If you've never run File Browser, you will need to create the database.
See 'filebrowser help config init' for more information. See 'filebrowser help config init' for more information.`,
Run: serveAndListen,
}
This command is used to start up the server. By default it starts listening func serveAndListen(cmd *cobra.Command, args []string) {
on localhost on a random port unless specified otherwise in the database or initConfig()
via flags.
switch logMethod := v.GetString("log"); logMethod {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: logMethod,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
if _, err := os.Stat(v.GetString("database")); os.IsNotExist(err) {
quickSetup(cmd)
}
Use the available flags to override the database/default options. These flags
values won't be persisted to the database. To persist configuration to the database
use the command 'filebrowser config set'.`,
Run: func(cmd *cobra.Command, args []string) {
db := getDB() db := getDB()
defer db.Close() defer db.Close()
st := getStorage(db) st := getStorage(db)
startServer(st)
port := v.GetInt("port")
address := v.GetString("address")
cert := v.GetString("cert")
key := v.GetString("key")
scope := v.GetString("scope")
scope, err := filepath.Abs(scope)
checkErr(err)
settings, err := st.Settings.Get()
checkErr(err)
settings.Scope = scope
err = st.Settings.Save(settings)
checkErr(err)
handler, err := fbhttp.NewHandler(st)
checkErr(err)
var listener net.Listener
if key != "" && cert != "" {
cer, err := tls.LoadX509KeyPair(cert, key)
checkErr(err)
config := &tls.Config{Certificates: []tls.Certificate{cer}}
listener, err = tls.Listen("tcp", address+":"+strconv.Itoa(port), config)
checkErr(err)
} else {
listener, err = net.Listen("tcp", address+":"+strconv.Itoa(port))
checkErr(err)
}
log.Println("Listening on", listener.Addr().String())
if err := http.Serve(listener, handler); err != nil {
log.Fatal(err)
}
}
func quickSetup(cmd *cobra.Command) {
db, err := storm.Open(v.GetString("database"))
checkErr(err)
defer db.Close()
set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: "",
Signup: false,
AuthMethod: auth.MethodJSONAuth,
Defaults: settings.UserDefaults{
Scope: ".",
Locale: "en",
Perm: users.Permissions{
Admin: false,
Execute: true,
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
},
}, },
} }
var ( st := getStorage(db)
cfgFile string
)
// POSSIBLE WORKAROUND TO IDENTIFY WHEN DEFAULT VALUES ARE BEING USED err = st.Settings.Save(set)
var defaults = struct { checkErr(err)
database string
address string err = st.Auth.Save(&auth.JSONAuth{})
log string checkErr(err)
port int
scope string password, err := users.HashPwd("admin")
admin string checkErr(err)
}{
"./filebrowser.db", user := &users.User{
"127.0.0.1", Username: "admin",
"stderr", Password: password,
80, LockPassword: false,
"/srv",
"admin",
} }
func init() { set.Defaults.Apply(user)
cobra.OnInitialize(initConfig) user.Perm.Admin = true
//rootCmd.SetVersionTemplate("File Browser {{printf \"version %s\" .Version}}\n")
f := rootCmd.Flags() err = st.Users.Save(user)
pf := rootCmd.PersistentFlags() checkErr(err)
pf.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')")
vaddP(pf, "database", "d", "./filebrowser.db", "path to the database")
vaddP(f, "address", "a", defaults.address, "address to listen on")
vaddP(f, "log", "l", defaults.log, "log output")
vaddP(f, "port", "p", defaults.port, "port to listen on")
vaddP(f, "cert", "t", "", "tls certificate (default comes from database)")
vaddP(f, "key", "k", "", "tls key (default comes from database)")
vaddP(f, "scope", "s", defaults.scope, "scope for users")
vaddP(f, "force", "f", false, "overwrite DB config with runtime params")
vaddP(f, "admin", "f", defaults.admin, "first username")
vaddP(f, "passwd", "f", "", "first username password hash")
vaddP(f, "baseurl", "b", "", "base URL")
// Bind the full flag sets to the configuration
if err := v.BindPFlags(f); err != nil {
panic(err)
}
if err := v.BindPFlags(pf); err != nil {
panic(err)
}
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.
func initConfig() { func initConfig() {
if cfgFile == "" { if cfgFile == "" {
// Find home directory.
home, err := homedir.Dir() home, err := homedir.Dir()
if err != nil { checkErr(err)
panic(err)
}
v.AddConfigPath(".") v.AddConfigPath(".")
v.AddConfigPath(home) v.AddConfigPath(home)
v.AddConfigPath("/etc/filebrowser/") v.AddConfigPath("/etc/filebrowser/")
v.SetConfigName(".filebrowser") v.SetConfigName(".filebrowser")
} else { } else {
// Use config file from the flag.
v.SetConfigFile(cfgFile) v.SetConfigFile(cfgFile)
} }
@ -131,174 +198,4 @@ func initConfig() {
} else { } else {
log.Println("Using config file:", v.ConfigFileUsed()) log.Println("Using config file:", v.ConfigFileUsed())
} }
log.Println("FORCE:", v.GetBool("force"))
/*
if DB exists
if force false
database has highest priority, if undefined in DB use config params
else
config params overwrite existing and non-existing params in DB
else
(quick)Setup with provided config params
*/
/*
DISPLAY WARNINGS WHEN DEFAULT VALUES ARE USED
This allows to know if a CLI flag was provided:
log.Println(rootCmd.Flags().Changed("database"))
However, that is not enough in order to know if a value came from a config file or from envvars.
This should allow so. But it seems not to work as expected (see spf13/viper#323):
log.Println(v.IsSet("database"))
*/
if _, err := os.Stat(v.GetString("database")); os.IsNotExist(err) {
quickSetup()
}
}
/*
func serverVisitAndReplace(s *settings.Settings) {
rootCmd.Flags().Visit(func(flag *pflag.Flag) {
switch flag.Name {
case "log":
s.Log = v.GetString(flag.Name)
case "address":
s.Server.Address = v.GetString(flag.Name)
case "port":
s.Server.Port = v.GetInt(flag.Name)
case "cert":
s.Server.TLSCert = v.GetString(flag.Name)
case "key":
s.Server.TLSKey = v.GetString(flag.Name)
}
})
}
*/
func quickSetup() {
scope := v.GetString("scope")
if scope == defaults.scope {
log.Println("[WARN] Using default value '/srv' as param 'scope'")
}
db, err := storm.Open(v.GetString("database"))
checkErr(err)
defer db.Close()
set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: v.GetString("baseurl"),
Log: v.GetString("log"),
Signup: false,
AuthMethod: auth.MethodJSONAuth,
Server: settings.Server{
Port: v.GetInt("port"),
Address: v.GetString("address"),
TLSCert: v.GetString("cert"),
TLSKey: v.GetString("key"),
},
Defaults: settings.UserDefaults{
Scope: scope,
Locale: "en",
Perm: users.Permissions{
Admin: false,
Execute: true,
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
},
},
}
// serverVisitAndReplace(set)
st := getStorage(db)
err = st.Settings.Save(set)
checkErr(err)
err = st.Auth.Save(&auth.JSONAuth{})
checkErr(err)
password := v.GetString("password")
if password == "" {
password, err = users.HashPwd("admin")
checkErr(err)
}
user := &users.User{
Username: v.GetString("admin"),
Password: password,
LockPassword: false,
}
set.Defaults.Apply(user)
user.Perm.Admin = true
err = st.Users.Save(user)
checkErr(err)
}
func setupLogger(s *settings.Settings) {
switch s.Log {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: s.Log,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
}
func startServer(st *storage.Storage) {
settings, err := st.Settings.Get()
checkErr(err)
// serverVisitAndReplace(settings)
setupLogger(settings)
handler, err := fbhttp.NewHandler(st)
checkErr(err)
var listener net.Listener
if settings.Server.TLSKey != "" && settings.Server.TLSCert != "" {
cer, err := tls.LoadX509KeyPair(settings.Server.TLSCert, settings.Server.TLSKey)
checkErr(err)
config := &tls.Config{Certificates: []tls.Certificate{cer}}
listener, err = tls.Listen("tcp", settings.Server.Address+":"+strconv.Itoa(settings.Server.Port), config)
checkErr(err)
} else {
listener, err = net.Listen("tcp", settings.Server.Address+":"+strconv.Itoa(settings.Server.Port))
checkErr(err)
}
log.Println("Listening on", listener.Addr().String())
if err := http.Serve(listener, handler); err != nil {
log.Fatal(err)
}
}
func generateRandomBytes(n int) []byte {
b := make([]byte, n)
_, err := rand.Read(b)
checkErr(err)
// Note that err == nil only if we read len(b) bytes.
return b
} }

View File

@ -39,7 +39,7 @@ func runRules(cmd *cobra.Command, users func(*users.User, *storage.Storage), glo
id := getUserIdentifier(cmd) id := getUserIdentifier(cmd)
if id != nil { if id != nil {
user, err := st.Users.Get(id) user, err := st.Users.Get("", id)
checkErr(err) checkErr(err)
if users != nil { if users != nil {

View File

@ -77,7 +77,7 @@ func addUserFlags(cmd *cobra.Command) {
cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order") cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order")
cmd.Flags().Bool("lockPassword", false, "lock password") cmd.Flags().Bool("lockPassword", false, "lock password")
cmd.Flags().StringSlice("commands", nil, "a list of the commands a user can execute") cmd.Flags().StringSlice("commands", nil, "a list of the commands a user can execute")
cmd.Flags().String("scope", "", "scope for users") cmd.Flags().String("scope", ".", "scope for users")
cmd.Flags().String("locale", "en", "locale for users") cmd.Flags().String("locale", "en", "locale for users")
cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users") cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users")
} }
@ -94,35 +94,35 @@ func getUserDefaults(cmd *cobra.Command, defaults *settings.UserDefaults, all bo
visit := func(flag *pflag.Flag) { visit := func(flag *pflag.Flag) {
switch flag.Name { switch flag.Name {
case "scope": case "scope":
defaults.Scope = mustGetString(cmd, "scope") defaults.Scope = mustGetString(cmd, flag.Name)
case "locale": case "locale":
defaults.Locale = mustGetString(cmd, "locale") defaults.Locale = mustGetString(cmd, flag.Name)
case "viewMode": case "viewMode":
defaults.ViewMode = getViewMode(cmd) defaults.ViewMode = getViewMode(cmd)
case "perm.admin": case "perm.admin":
defaults.Perm.Admin = mustGetBool(cmd, "perm.admin") defaults.Perm.Admin = mustGetBool(cmd, flag.Name)
case "perm.execute": case "perm.execute":
defaults.Perm.Execute = mustGetBool(cmd, "perm.execute") defaults.Perm.Execute = mustGetBool(cmd, flag.Name)
case "perm.create": case "perm.create":
defaults.Perm.Create = mustGetBool(cmd, "perm.create") defaults.Perm.Create = mustGetBool(cmd, flag.Name)
case "perm.rename": case "perm.rename":
defaults.Perm.Rename = mustGetBool(cmd, "perm.rename") defaults.Perm.Rename = mustGetBool(cmd, flag.Name)
case "perm.modify": case "perm.modify":
defaults.Perm.Modify = mustGetBool(cmd, "perm.modify") defaults.Perm.Modify = mustGetBool(cmd, flag.Name)
case "perm.delete": case "perm.delete":
defaults.Perm.Delete = mustGetBool(cmd, "perm.delete") defaults.Perm.Delete = mustGetBool(cmd, flag.Name)
case "perm.share": case "perm.share":
defaults.Perm.Share = mustGetBool(cmd, "perm.share") defaults.Perm.Share = mustGetBool(cmd, flag.Name)
case "perm.download": case "perm.download":
defaults.Perm.Download = mustGetBool(cmd, "perm.download") defaults.Perm.Download = mustGetBool(cmd, flag.Name)
case "commands": case "commands":
commands, err := cmd.Flags().GetStringSlice("commands") commands, err := cmd.Flags().GetStringSlice(flag.Name)
checkErr(err) checkErr(err)
defaults.Commands = commands defaults.Commands = commands
case "sorting.by": case "sorting.by":
defaults.Sorting.By = mustGetString(cmd, "sorting.by") defaults.Sorting.By = mustGetString(cmd, flag.Name)
case "sorting.asc": case "sorting.asc":
defaults.Sorting.Asc = mustGetBool(cmd, "sorting.asc") defaults.Sorting.Asc = mustGetBool(cmd, flag.Name)
} }
} }

View File

@ -32,19 +32,21 @@ var findUsers = func(cmd *cobra.Command, args []string) {
defer db.Close() defer db.Close()
st := getStorage(db) st := getStorage(db)
settings, err := st.Settings.Get()
checkErr(err)
username, _ := cmd.Flags().GetString("username") username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id") id, _ := cmd.Flags().GetUint("id")
var err error
var list []*users.User var list []*users.User
var user *users.User var user *users.User
if username != "" { if username != "" {
user, err = st.Users.Get(username) user, err = st.Users.Get(settings.Scope, username)
} else if id != 0 { } else if id != 0 {
user, err = st.Users.Get(id) user, err = st.Users.Get(settings.Scope, id)
} else { } else {
list, err = st.Users.Gets() list, err = st.Users.Gets(settings.Scope)
} }
checkErr(err) checkErr(err)

View File

@ -26,17 +26,19 @@ options you want to change.`,
defer db.Close() defer db.Close()
st := getStorage(db) st := getStorage(db)
set, err := st.Settings.Get()
checkErr(err)
id, _ := cmd.Flags().GetUint("id") id, _ := cmd.Flags().GetUint("id")
username := mustGetString(cmd, "username") username := mustGetString(cmd, "username")
password := mustGetString(cmd, "password") password := mustGetString(cmd, "password")
var user *users.User var user *users.User
var err error
if id != 0 { if id != 0 {
user, err = st.Users.Get(id) user, err = st.Users.Get(set.Scope, id)
} else { } else {
user, err = st.Users.Get(username) user, err = st.Users.Get(set.Scope, username)
} }
checkErr(err) checkErr(err)

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"crypto/rand"
"errors" "errors"
"os" "os"
@ -80,3 +81,11 @@ func getDB() *storm.DB {
func getStorage(db *storm.DB) *storage.Storage { func getStorage(db *storm.DB) *storage.Storage {
return bolt.NewStorage(db) return bolt.NewStorage(db)
} }
func generateRandomBytes(n int) []byte {
b := make([]byte, n)
_, err := rand.Read(b)
checkErr(err)
// Note that err == nil only if we read len(b) bytes.
return b
}

View File

@ -8,6 +8,7 @@ import (
"encoding/hex" "encoding/hex"
"hash" "hash"
"io" "io"
"log"
"mime" "mime"
"net/http" "net/http"
"os" "os"
@ -76,7 +77,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
return file, file.readListing(opts.Checker) return file, file.readListing(opts.Checker)
} }
err = file.detectType(opts.Modify) err = file.detectType(opts.Modify, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -126,17 +127,25 @@ func (i *FileInfo) Checksum(algo string) error {
return nil return nil
} }
func (i *FileInfo) detectType(modify bool) error { func (i *FileInfo) detectType(modify, saveContent bool) error {
// failing to detect the type should not return error.
// imagine the situation where a file in a dir with thousands
// of files couldn't be opened: we'd have immediately
// a 500 even though it doesn't matter. So we just log it.
reader, err := i.Fs.Open(i.Path) reader, err := i.Fs.Open(i.Path)
if err != nil { if err != nil {
return err log.Print(err)
i.Type = "blob"
return nil
} }
defer reader.Close() defer reader.Close()
buffer := make([]byte, 512) buffer := make([]byte, 512)
n, err := reader.Read(buffer) n, err := reader.Read(buffer)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return err log.Print(err)
i.Type = "blob"
return nil
} }
mimetype := mime.TypeByExtension(i.Extension) mimetype := mime.TypeByExtension(i.Extension)
@ -160,18 +169,21 @@ func (i *FileInfo) detectType(modify bool) error {
return nil return nil
default: default:
i.Type = "text" i.Type = "text"
if !modify {
i.Type = "textImmutable"
}
if saveContent {
afs := &afero.Afero{Fs: i.Fs} afs := &afero.Afero{Fs: i.Fs}
content, err := afs.ReadFile(i.Path) content, err := afs.ReadFile(i.Path)
if err != nil { if err != nil {
return err return err
} }
if !modify {
i.Type = "textImmutable"
}
i.Content = string(content) i.Content = string(content)
} }
}
return nil return nil
} }
@ -238,7 +250,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
} else { } else {
listing.NumFiles++ listing.NumFiles++
err := file.detectType(true) err := file.detectType(true, false)
if err != nil { if err != nil {
return err return err
} }

@ -1 +1 @@
Subproject commit 95fc3dfdfbe21b1d55538add66bf0d5f38197320 Subproject commit 0e7d4ef110ee550375d4bf15dfa9ded70214076a

View File

@ -67,7 +67,7 @@ func withUser(fn handleFunc) handleFunc {
w.Header().Add("X-Renew-Token", "true") w.Header().Add("X-Renew-Token", "true")
} }
d.user, err = d.store.Users.Get(tk.User.ID) d.user, err = d.store.Users.Get(d.settings.Scope, tk.User.ID)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -91,7 +91,7 @@ var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, e
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
user, err := auther.Auth(r) user, err := auther.Auth(r, d.store.Users, d.Settings)
if err == os.ErrPermission { if err == os.ErrPermission {
return http.StatusForbidden, nil return http.StatusForbidden, nil
} else if err != nil { } else if err != nil {

View File

@ -13,7 +13,7 @@ var withHashFile = func(fn handleFunc) handleFunc {
return errToStatus(err), err return errToStatus(err), err
} }
user, err := d.store.Users.Get(link.UserID) user, err := d.store.Users.Get(d.settings.Scope, link.UserID)
if err != nil { if err != nil {
return errToStatus(err), err return errToStatus(err), err
} }

View File

@ -85,9 +85,7 @@ func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler) {
return http.StatusNotFound, nil return http.StatusNotFound, nil
} }
w.Header().Set("x-frame-options", "SAMEORIGIN")
w.Header().Set("x-xss-protection", "1; mode=block") w.Header().Set("x-xss-protection", "1; mode=block")
return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8") return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8")
}, "", storage) }, "", storage)

View File

@ -61,7 +61,7 @@ func withSelfOrAdmin(fn handleFunc) handleFunc {
} }
var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
users, err := d.store.Users.Gets() users, err := d.store.Users.Gets(d.settings.Scope)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -78,7 +78,7 @@ var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
}) })
var userGetHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { var userGetHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
u, err := d.store.Users.Get(d.raw.(uint)) u, err := d.store.Users.Get(d.settings.Scope, d.raw.(uint))
if err == errors.ErrNotExist { if err == errors.ErrNotExist {
return http.StatusNotFound, err return http.StatusNotFound, err
} }
@ -147,7 +147,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
req.Data.Password, err = users.HashPwd(req.Data.Password) req.Data.Password, err = users.HashPwd(req.Data.Password)
} else { } else {
var suser *users.User var suser *users.User
suser, err = d.store.Users.Get(d.raw.(uint)) suser, err = d.store.Users.Get(d.settings.Scope, d.raw.(uint))
req.Data.Password = suser.Password req.Data.Password = suser.Password
} }

View File

@ -9,23 +9,14 @@ type AuthMethod string
type Settings struct { type Settings struct {
Key []byte `json:"key"` Key []byte `json:"key"`
BaseURL string `json:"baseURL"` BaseURL string `json:"baseURL"`
Log string `json:"log"` Scope string `json:"scope"`
Server Server `json:"server"`
Signup bool `json:"signup"` Signup bool `json:"signup"`
Defaults UserDefaults `json:"defaults"` Defaults UserDefaults `json:"defaults"`
AuthMethod AuthMethod `json:"authMethod"` AuthMethod AuthMethod `json:"authMethod"`
Branding Branding `json:"branding"` Branding Branding `json:"branding"`
Commands map[string][]string `json:"commands"` Commands map[string][]string `json:"commands"`
Shell []string `json:"shell"` Shell []string `json:"shell"`
Rules []rules.Rule `json:"rules"` // TODO: use this add to cli Rules []rules.Rule `json:"rules"`
}
// Server settings.
type Server struct {
Port int `json:"port"`
Address string `json:"address"`
TLSCert string `json:"tlsCert"`
TLSKey string `json:"tlsKey"`
} }
// GetRules implements rules.Provider. // GetRules implements rules.Provider.

View File

@ -111,7 +111,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
s := &settings.Settings{ s := &settings.Settings{
Key: key, Key: key,
BaseURL: cfg.BaseURL, BaseURL: cfg.BaseURL,
Log: cfg.Log,
Signup: false, Signup: false,
Defaults: settings.UserDefaults{ Defaults: settings.UserDefaults{
Scope: cfg.Defaults.Scope, Scope: cfg.Defaults.Scope,
@ -129,10 +128,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
Download: true, Download: true,
}, },
}, },
Server: settings.Server{
Address: cfg.Address,
Port: cfg.Port,
},
} }
var auther auth.Auther var auther auth.Auther

View File

@ -3,7 +3,6 @@ package importer
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
@ -52,7 +51,6 @@ func readOldUsers(db *storm.DB) ([]*oldUser, error) {
} }
func convertUsersToNew(old []*oldUser) ([]*users.User, error) { func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
var err error
list := []*users.User{} list := []*users.User{}
for _, oldUser := range old { for _, oldUser := range old {
@ -82,12 +80,7 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
user.Rules = append(user.Rules, *rule) user.Rules = append(user.Rules, *rule)
} }
user.Scope, err = filepath.Abs(user.Scope) err := user.Clean("")
if err != nil {
return nil, err
}
err = user.Clean()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -36,7 +36,7 @@ func NewStorage(back StorageBackend) *Storage {
// Get allows you to get a user by its name or username. The provided // Get allows you to get a user by its name or username. The provided
// id must be a string for username lookup or a uint for id lookup. If id // id must be a string for username lookup or a uint for id lookup. If id
// is neither, a ErrInvalidDataType will be returned. // is neither, a ErrInvalidDataType will be returned.
func (s *Storage) Get(id interface{}) (*User, error) { func (s *Storage) Get(baseScope string, id interface{}) (*User, error) {
var ( var (
user *User user *User
err error err error
@ -55,19 +55,19 @@ func (s *Storage) Get(id interface{}) (*User, error) {
return nil, err return nil, err
} }
user.Clean() user.Clean(baseScope)
return user, err return user, err
} }
// Gets gets a list of all users. // Gets gets a list of all users.
func (s *Storage) Gets() ([]*User, error) { func (s *Storage) Gets(baseScope string) ([]*User, error) {
users, err := s.back.Gets() users, err := s.back.Gets()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, user := range users { for _, user := range users {
user.Clean() user.Clean(baseScope)
} }
return users, err return users, err
@ -75,7 +75,7 @@ func (s *Storage) Gets() ([]*User, error) {
// Update updates a user in the database. // Update updates a user in the database.
func (s *Storage) Update(user *User, fields ...string) error { func (s *Storage) Update(user *User, fields ...string) error {
err := user.Clean(fields...) err := user.Clean("", fields...)
if err != nil { if err != nil {
return err return err
} }
@ -93,7 +93,7 @@ func (s *Storage) Update(user *User, fields ...string) error {
// Save saves the user in a storage. // Save saves the user in a storage.
func (s *Storage) Save(user *User) error { func (s *Storage) Save(user *User) error {
if err := user.Clean(); err != nil { if err := user.Clean(""); err != nil {
return err return err
} }

View File

@ -1,10 +1,11 @@
package users package users
import ( import (
"github.com/filebrowser/filebrowser/v2/errors"
"path/filepath" "path/filepath"
"regexp" "regexp"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/files" "github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -51,7 +52,7 @@ var checkableFields = []string{
// Clean cleans up a user and verifies if all its fields // Clean cleans up a user and verifies if all its fields
// are alright to be saved. // are alright to be saved.
func (u *User) Clean(fields ...string) error { func (u *User) Clean(baseScope string, fields ...string) error {
if len(fields) == 0 { if len(fields) == 0 {
fields = checkableFields fields = checkableFields
} }
@ -66,10 +67,6 @@ func (u *User) Clean(fields ...string) error {
if u.Password == "" { if u.Password == "" {
return errors.ErrEmptyPassword return errors.ErrEmptyPassword
} }
case "Scope":
if !filepath.IsAbs(u.Scope) {
return errors.ErrScopeIsRelative
}
case "ViewMode": case "ViewMode":
if u.ViewMode == "" { if u.ViewMode == "" {
u.ViewMode = ListViewMode u.ViewMode = ListViewMode
@ -90,7 +87,13 @@ func (u *User) Clean(fields ...string) error {
} }
if u.Fs == nil { if u.Fs == nil {
u.Fs = afero.NewBasePathFs(afero.NewOsFs(), u.Scope) scope := u.Scope
if !filepath.IsAbs(scope) {
scope = filepath.Join(baseScope, scope)
}
u.Fs = afero.NewBasePathFs(afero.NewOsFs(), scope)
} }
return nil return nil