feat: add global scope

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
Henrique Dias 2019-01-06 08:58:31 +00:00
parent c830b80269
commit 2156b3e68b
21 changed files with 122 additions and 121 deletions

View File

@ -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)
}

View File

@ -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.

View File

@ -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)
}

View File

@ -19,17 +19,12 @@ type ProxyAuth struct {
}
// 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
}

View File

@ -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.

View File

@ -30,10 +30,11 @@ var configCmd = &cobra.Command{
}
func addConfigFlags(cmd *cobra.Command) {
addUserFlags(cmd)
addUserFlags(cmd, "defaults.")
cmd.Flags().StringP("baseURL", "b", "/", "base url of this installation")
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("scope", "", "scope to prepend to a user's scope when it is relative (default is current working directory)")
cmd.Flags().StringP("address", "a", "127.0.0.1", "default address to listen to")
cmd.Flags().StringP("log", "l", "stderr", "log output")
@ -102,6 +103,7 @@ func printSettings(s *settings.Settings, auther auth.Auther) {
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.Fprintf(w, "Scope:\t%s\t\n", s.Scope)
fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tAddress:\t%s\n", s.Server.Address)
fmt.Fprintf(w, "\tPort:\t%d\n", s.Server.Port)

View File

@ -15,7 +15,6 @@ func init() {
configCmd.AddCommand(configInitCmd)
rootCmd.AddCommand(configInitCmd)
addConfigFlags(configInitCmd)
configInitCmd.MarkFlagRequired("scope")
}
var configInitCmd = &cobra.Command{
@ -33,9 +32,16 @@ override the options.`,
}
defaults := settings.UserDefaults{}
getUserDefaults(cmd, &defaults, true)
getUserDefaults(cmd, &defaults, "defaults.", true)
authMethod, auther := getAuthentication(cmd)
var err error
scope := mustGetString(cmd, "scope")
if scope == "" {
scope, err = os.Getwd()
checkErr(err)
}
db, err := storm.Open(databasePath)
checkErr(err)
defer db.Close()
@ -45,6 +51,7 @@ override the options.`,
BaseURL: mustGetString(cmd, "baseURL"),
Log: mustGetString(cmd, "log"),
Signup: mustGetBool(cmd, "signup"),
Scope: scope,
Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "),
AuthMethod: authMethod,
Defaults: defaults,

View File

@ -57,7 +57,7 @@ you want to change.`,
}
})
getUserDefaults(cmd, &s.Defaults, false)
getUserDefaults(cmd, &s.Defaults, "defaults.", false)
var auther auth.Auther
if hasAuth {

View File

@ -3,7 +3,6 @@ package cmd
import (
"crypto/rand"
"crypto/tls"
"errors"
"io/ioutil"
"log"
"net"
@ -35,7 +34,7 @@ func init() {
rootCmd.Flags().IntP("port", "p", 0, "port to listen on (default comes from database)")
rootCmd.Flags().StringP("cert", "c", "", "tls certificate (default comes from database)")
rootCmd.Flags().StringP("key", "k", "", "tls key (default comes from database)")
rootCmd.Flags().StringP("scope", "s", "", "scope for users")
rootCmd.Flags().StringP("scope", "s", "", "scope to prepend to a user's scope when it is relative (default comes from database)")
}
var rootCmd = &cobra.Command{
@ -103,9 +102,11 @@ func serverVisitAndReplace(cmd *cobra.Command, s *settings.Settings) {
}
func quickSetup(cmd *cobra.Command) {
var err error
scope := mustGetString(cmd, "scope")
if scope == "" {
panic(errors.New("scope flag must be set for quick setup"))
scope, err = os.Getwd()
checkErr(err)
}
db, err := storm.Open(databasePath)
@ -115,6 +116,7 @@ func quickSetup(cmd *cobra.Command) {
set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: "",
Scope: scope,
Log: "stderr",
Signup: false,
AuthMethod: auth.MethodJSONAuth,
@ -125,7 +127,7 @@ func quickSetup(cmd *cobra.Command) {
TLSKey: mustGetString(cmd, "key"),
},
Defaults: settings.UserDefaults{
Scope: scope,
Scope: ".",
Locale: "en",
Perm: users.Permissions{
Admin: false,
@ -169,6 +171,13 @@ func startServer(cmd *cobra.Command, st *storage.Storage) {
settings, err := st.Settings.Get()
checkErr(err)
scope := mustGetString(cmd, "scope")
if scope != "" {
settings.Scope = scope
err = st.Settings.Save(settings)
checkErr(err)
}
serverVisitAndReplace(cmd, settings)
setupLogger(settings)

View File

@ -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 {

View File

@ -64,22 +64,22 @@ func usernameOrIDRequired(cmd *cobra.Command, args []string) error {
return nil
}
func addUserFlags(cmd *cobra.Command) {
cmd.Flags().Bool("perm.admin", false, "admin perm for users")
cmd.Flags().Bool("perm.execute", true, "execute perm for users")
cmd.Flags().Bool("perm.create", true, "create perm for users")
cmd.Flags().Bool("perm.rename", true, "rename perm for users")
cmd.Flags().Bool("perm.modify", true, "modify perm for users")
cmd.Flags().Bool("perm.delete", true, "delete perm for users")
cmd.Flags().Bool("perm.share", true, "share perm for users")
cmd.Flags().Bool("perm.download", true, "download perm for users")
cmd.Flags().String("sorting.by", "name", "sorting mode (name, size or modified)")
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("locale", "en", "locale for users")
cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users")
func addUserFlags(cmd *cobra.Command, prepend string) {
cmd.Flags().Bool(prepend+"perm.admin", false, "admin perm for users")
cmd.Flags().Bool(prepend+"perm.execute", true, "execute perm for users")
cmd.Flags().Bool(prepend+"perm.create", true, "create perm for users")
cmd.Flags().Bool(prepend+"perm.rename", true, "rename perm for users")
cmd.Flags().Bool(prepend+"perm.modify", true, "modify perm for users")
cmd.Flags().Bool(prepend+"perm.delete", true, "delete perm for users")
cmd.Flags().Bool(prepend+"perm.share", true, "share perm for users")
cmd.Flags().Bool(prepend+"perm.download", true, "download perm for users")
cmd.Flags().String(prepend+"sorting.by", "name", "sorting mode (name, size or modified)")
cmd.Flags().Bool(prepend+"sorting.asc", false, "sorting by ascending order")
cmd.Flags().Bool(prepend+"lockPassword", false, "lock password")
cmd.Flags().StringSlice(prepend+"commands", nil, "a list of the commands a user can execute")
cmd.Flags().String(prepend+"scope", "", "scope for users")
cmd.Flags().String(prepend+"locale", "en", "locale for users")
cmd.Flags().String(prepend+"viewMode", string(users.ListViewMode), "view mode for users")
}
func getViewMode(cmd *cobra.Command) users.ViewMode {
@ -90,39 +90,39 @@ func getViewMode(cmd *cobra.Command) users.ViewMode {
return viewMode
}
func getUserDefaults(cmd *cobra.Command, defaults *settings.UserDefaults, all bool) {
func getUserDefaults(cmd *cobra.Command, defaults *settings.UserDefaults, prepend string, all bool) {
visit := func(flag *pflag.Flag) {
switch flag.Name {
case "scope":
defaults.Scope = mustGetString(cmd, "scope")
case "locale":
defaults.Locale = mustGetString(cmd, "locale")
case "viewMode":
case prepend+"scope":
defaults.Scope = mustGetString(cmd, flag.Name)
case prepend+"locale":
defaults.Locale = mustGetString(cmd, flag.Name)
case prepend+"viewMode":
defaults.ViewMode = getViewMode(cmd)
case "perm.admin":
defaults.Perm.Admin = mustGetBool(cmd, "perm.admin")
case "perm.execute":
defaults.Perm.Execute = mustGetBool(cmd, "perm.execute")
case "perm.create":
defaults.Perm.Create = mustGetBool(cmd, "perm.create")
case "perm.rename":
defaults.Perm.Rename = mustGetBool(cmd, "perm.rename")
case "perm.modify":
defaults.Perm.Modify = mustGetBool(cmd, "perm.modify")
case "perm.delete":
defaults.Perm.Delete = mustGetBool(cmd, "perm.delete")
case "perm.share":
defaults.Perm.Share = mustGetBool(cmd, "perm.share")
case "perm.download":
defaults.Perm.Download = mustGetBool(cmd, "perm.download")
case "commands":
commands, err := cmd.Flags().GetStringSlice("commands")
case prepend+"perm.admin":
defaults.Perm.Admin = mustGetBool(cmd, flag.Name)
case prepend+"perm.execute":
defaults.Perm.Execute = mustGetBool(cmd, flag.Name)
case prepend+"perm.create":
defaults.Perm.Create = mustGetBool(cmd, flag.Name)
case prepend+"perm.rename":
defaults.Perm.Rename = mustGetBool(cmd, flag.Name)
case prepend+"perm.modify":
defaults.Perm.Modify = mustGetBool(cmd, flag.Name)
case prepend+"perm.delete":
defaults.Perm.Delete = mustGetBool(cmd, flag.Name)
case prepend+"perm.share":
defaults.Perm.Share = mustGetBool(cmd, flag.Name)
case prepend+"perm.download":
defaults.Perm.Download = mustGetBool(cmd, flag.Name)
case prepend+"commands":
commands, err := cmd.Flags().GetStringSlice(flag.Name)
checkErr(err)
defaults.Commands = commands
case "sorting.by":
defaults.Sorting.By = mustGetString(cmd, "sorting.by")
case "sorting.asc":
defaults.Sorting.Asc = mustGetBool(cmd, "sorting.asc")
case prepend+"sorting.by":
defaults.Sorting.By = mustGetString(cmd, flag.Name)
case prepend+"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()
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)

View File

@ -8,7 +8,7 @@ import (
func init() {
usersCmd.AddCommand(usersNewCmd)
addUserFlags(usersNewCmd)
addUserFlags(usersNewCmd, "")
usersNewCmd.Flags().StringP("username", "u", "", "new users's username")
usersNewCmd.Flags().StringP("password", "p", "", "new user's password")
usersNewCmd.MarkFlagRequired("username")
@ -27,7 +27,7 @@ var usersNewCmd = &cobra.Command{
s, err := st.Settings.Get()
checkErr(err)
getUserDefaults(cmd, &s.Defaults, false)
getUserDefaults(cmd, &s.Defaults, "", false)
password, _ := cmd.Flags().GetString("password")
password, err = users.HashPwd(password)

View File

@ -12,7 +12,7 @@ func init() {
usersUpdateCmd.Flags().UintP("id", "i", 0, "id of the user")
usersUpdateCmd.Flags().StringP("username", "u", "", "user to change or new username if flag 'id' is set")
usersUpdateCmd.Flags().StringP("password", "p", "", "new password")
addUserFlags(usersUpdateCmd)
addUserFlags(usersUpdateCmd, "")
}
var usersUpdateCmd = &cobra.Command{
@ -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)
@ -49,7 +51,7 @@ options you want to change.`,
Sorting: user.Sorting,
Commands: user.Commands,
}
getUserDefaults(cmd, &defaults, false)
getUserDefaults(cmd, &defaults, "", false)
user.Scope = defaults.Scope
user.Locale = defaults.Locale
user.ViewMode = defaults.ViewMode

View File

@ -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 {

View File

@ -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
}

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) {
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
}

View File

@ -10,6 +10,7 @@ type Settings struct {
Key []byte `json:"key"`
BaseURL string `json:"baseURL"`
Log string `json:"log"`
Scope string `json:"scope"`
Server Server `json:"server"`
Signup bool `json:"signup"`
Defaults UserDefaults `json:"defaults"`

View File

@ -3,7 +3,7 @@ package importer
import (
"encoding/json"
"fmt"
"path/filepath"
"os"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/rules"
@ -52,7 +52,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 +81,12 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
user.Rules = append(user.Rules, *rule)
}
user.Scope, err = filepath.Abs(user.Scope)
baseScope, err := os.Getwd()
if err != nil {
return nil, err
}
err = user.Clean()
err = user.Clean(baseScope)
if err != nil {
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
// 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
}

View File

@ -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