mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
merge with master
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
Former-commit-id: dfac33db9ca3454cd3af02a732a64a02a8268b01 [formerly 38be015f7b3376a3bc992725c1cb1a019c1843cb] [formerly 5a4e310b96ebc5895e64f8eeb2f592890d4b63b0 [formerly 2b8bd28158
]]
Former-commit-id: a30bfcebe6bbc115990b8f8398171b32942a8767 [formerly 553fafa22b46b2689ece093f3b0cf85b3cff1c87]
Former-commit-id: 5ce047d4e3f5217a179685f8b2850d4eb157376e
This commit is contained in:
commit
e5c150e83f
@ -3,13 +3,12 @@ package auth
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
|
||||
// Auther is the authentication interface.
|
||||
type Auther interface {
|
||||
// Auth is called to authenticate a request.
|
||||
Auth(*http.Request) (*users.User, error)
|
||||
// SetStorage attaches the Storage instance.
|
||||
SetStorage(*users.Storage)
|
||||
Auth(*http.Request, *users.Storage, *settings.Settings) (*users.User, error)
|
||||
}
|
||||
|
10
auth/json.go
10
auth/json.go
@ -23,11 +23,10 @@ type jsonCred struct {
|
||||
// JSONAuth is a json implementaion of an Auther.
|
||||
type JSONAuth struct {
|
||||
ReCaptcha *ReCaptcha
|
||||
storage *users.Storage
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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) {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
@ -60,11 +59,6 @@ func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) {
|
||||
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"
|
||||
|
||||
// ReCaptcha identifies a recaptcha conenction.
|
||||
|
10
auth/none.go
10
auth/none.go
@ -12,15 +12,9 @@ const MethodNoAuth settings.AuthMethod = "noauth"
|
||||
|
||||
// NoAuth is no auth implementation of auther.
|
||||
type NoAuth struct {
|
||||
storage *users.Storage
|
||||
}
|
||||
|
||||
// Auth uses authenticates user 1.
|
||||
func (a *NoAuth) Auth(r *http.Request) (*users.User, error) {
|
||||
return a.storage.Get(1)
|
||||
}
|
||||
|
||||
// SetStorage attaches the storage to the auther.
|
||||
func (a *NoAuth) SetStorage(s *users.Storage) {
|
||||
a.storage = s
|
||||
func (a *NoAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) {
|
||||
return sto.Get(set.Scope, 1)
|
||||
}
|
||||
|
@ -14,22 +14,16 @@ const MethodProxyAuth settings.AuthMethod = "proxy"
|
||||
|
||||
// ProxyAuth is a proxy implementation of an auther.
|
||||
type ProxyAuth struct {
|
||||
Header string
|
||||
storage *users.Storage
|
||||
Header string
|
||||
}
|
||||
|
||||
// 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)
|
||||
user, err := a.storage.Get(username)
|
||||
user, err := sto.Get(set.Scope, username)
|
||||
if err == errors.ErrNotExist {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
return user, err
|
||||
}
|
||||
|
||||
// SetStorage attaches the storage to the auther.
|
||||
func (a *ProxyAuth) SetStorage(s *users.Storage) {
|
||||
a.storage = s
|
||||
}
|
||||
|
@ -22,15 +22,9 @@ func NewStorage(back StorageBackend, users *users.Storage) *Storage {
|
||||
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) {
|
||||
auther, err := s.back.Get(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auther.SetStorage(s.users)
|
||||
return auther, nil
|
||||
return s.back.Get(t)
|
||||
}
|
||||
|
||||
// Save wraps a StorageBackend.Save.
|
||||
|
@ -35,12 +35,6 @@ func addConfigFlags(cmd *cobra.Command) {
|
||||
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().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.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, "Auth method:\t%s\n", s.AuthMethod)
|
||||
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.Fprintf(w, "\tName:\t%s\n", s.Branding.Name)
|
||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files)
|
||||
|
@ -16,7 +16,6 @@ func init() {
|
||||
configCmd.AddCommand(configInitCmd)
|
||||
rootCmd.AddCommand(configInitCmd)
|
||||
addConfigFlags(configInitCmd)
|
||||
configInitCmd.MarkFlagRequired("scope")
|
||||
}
|
||||
|
||||
var configInitCmd = &cobra.Command{
|
||||
@ -45,17 +44,10 @@ override the options.`,
|
||||
s := &settings.Settings{
|
||||
Key: generateRandomBytes(64), // 256 bit
|
||||
BaseURL: mustGetString(cmd, "baseURL"),
|
||||
Log: mustGetString(cmd, "log"),
|
||||
Signup: mustGetBool(cmd, "signup"),
|
||||
Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "),
|
||||
AuthMethod: authMethod,
|
||||
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{
|
||||
Name: mustGetString(cmd, "branding.name"),
|
||||
DisableExternal: mustGetBool(cmd, "branding.disableExternal"),
|
||||
|
@ -44,16 +44,6 @@ you want to change.`,
|
||||
s.Branding.DisableExternal = mustGetBool(cmd, flag.Name)
|
||||
case "branding.files":
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
|
379
cmd/root.go
379
cmd/root.go
@ -1,30 +1,53 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/asdine/storm"
|
||||
"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"
|
||||
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/pflag"
|
||||
v "github.com/spf13/viper"
|
||||
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{
|
||||
Use: "filebrowser",
|
||||
Short: "A stylish web-based file browser",
|
||||
@ -33,89 +56,133 @@ manage your user and all the configurations without accessing the
|
||||
web interface.
|
||||
|
||||
If you've never run File Browser, you will need to create the database.
|
||||
See 'filebrowser help config init' for more information.
|
||||
|
||||
This command is used to start up the server. By default it starts listening
|
||||
on localhost on a random port unless specified otherwise in the database or
|
||||
via flags.
|
||||
|
||||
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()
|
||||
defer db.Close()
|
||||
st := getStorage(db)
|
||||
startServer(st)
|
||||
},
|
||||
See 'filebrowser help config init' for more information.`,
|
||||
Run: serveAndListen,
|
||||
}
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
)
|
||||
func serveAndListen(cmd *cobra.Command, args []string) {
|
||||
initConfig()
|
||||
|
||||
// POSSIBLE WORKAROUND TO IDENTIFY WHEN DEFAULT VALUES ARE BEING USED
|
||||
var defaults = struct {
|
||||
database string
|
||||
address string
|
||||
log string
|
||||
port int
|
||||
scope string
|
||||
admin string
|
||||
}{
|
||||
"./filebrowser.db",
|
||||
"127.0.0.1",
|
||||
"stderr",
|
||||
80,
|
||||
"/srv",
|
||||
"admin",
|
||||
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)
|
||||
}
|
||||
|
||||
db := getDB()
|
||||
defer db.Close()
|
||||
st := getStorage(db)
|
||||
|
||||
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 init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
//rootCmd.SetVersionTemplate("File Browser {{printf \"version %s\" .Version}}\n")
|
||||
func quickSetup(cmd *cobra.Command) {
|
||||
db, err := storm.Open(v.GetString("database"))
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
f := rootCmd.Flags()
|
||||
pf := rootCmd.PersistentFlags()
|
||||
|
||||
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)
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := v.BindPFlags(pf); err != nil {
|
||||
panic(err)
|
||||
|
||||
st := getStorage(db)
|
||||
|
||||
err = st.Settings.Save(set)
|
||||
checkErr(err)
|
||||
|
||||
err = st.Auth.Save(&auth.JSONAuth{})
|
||||
checkErr(err)
|
||||
|
||||
password, err := users.HashPwd("admin")
|
||||
checkErr(err)
|
||||
|
||||
user := &users.User{
|
||||
Username: "admin",
|
||||
Password: password,
|
||||
LockPassword: false,
|
||||
}
|
||||
|
||||
set.Defaults.Apply(user)
|
||||
user.Perm.Admin = true
|
||||
|
||||
err = st.Users.Save(user)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile == "" {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
checkErr(err)
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath(home)
|
||||
v.AddConfigPath("/etc/filebrowser/")
|
||||
v.SetConfigName(".filebrowser")
|
||||
} else {
|
||||
// Use config file from the flag.
|
||||
v.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
||||
@ -131,174 +198,4 @@ func initConfig() {
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func runRules(cmd *cobra.Command, users func(*users.User, *storage.Storage), glo
|
||||
|
||||
id := getUserIdentifier(cmd)
|
||||
if id != nil {
|
||||
user, err := st.Users.Get(id)
|
||||
user, err := st.Users.Get("", id)
|
||||
checkErr(err)
|
||||
|
||||
if users != nil {
|
||||
|
28
cmd/users.go
28
cmd/users.go
@ -77,7 +77,7 @@ func addUserFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order")
|
||||
cmd.Flags().Bool("lockPassword", false, "lock password")
|
||||
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("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) {
|
||||
switch flag.Name {
|
||||
case "scope":
|
||||
defaults.Scope = mustGetString(cmd, "scope")
|
||||
defaults.Scope = mustGetString(cmd, flag.Name)
|
||||
case "locale":
|
||||
defaults.Locale = mustGetString(cmd, "locale")
|
||||
defaults.Locale = mustGetString(cmd, flag.Name)
|
||||
case "viewMode":
|
||||
defaults.ViewMode = getViewMode(cmd)
|
||||
case "perm.admin":
|
||||
defaults.Perm.Admin = mustGetBool(cmd, "perm.admin")
|
||||
defaults.Perm.Admin = mustGetBool(cmd, flag.Name)
|
||||
case "perm.execute":
|
||||
defaults.Perm.Execute = mustGetBool(cmd, "perm.execute")
|
||||
defaults.Perm.Execute = mustGetBool(cmd, flag.Name)
|
||||
case "perm.create":
|
||||
defaults.Perm.Create = mustGetBool(cmd, "perm.create")
|
||||
defaults.Perm.Create = mustGetBool(cmd, flag.Name)
|
||||
case "perm.rename":
|
||||
defaults.Perm.Rename = mustGetBool(cmd, "perm.rename")
|
||||
defaults.Perm.Rename = mustGetBool(cmd, flag.Name)
|
||||
case "perm.modify":
|
||||
defaults.Perm.Modify = mustGetBool(cmd, "perm.modify")
|
||||
defaults.Perm.Modify = mustGetBool(cmd, flag.Name)
|
||||
case "perm.delete":
|
||||
defaults.Perm.Delete = mustGetBool(cmd, "perm.delete")
|
||||
defaults.Perm.Delete = mustGetBool(cmd, flag.Name)
|
||||
case "perm.share":
|
||||
defaults.Perm.Share = mustGetBool(cmd, "perm.share")
|
||||
defaults.Perm.Share = mustGetBool(cmd, flag.Name)
|
||||
case "perm.download":
|
||||
defaults.Perm.Download = mustGetBool(cmd, "perm.download")
|
||||
defaults.Perm.Download = mustGetBool(cmd, flag.Name)
|
||||
case "commands":
|
||||
commands, err := cmd.Flags().GetStringSlice("commands")
|
||||
commands, err := cmd.Flags().GetStringSlice(flag.Name)
|
||||
checkErr(err)
|
||||
defaults.Commands = commands
|
||||
case "sorting.by":
|
||||
defaults.Sorting.By = mustGetString(cmd, "sorting.by")
|
||||
defaults.Sorting.By = mustGetString(cmd, flag.Name)
|
||||
case "sorting.asc":
|
||||
defaults.Sorting.Asc = mustGetBool(cmd, "sorting.asc")
|
||||
defaults.Sorting.Asc = mustGetBool(cmd, flag.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,19 +32,21 @@ var findUsers = func(cmd *cobra.Command, args []string) {
|
||||
defer db.Close()
|
||||
st := getStorage(db)
|
||||
|
||||
settings, err := st.Settings.Get()
|
||||
checkErr(err)
|
||||
|
||||
username, _ := cmd.Flags().GetString("username")
|
||||
id, _ := cmd.Flags().GetUint("id")
|
||||
|
||||
var err error
|
||||
var list []*users.User
|
||||
var user *users.User
|
||||
|
||||
if username != "" {
|
||||
user, err = st.Users.Get(username)
|
||||
user, err = st.Users.Get(settings.Scope, username)
|
||||
} else if id != 0 {
|
||||
user, err = st.Users.Get(id)
|
||||
user, err = st.Users.Get(settings.Scope, id)
|
||||
} else {
|
||||
list, err = st.Users.Gets()
|
||||
list, err = st.Users.Gets(settings.Scope)
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
|
@ -26,17 +26,19 @@ options you want to change.`,
|
||||
defer db.Close()
|
||||
st := getStorage(db)
|
||||
|
||||
set, err := st.Settings.Get()
|
||||
checkErr(err)
|
||||
|
||||
id, _ := cmd.Flags().GetUint("id")
|
||||
username := mustGetString(cmd, "username")
|
||||
password := mustGetString(cmd, "password")
|
||||
|
||||
var user *users.User
|
||||
var err error
|
||||
|
||||
if id != 0 {
|
||||
user, err = st.Users.Get(id)
|
||||
user, err = st.Users.Get(set.Scope, id)
|
||||
} else {
|
||||
user, err = st.Users.Get(username)
|
||||
user, err = st.Users.Get(set.Scope, username)
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
@ -80,3 +81,11 @@ func getDB() *storm.DB {
|
||||
func getStorage(db *storm.DB) *storage.Storage {
|
||||
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
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -76,7 +77,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||
return file, file.readListing(opts.Checker)
|
||||
}
|
||||
|
||||
err = file.detectType(opts.Modify)
|
||||
err = file.detectType(opts.Modify, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -126,17 +127,25 @@ func (i *FileInfo) Checksum(algo string) error {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
i.Type = "blob"
|
||||
return nil
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
buffer := make([]byte, 512)
|
||||
n, err := reader.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
log.Print(err)
|
||||
i.Type = "blob"
|
||||
return nil
|
||||
}
|
||||
|
||||
mimetype := mime.TypeByExtension(i.Extension)
|
||||
@ -160,17 +169,20 @@ func (i *FileInfo) detectType(modify bool) error {
|
||||
return nil
|
||||
default:
|
||||
i.Type = "text"
|
||||
afs := &afero.Afero{Fs: i.Fs}
|
||||
content, err := afs.ReadFile(i.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !modify {
|
||||
i.Type = "textImmutable"
|
||||
}
|
||||
|
||||
i.Content = string(content)
|
||||
if saveContent {
|
||||
afs := &afero.Afero{Fs: i.Fs}
|
||||
content, err := afs.ReadFile(i.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.Content = string(content)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -238,7 +250,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
|
||||
} else {
|
||||
listing.NumFiles++
|
||||
|
||||
err := file.detectType(true)
|
||||
err := file.detectType(true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
2
frontend
2
frontend
@ -1 +1 @@
|
||||
Subproject commit 95fc3dfdfbe21b1d55538add66bf0d5f38197320
|
||||
Subproject commit 0e7d4ef110ee550375d4bf15dfa9ded70214076a
|
@ -67,7 +67,7 @@ func withUser(fn handleFunc) handleFunc {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
user, err := auther.Auth(r)
|
||||
user, err := auther.Auth(r, d.store.Users, d.Settings)
|
||||
if err == os.ErrPermission {
|
||||
return http.StatusForbidden, nil
|
||||
} else if err != nil {
|
||||
|
@ -13,7 +13,7 @@ var withHashFile = func(fn handleFunc) handleFunc {
|
||||
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 {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
@ -85,9 +85,7 @@ func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler) {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||
|
||||
return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8")
|
||||
}, "", storage)
|
||||
|
||||
|
@ -61,7 +61,7 @@ func withSelfOrAdmin(fn handleFunc) handleFunc {
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -9,23 +9,14 @@ type AuthMethod string
|
||||
type Settings struct {
|
||||
Key []byte `json:"key"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Log string `json:"log"`
|
||||
Server Server `json:"server"`
|
||||
Scope string `json:"scope"`
|
||||
Signup bool `json:"signup"`
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
AuthMethod AuthMethod `json:"authMethod"`
|
||||
Branding Branding `json:"branding"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Shell []string `json:"shell"`
|
||||
Rules []rules.Rule `json:"rules"` // TODO: use this add to cli
|
||||
}
|
||||
|
||||
// Server settings.
|
||||
type Server struct {
|
||||
Port int `json:"port"`
|
||||
Address string `json:"address"`
|
||||
TLSCert string `json:"tlsCert"`
|
||||
TLSKey string `json:"tlsKey"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
}
|
||||
|
||||
// GetRules implements rules.Provider.
|
||||
|
@ -111,7 +111,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
||||
s := &settings.Settings{
|
||||
Key: key,
|
||||
BaseURL: cfg.BaseURL,
|
||||
Log: cfg.Log,
|
||||
Signup: false,
|
||||
Defaults: settings.UserDefaults{
|
||||
Scope: cfg.Defaults.Scope,
|
||||
@ -129,10 +128,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
||||
Download: true,
|
||||
},
|
||||
},
|
||||
Server: settings.Server{
|
||||
Address: cfg.Address,
|
||||
Port: cfg.Port,
|
||||
},
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
|
@ -3,7 +3,6 @@ package importer
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/asdine/storm"
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
@ -52,7 +51,6 @@ func readOldUsers(db *storm.DB) ([]*oldUser, error) {
|
||||
}
|
||||
|
||||
func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
|
||||
var err error
|
||||
list := []*users.User{}
|
||||
|
||||
for _, oldUser := range old {
|
||||
@ -82,12 +80,7 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
|
||||
user.Rules = append(user.Rules, *rule)
|
||||
}
|
||||
|
||||
user.Scope, err = filepath.Abs(user.Scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = user.Clean()
|
||||
err := user.Clean("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func NewStorage(back StorageBackend) *Storage {
|
||||
// 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
|
||||
// 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 (
|
||||
user *User
|
||||
err error
|
||||
@ -55,19 +55,19 @@ func (s *Storage) Get(id interface{}) (*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.Clean()
|
||||
user.Clean(baseScope)
|
||||
return user, err
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
user.Clean()
|
||||
user.Clean(baseScope)
|
||||
}
|
||||
|
||||
return users, err
|
||||
@ -75,7 +75,7 @@ func (s *Storage) Gets() ([]*User, error) {
|
||||
|
||||
// Update updates a user in the database.
|
||||
func (s *Storage) Update(user *User, fields ...string) error {
|
||||
err := user.Clean(fields...)
|
||||
err := user.Clean("", fields...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -93,7 +93,7 @@ func (s *Storage) Update(user *User, fields ...string) error {
|
||||
|
||||
// Save saves the user in a storage.
|
||||
func (s *Storage) Save(user *User) error {
|
||||
if err := user.Clean(); err != nil {
|
||||
if err := user.Clean(""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
"github.com/spf13/afero"
|
||||
@ -51,7 +52,7 @@ var checkableFields = []string{
|
||||
|
||||
// Clean cleans up a user and verifies if all its fields
|
||||
// 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 {
|
||||
fields = checkableFields
|
||||
}
|
||||
@ -66,10 +67,6 @@ func (u *User) Clean(fields ...string) error {
|
||||
if u.Password == "" {
|
||||
return errors.ErrEmptyPassword
|
||||
}
|
||||
case "Scope":
|
||||
if !filepath.IsAbs(u.Scope) {
|
||||
return errors.ErrScopeIsRelative
|
||||
}
|
||||
case "ViewMode":
|
||||
if u.ViewMode == "" {
|
||||
u.ViewMode = ListViewMode
|
||||
@ -90,7 +87,13 @@ func (u *User) Clean(fields ...string) error {
|
||||
}
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user