feat: new user api and quick setup

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
Henrique Dias 2018-12-31 16:03:36 +00:00
parent 0f7f4933bb
commit 4744dc3f75
21 changed files with 335 additions and 179 deletions

14
cmd/cmd.go Normal file
View File

@ -0,0 +1,14 @@
package cmd
import (
"fmt"
"os"
)
// Execute executes the commands.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/filebrowser/filebrowser/bolt"
"github.com/spf13/cobra"
)
@ -21,15 +20,15 @@ var cmdsAddCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := bolt.ConfigStore{DB: db}
r, err := st.GetRunner()
st := getStore(db)
r, err := st.Config.GetRunner()
checkErr(err)
evt := mustGetString(cmd, "event")
command := mustGetString(cmd, "command")
r.Commands[evt] = append(r.Commands[evt], command)
err = st.SaveRunner(r)
err = st.Config.SaveRunner(r)
checkErr(err)
printEvents(r.Commands)
},

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/filebrowser/filebrowser/bolt"
"github.com/spf13/cobra"
)
@ -18,8 +17,8 @@ var cmdsLsCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := bolt.ConfigStore{DB: db}
r, err := st.GetRunner()
st := getStore(db)
r, err := st.Config.GetRunner()
checkErr(err)
evt := mustGetString(cmd, "event")

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/filebrowser/filebrowser/bolt"
"github.com/spf13/cobra"
)
@ -21,8 +20,8 @@ var cmdsRmCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := bolt.ConfigStore{DB: db}
r, err := st.GetRunner()
st := getStore(db)
r, err := st.Config.GetRunner()
checkErr(err)
evt := mustGetString(cmd, "event")
@ -30,7 +29,7 @@ var cmdsRmCmd = &cobra.Command{
checkErr(err)
r.Commands[evt] = append(r.Commands[evt][:i], r.Commands[evt][i+1:]...)
err = st.SaveRunner(r)
err = st.Config.SaveRunner(r)
checkErr(err)
printEvents(r.Commands)
},

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/filebrowser/filebrowser/bolt"
"github.com/spf13/cobra"
)
@ -17,10 +16,10 @@ var configCatCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := bolt.ConfigStore{DB: db}
s, err := st.GetSettings()
st := getStore(db)
s, err := st.Config.GetSettings()
checkErr(err)
auther, err := st.GetAuther(s.AuthMethod)
auther, err := st.Config.GetAuther(s.AuthMethod)
checkErr(err)
printSettings(s, auther)
},

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/bolt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
@ -26,50 +27,49 @@ this options can be changed in the future with the command
to the defaults when creating new users and you don't
override the options.`,
Args: cobra.NoArgs,
Run: initDatabase,
}
Run: func(cmd *cobra.Command, args []string) {
if _, err := os.Stat(databasePath); err == nil {
panic(errors.New(databasePath + " already exists"))
}
func initDatabase(cmd *cobra.Command, args []string) {
if _, err := os.Stat(databasePath); err == nil {
panic(errors.New(databasePath + " already exists"))
}
defaults := types.UserDefaults{}
getUserDefaults(cmd, &defaults, true)
authMethod, auther := getAuthentication(cmd)
defaults := types.UserDefaults{}
getUserDefaults(cmd, &defaults, true)
authMethod, auther := getAuthentication(cmd)
settings := &types.Settings{
Key: generateRandomBytes(64), // 256 bits
BaseURL: mustGetString(cmd, "baseURL"),
Signup: mustGetBool(cmd, "signup"),
Defaults: defaults,
AuthMethod: authMethod,
Branding: types.Branding{
Name: mustGetString(cmd, "branding.name"),
DisableExternal: mustGetBool(cmd, "branding.disableExternal"),
Files: mustGetString(cmd, "branding.files"),
},
}
settings := &types.Settings{
Key: generateRandomBytes(64), // 256 bits
BaseURL: mustGetString(cmd, "baseURL"),
Signup: mustGetBool(cmd, "signup"),
Defaults: defaults,
AuthMethod: authMethod,
}
db, err := bolt.Open(databasePath)
checkErr(err)
defer db.Close()
runner := &types.Runner{
Commands: map[string][]string{},
}
saveConfig(db, settings, &types.Runner{}, auther)
for _, event := range types.DefaultEvents {
runner.Commands[event] = []string{}
}
db, err := bolt.Open(databasePath)
checkErr(err)
defer db.Close()
st := bolt.ConfigStore{DB: db}
err = st.SaveSettings(settings)
checkErr(err)
err = st.SaveRunner(runner)
checkErr(err)
err = st.SaveAuther(auther)
checkErr(err)
fmt.Printf(`
fmt.Printf(`
Congratulations! You've set up your database to use with File Browser.
Now add your first user via 'filebrowser users new' and then you just
need to call the main command to boot up the server.
`)
printSettings(settings, auther)
printSettings(settings, auther)
},
}
func saveConfig(db *storm.DB, s *types.Settings, r *types.Runner, a types.Auther) {
st := getStore(db)
err := st.Config.SaveSettings(s)
checkErr(err)
err = st.Config.SaveRunner(r)
checkErr(err)
err = st.Config.SaveAuther(a)
checkErr(err)
}

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/filebrowser/filebrowser/bolt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -22,8 +21,8 @@ you want to change.`,
db := getDB()
defer db.Close()
st := bolt.ConfigStore{DB: db}
s, err := st.GetSettings()
st := getStore(db)
s, err := st.Config.GetSettings()
checkErr(err)
auth := false
@ -49,14 +48,14 @@ you want to change.`,
var auther types.Auther
if auth {
s.AuthMethod, auther = getAuthentication(cmd)
err = st.SaveAuther(auther)
err = st.Config.SaveAuther(auther)
checkErr(err)
} else {
auther, err = st.GetAuther(s.AuthMethod)
auther, err = st.Config.GetAuther(s.AuthMethod)
checkErr(err)
}
err = st.SaveSettings(s)
err = st.Config.SaveSettings(s)
checkErr(err)
printSettings(s, auther)
},

