feat: config/users import/export (#613)

Supports json and yaml.
pull/615/head
Henrique Dias 2019-01-08 08:57:24 +00:00 committed by GitHub
parent 46b221533a
commit 73b8d2ee7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 279 additions and 7 deletions

View File

@ -22,11 +22,11 @@ type jsonCred struct {
// JSONAuth is a json implementaion of an Auther.
type JSONAuth struct {
ReCaptcha *ReCaptcha
ReCaptcha *ReCaptcha `json:"recaptcha" yaml:"recaptcha"`
}
// Auth authenticates the user via a json in content body.
func (a *JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
var cred jsonCred
if r.Body == nil {

View File

@ -14,6 +14,6 @@ const MethodNoAuth settings.AuthMethod = "noauth"
type NoAuth struct{}
// Auth uses authenticates user 1.
func (a *NoAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
func (a NoAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
return sto.Get(root, 1)
}

View File

@ -14,11 +14,11 @@ const MethodProxyAuth settings.AuthMethod = "proxy"
// ProxyAuth is a proxy implementation of an auther.
type ProxyAuth struct {
Header string
Header string `json:"header"`
}
// Auth authenticates the user via an HTTP header.
func (a *ProxyAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
func (a ProxyAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users.User, error) {
username := r.Header.Get(a.Header)
user, err := sto.Get(root, username)
if err == errors.ErrNotExist {

30
cmd/config_export.go Normal file
View File

@ -0,0 +1,30 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
configCmd.AddCommand(configExportCmd)
}
var configExportCmd = &cobra.Command{
Use: "export <filename>",
Short: "Export the configuration to a file.",
Args: jsonYamlArg,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
settings, err := d.store.Settings.Get()
checkErr(err)
auther, err := d.store.Auth.Get(settings.AuthMethod)
checkErr(err)
data := &settingsFile{
Settings: settings,
Auther: auther,
}
err = marshal(args[0], data)
checkErr(err)
}, pythonConfig{}),
}

76
cmd/config_import.go Normal file
View File

@ -0,0 +1,76 @@
package cmd
import (
"encoding/json"
"errors"
"reflect"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
)
func init() {
configCmd.AddCommand(configImportCmd)
}
type settingsFile struct {
Settings *settings.Settings `json:"settings"`
Auther interface{} `json:"auther"`
}
var configImportCmd = &cobra.Command{
Use: "import <filename>",
Short: `Import a configuration file. This will replace all the existing
configuration. Can be used with or without unexisting databases.
If used with a nonexisting database, a key will be generated
automatically. Otherwise the key will be kept the same as in the
database.`,
Args: jsonYamlArg,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
var key []byte
if d.hadDB {
settings, err := d.store.Settings.Get()
checkErr(err)
key = settings.Key
} else {
key = generateRandomBytes(64)
}
file := settingsFile{}
err := unmarshal(args[0], &file)
checkErr(err)
file.Settings.Key = key
err = d.store.Settings.Save(file.Settings)
checkErr(err)
autherInterf := cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
var auther auth.Auther
switch file.Settings.AuthMethod {
case auth.MethodJSONAuth:
auther = getAuther(auth.JSONAuth{}, autherInterf).(*auth.JSONAuth)
case auth.MethodNoAuth:
auther = getAuther(auth.NoAuth{}, autherInterf).(*auth.NoAuth)
case auth.MethodProxyAuth:
auther = getAuther(auth.ProxyAuth{}, autherInterf).(*auth.ProxyAuth)
default:
checkErr(errors.New("invalid auth method"))
}
err = d.store.Auth.Save(auther)
checkErr(err)
printSettings(file.Settings, auther)
}, pythonConfig{allowNoDB: true}),
}
func getAuther(sample auth.Auther, data interface{}) interface{} {
authType := reflect.TypeOf(sample)
auther := reflect.New(authType).Interface()
bytes, err := json.Marshal(data)
checkErr(err)
err = json.Unmarshal(bytes, &auther)
checkErr(err)
return auther
}

22
cmd/users_export.go Normal file
View File

@ -0,0 +1,22 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
usersCmd.AddCommand(usersExportCmd)
}
var usersExportCmd = &cobra.Command{
Use: "export <filename>",
Short: "Export all users.",
Args: jsonYamlArg,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
list, err := d.store.Users.Gets("")
checkErr(err)
err = marshal(args[0], list)
checkErr(err)
}, pythonConfig{}),
}

