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,10 +27,7 @@ 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,
}
func initDatabase(cmd *cobra.Command, args []string) {
Run: func(cmd *cobra.Command, args []string) {
if _, err := os.Stat(databasePath); err == nil {
panic(errors.New(databasePath + " already exists"))
}
@ -44,27 +42,18 @@ func initDatabase(cmd *cobra.Command, args []string) {
Signup: mustGetBool(cmd, "signup"),
Defaults: defaults,
AuthMethod: authMethod,
}
runner := &types.Runner{
Commands: map[string][]string{},
}
for _, event := range types.DefaultEvents {
runner.Commands[event] = []string{}
Branding: types.Branding{
Name: mustGetString(cmd, "branding.name"),
DisableExternal: mustGetBool(cmd, "branding.disableExternal"),
Files: mustGetString(cmd, "branding.files"),
},
}
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)
saveConfig(db, settings, &types.Runner{}, auther)
fmt.Printf(`
Congratulations! You've set up your database to use with File Browser.
@ -72,4 +61,15 @@ 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)
},
}
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,6 +47,32 @@ 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) {
setupLogger(cmd)
if _, err := os.Stat(databasePath); os.IsNotExist(err) {
quickSetup(cmd)
}
var err error
db := getDB()
defer db.Close()
env := &fhttp.Env{
Store: getStore(db),
}
env.Settings, err = env.Store.Config.GetSettings()
checkErr(err)
env.Auther, err = env.Store.Config.GetAuther(env.Settings.AuthMethod)
checkErr(err)
env.Runner, err = env.Store.Config.GetRunner()
checkErr(err)
startServer(cmd, env)
},
}
func setupLogger(cmd *cobra.Command) {
switch l := mustGetString(cmd, "log"); l {
case "stdout":
log.SetOutput(os.Stdout)
@ -60,28 +88,59 @@ listening on loalhost on a random port. Use the flags to change it.`,
MaxBackups: 10,
})
}
}
var err error
db := getDB()
defer db.Close()
func quickSetup(cmd *cobra.Command) {
scope := mustGetString(cmd, "scope")
if scope == "" {
panic(errors.New("scope flag must be set for quick setup"))
}
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},
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,
},
},
}
env.Settings, err = env.Store.Config.GetSettings()
checkErr(err)
env.Auther, err = env.Store.Config.GetAuther(env.Settings.AuthMethod)
checkErr(err)
env.Runner, err = env.Store.Config.GetRunner()
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)
@ -106,21 +165,4 @@ listening on loalhost on a random port. Use the flags to change it.`,
if err := http.Serve(listener, fhttp.Handler(env)); err != nil {
log.Fatal(err)
}
},
}
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)
}
}

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)