View File

@ -2,7 +2,7 @@ package cmd
import (
"crypto/tls"
"fmt"
"errors"
"io/ioutil"
"log"
"net"
@ -10,9 +10,11 @@ import (
"os"
"strconv"
"github.com/filebrowser/filebrowser/auth"
"github.com/filebrowser/filebrowser/bolt"
fhttp "github.com/filebrowser/filebrowser/http"
"github.com/filebrowser/filebrowser/types"
fhttp "github.com/filebrowser/filebrowser/http"
"github.com/spf13/cobra"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
)
@ -29,7 +31,7 @@ func init() {
rootCmd.Flags().IntP("port", "p", 0, "port to listen on")
rootCmd.Flags().StringP("cert", "c", "", "tls certificate")
rootCmd.Flags().StringP("key", "k", "", "tls key")
rootCmd.AddCommand(versionCmd)
rootCmd.Flags().StringP("scope", "s", "", "scope for users")
}
var rootCmd = &cobra.Command{
@ -45,34 +47,18 @@ See 'filebrowser help config init' for more information.
This command is used to start up the server. By default it starts
listening on loalhost on a random port. Use the flags to change it.`,
Run: func(cmd *cobra.Command, args []string) {
switch l := mustGetString(cmd, "log"); l {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: l,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
setupLogger(cmd)
if _, err := os.Stat(databasePath); os.IsNotExist(err) {
quickSetup(cmd)
}
var err error
db := getDB()
defer db.Close()
usersStore := &types.UsersVerify{Store: bolt.UsersStore{DB: db}}
env := &fhttp.Env{
Store: &types.Store{
Users: usersStore,
Config: bolt.ConfigStore{DB: db, Users: usersStore},
Share: bolt.ShareStore{DB: db},
},
Store: getStore(db),
}
env.Settings, err = env.Store.Config.GetSettings()
@ -82,45 +68,101 @@ listening on loalhost on a random port. Use the flags to change it.`,
env.Runner, err = env.Store.Config.GetRunner()
checkErr(err)
addr := mustGetString(cmd, "address")
port, err := cmd.Flags().GetInt("port")
checkErr(err)
cert := mustGetString(cmd, "cert")
key := mustGetString(cmd, "key")
var listener net.Listener
if cert != "" && key != "" {
cer, err := tls.LoadX509KeyPair(cert, key)
checkErr(err)
config := &tls.Config{Certificates: []tls.Certificate{cer}}
listener, err = tls.Listen("tcp", addr+":"+strconv.Itoa(port), config)
checkErr(err)
} else {
listener, err = net.Listen("tcp", addr+":"+strconv.Itoa(port))
checkErr(err)
}
log.Println("Listening on", listener.Addr().String())
if err := http.Serve(listener, fhttp.Handler(env)); err != nil {
log.Fatal(err)
}
startServer(cmd, env)
},
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("File Browser Version " + types.Version)
},
}
// Execute executes the commands.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
func setupLogger(cmd *cobra.Command) {
switch l := mustGetString(cmd, "log"); l {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: l,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
}
func quickSetup(cmd *cobra.Command) {
scope := mustGetString(cmd, "scope")
if scope == "" {
panic(errors.New("scope flag must be set for quick setup"))
}
settings := &types.Settings{
Key: generateRandomBytes(64),
BaseURL: "",
Signup: false,
AuthMethod: auth.MethodJSONAuth,
Defaults: types.UserDefaults{
Scope: scope,
Locale: "en",
Perm: types.Permissions{
Admin: false,
Execute: true,
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
},
},
}
password, err := types.HashPwd("admin")
checkErr(err)
user := &types.User{
Username: "admin",
Password: password,
LockPassword: false,
}
user.ApplyDefaults(settings.Defaults)
user.Perm.Admin = true
db, err := bolt.Open(databasePath)
checkErr(err)
defer db.Close()
saveConfig(db, settings, &types.Runner{}, &auth.JSONAuth{})
st := getStore(db)
err = st.Users.Save(user)
checkErr(err)
}
func startServer(cmd *cobra.Command, env *fhttp.Env) {
addr := mustGetString(cmd, "address")
port, err := cmd.Flags().GetInt("port")
checkErr(err)
cert := mustGetString(cmd, "cert")
key := mustGetString(cmd, "key")
var listener net.Listener
if cert != "" && key != "" {
cer, err := tls.LoadX509KeyPair(cert, key)
checkErr(err)
config := &tls.Config{Certificates: []tls.Certificate{cer}}
listener, err = tls.Listen("tcp", addr+":"+strconv.Itoa(port), config)
checkErr(err)
} else {
listener, err = net.Listen("tcp", addr+":"+strconv.Itoa(port))
checkErr(err)
}
log.Println("Listening on", listener.Addr().String())
if err := http.Serve(listener, fhttp.Handler(env)); err != nil {
log.Fatal(err)
}
}

View File

@ -1,7 +1,6 @@
package cmd
import (
storage "github.com/filebrowser/filebrowser/bolt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
)
@ -31,7 +30,7 @@ var usersLsCmd = &cobra.Command{
var findUsers = func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := storage.UsersStore{DB: db}
st := getStore(db)
username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id")
@ -41,11 +40,11 @@ var findUsers = func(cmd *cobra.Command, args []string) {
var user *types.User
if username != "" {
user, err = st.GetByUsername(username)
user, err = st.Users.GetByUsername(username)
} else if id != 0 {
user, err = st.Get(id)
user, err = st.Users.Get(id)
} else {
users, err = st.Gets()
users, err = st.Users.Gets()
}
checkErr(err)

View File

@ -1,7 +1,6 @@
package cmd
import (
storage "github.com/filebrowser/filebrowser/bolt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
)
@ -24,10 +23,9 @@ var usersNewCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
ust := storage.UsersStore{DB: db}
cst := storage.ConfigStore{DB: db}
st := getStore(db)
settings, err := cst.GetSettings()
settings, err := st.Config.GetSettings()
checkErr(err)
getUserDefaults(cmd, &settings.Defaults, false)
@ -39,14 +37,11 @@ var usersNewCmd = &cobra.Command{
Username: mustGetString(cmd, "username"),
Password: password,
LockPassword: mustGetBool(cmd, "lockPassword"),
Scope: settings.Defaults.Scope,
Locale: settings.Defaults.Locale,
ViewMode: settings.Defaults.ViewMode,
Perm: settings.Defaults.Perm,
Sorting: settings.Defaults.Sorting,
}
err = ust.Save(user)
user.ApplyDefaults(settings.Defaults)
err = st.Users.Save(user)
checkErr(err)
printUsers([]*types.User{user})
},

View File

@ -3,7 +3,6 @@ package cmd
import (
"fmt"
storage "github.com/filebrowser/filebrowser/bolt"
"github.com/spf13/cobra"
)
@ -21,7 +20,7 @@ var usersRmCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := storage.UsersStore{DB: db}
st := getStore(db)
username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id")
@ -29,9 +28,9 @@ var usersRmCmd = &cobra.Command{
var err error
if username != "" {
err = st.DeleteByUsername(username)
err = st.Users.DeleteByUsername(username)
} else {
err = st.Delete(id)
err = st.Users.Delete(id)
}
checkErr(err)

View File

@ -1,7 +1,6 @@
package cmd
import (
storage "github.com/filebrowser/filebrowser/bolt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
)
@ -24,7 +23,7 @@ options you want to change.`,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := storage.UsersStore{DB: db}
st := getStore(db)
id, _ := cmd.Flags().GetUint("id")
username := mustGetString(cmd, "username")
@ -34,9 +33,9 @@ options you want to change.`,
var err error
if id != 0 {
user, err = st.Get(id)
user, err = st.Users.Get(id)
} else {
user, err = st.GetByUsername(username)
user, err = st.Users.GetByUsername(username)
}
checkErr(err)
@ -67,7 +66,7 @@ options you want to change.`,
checkErr(err)
}
err = st.Update(user)
err = st.Users.Update(user)
checkErr(err)
printUsers([]*types.User{user})
},

View File

@ -7,6 +7,7 @@ import (
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/bolt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
)
@ -38,6 +39,27 @@ func getDB() *storm.DB {
return db
}
func getStore(db *storm.DB) *types.Store {
usersStore := &types.UsersVerify{
Store: bolt.UsersStore{
DB: db,
},
}
configSore := &types.ConfigVerify{
Store: bolt.ConfigStore{
DB: db,
Users: usersStore,
},
}
return &types.Store{
Users: usersStore,
Config: configSore,
Share: bolt.ShareStore{DB: db},
}
}
func generateRandomBytes(n int) []byte {
b := make([]byte, n)
_, err := rand.Read(b)

20
cmd/version.go Normal file
View File

@ -0,0 +1,20 @@
package cmd
import (
"fmt"
"github.com/filebrowser/filebrowser/types"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("File Browser Version " + types.Version)
},
}

View File

@ -53,13 +53,10 @@ func (e *Env) signupHandler(w http.ResponseWriter, r *http.Request) {
user := &types.User{
Username: info.Username,
Locale: e.Settings.Defaults.Locale,
Perm: e.Settings.Defaults.Perm,
ViewMode: e.Settings.Defaults.ViewMode,
Commands: e.Settings.Defaults.Commands,
Scope: e.Settings.Defaults.Scope,
}
user.ApplyDefaults(e.Settings.Defaults)
pwd, err := types.HashPwd(info.Password)
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)

View File

@ -15,14 +15,12 @@ import (
)
func (e *Env) getStaticData() map[string]interface{} {
baseURL := strings.TrimSuffix(e.Settings.BaseURL, "/")
staticURL := strings.TrimPrefix(baseURL+"/static", "/")
staticURL := strings.TrimPrefix(e.Settings.BaseURL+"/static", "/")
// TODO: baseurl must always not have the trailing slash
data := map[string]interface{}{
"Name": e.Settings.Branding.Name,
"DisableExternal": e.Settings.Branding.DisableExternal,
"BaseURL": baseURL,
"BaseURL": e.Settings.BaseURL,
"Version": types.Version,
"StaticURL": staticURL,
"Signup": e.Settings.Signup,

View File

@ -17,4 +17,5 @@ var (
ErrPathIsRel = errors.New("path is relative")
ErrNoPermission = errors.New("permission denied")
ErrInvalidAuthMethod = errors.New("invalid auth method")
ErrEmptyKey = errors.New("empty key")
)

View File

@ -10,19 +10,12 @@ import (
"github.com/mholt/caddy"
)
// DefaultEvents are the default events that can trigger commands.
// See Settings.Commands.
var DefaultEvents = []string{
"before_save",
"after_save",
"before_copy",
"after_copy",
"before_rename",
"after_rename",
"before_upload",
"after_upload",
"before_delete",
"after_delete",
var defaultEvents = []string{
"save",
"copy",
"rename",
"upload",
"delete",
}
// Runner runs certain commands.

View File

@ -3,22 +3,10 @@ package types
// Store is used to persist data.
type Store struct {
Users *UsersVerify
Config ConfigStore
Config *ConfigVerify
Share ShareStore
}
// ConfigStore is used to manage configurations relativey to a data storage.
type ConfigStore interface {
Get(name string, to interface{}) error
Save(name string, from interface{}) error
GetSettings() (*Settings, error)
SaveSettings(*Settings) error
SaveRunner(*Runner) error
GetRunner() (*Runner, error)
GetAuther(AuthMethod) (Auther, error)
SaveAuther(Auther) error
}
// ShareStore is the interface to manage share links.
type ShareStore interface {
Get(hash string) (*ShareLink, error)

84
types/storage_config.go Normal file
View File

@ -0,0 +1,84 @@
package types
import "strings"
// ConfigStore is used to manage configurations relativey to a data storage.
type ConfigStore interface {
GetSettings() (*Settings, error)
SaveSettings(*Settings) error
SaveRunner(*Runner) error
GetRunner() (*Runner, error)
GetAuther(AuthMethod) (Auther, error)
SaveAuther(Auther) error
}
// ConfigVerify wraps a ConfigStore and makes the verifications needed.
type ConfigVerify struct {
Store ConfigStore
}
// GetSettings wraps a ConfigStore.GetSettings
func (v ConfigVerify) GetSettings() (*Settings, error) {
return v.Store.GetSettings()
}
// SaveSettings wraps a ConfigStore.SaveSettings
func (v ConfigVerify) SaveSettings(s *Settings) error {
s.BaseURL = strings.TrimSuffix(s.BaseURL, "/")
if len(s.Key) == 0 {
return ErrEmptyKey
}
if s.Defaults.Locale == "" {
s.Defaults.Locale = "en"
}
if s.Defaults.Commands == nil {
s.Defaults.Commands = []string{}
}
if s.Defaults.ViewMode == "" {
s.Defaults.ViewMode = MosaicViewMode
}
if s.Rules == nil {
s.Rules = []Rule{}
}
return v.Store.SaveSettings(s)
}
// GetRunner wraps a ConfigStore.GetRunner
func (v ConfigVerify) GetRunner() (*Runner, error) {
return v.Store.GetRunner()
}
// SaveRunner wraps a ConfigStore.SaveRunner
func (v ConfigVerify) SaveRunner(r *Runner) error {
if r.Commands == nil {
r.Commands = map[string][]string{}
}
for _, event := range defaultEvents {
if _, ok := r.Commands["before_"+event]; !ok {
r.Commands["before_"+event] = []string{}
}
if _, ok := r.Commands["after_"+event]; !ok {
r.Commands["after_"+event] = []string{}
}
}
return v.Store.SaveRunner(r)
}
// GetAuther wraps a ConfigStore.GetAuther
func (v ConfigVerify) GetAuther(t AuthMethod) (Auther, error) {
return v.Store.GetAuther(t)
}
// SaveAuther wraps a ConfigStore.SaveAuther
func (v ConfigVerify) SaveAuther(a Auther) error {
return v.Store.SaveAuther(a)
}

View File

@ -99,10 +99,20 @@ func (u *User) clean(fields ...string) error {
}
// IsAllowed checks if an user is allowed to go to a certain path.
func (u User) IsAllowed(url string) bool {
func (u *User) IsAllowed(url string) bool {
return isAllowed(url, u.Rules)
}
// ApplyDefaults applies defaults to a user.
func (u *User) ApplyDefaults(defaults UserDefaults) {
u.Scope = defaults.Scope
u.Locale = defaults.Locale
u.ViewMode = defaults.ViewMode
u.Perm = defaults.Perm
u.Sorting = defaults.Sorting
u.Commands = defaults.Commands
}
// HashPwd hashes a password.
func HashPwd(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)