65
cmd/users_import.go Normal file
View File

@ -0,0 +1,65 @@
package cmd
import (
"errors"
"os"
"strconv"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
usersCmd.AddCommand(usersImportCmd)
usersImportCmd.Flags().Bool("overwrite", false, "overwrite users with the same id/username combo")
}
var usersImportCmd = &cobra.Command{
Use: "import <filename>",
Short: "Import users from a file.",
Args: jsonYamlArg,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
fd, err := os.Open(args[0])
checkErr(err)
defer fd.Close()
list := []*users.User{}
err = unmarshal(args[0], &list)
checkErr(err)
for _, user := range list {
err = user.Clean("")
checkErr(err)
}
overwrite := mustGetBool(cmd, "overwrite")
for _, user := range list {
old, err := d.store.Users.Get("", user.ID)
// User exists in DB.
if err == nil {
if !overwrite {
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registred"))
}
// If the usernames mismatch, check if there is another one in the DB
// with the new username. If there is, print an error and cancel the
// operation
if user.Username != old.Username {
conflictuous, err := d.store.Users.Get("", user.Username)
if err == nil {
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
}
}
}
err = d.store.Users.Save(user)
checkErr(err)
}
}, pythonConfig{}),
}
func usernameConflictError(username string, original, new uint) error {
return errors.New("can't import user with ID " + strconv.Itoa(int(new)) + " and username \"" + username + "\" because the username is already registred with the user " + strconv.Itoa(int(original)))
}

View File

@ -2,8 +2,12 @@ package cmd
import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/storage"
@ -11,6 +15,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
)
func vaddP(f *pflag.FlagSet, k, p string, i interface{}, u string) {
@ -39,7 +44,8 @@ func vadd(f *pflag.FlagSet, k string, i interface{}, u string) {
func checkErr(err error) {
if err != nil {
panic(err)
fmt.Println(err)
os.Exit(1)
}
}
@ -108,3 +114,76 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
fn(cmd, args, data)
}
}
func marshal(filename string, data interface{}) error {
fd, err := os.Create(filename)
checkErr(err)
defer fd.Close()
switch ext := filepath.Ext(filename); ext {
case ".json":
encoder := json.NewEncoder(fd)
encoder.SetIndent("", " ")
return encoder.Encode(data)
case ".yml", ".yaml":
encoder := yaml.NewEncoder(fd)
return encoder.Encode(data)
default:
return errors.New("invalid format: " + ext)
}
}
func unmarshal(filename string, data interface{}) error {
fd, err := os.Open(filename)
checkErr(err)
defer fd.Close()
switch ext := filepath.Ext(filename); ext {
case ".json":
return json.NewDecoder(fd).Decode(data)
case ".yml", ".yaml":
return yaml.NewDecoder(fd).Decode(data)
default:
return errors.New("invalid format: " + ext)
}
}
func jsonYamlArg(cmd *cobra.Command, args []string) error {
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
return err
}
switch ext := filepath.Ext(args[0]); ext {
case ".json", ".yml", ".yaml":
return nil
default:
return errors.New("invalid format: " + ext)
}
}
func cleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range in {
result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
}
return result
}
func cleanUpInterfaceArray(in []interface{}) []interface{} {
result := make([]interface{}, len(in))
for i, v := range in {
result[i] = cleanUpMapValue(v)
}
return result
}
func cleanUpMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanUpInterfaceArray(v)
case map[interface{}]interface{}:
return cleanUpInterfaceMap(v)
default:
return v
}
}

View File

@ -31,7 +31,7 @@ type User struct {
Perm Permissions `json:"perm"`
Commands []string `json:"commands"`
Sorting files.Sorting `json:"sorting"`
Fs afero.Fs `json:"-"`
Fs afero.Fs `json:"-" yaml:"-"`
Rules []rules.Rule `json:"rules"`
}