feat: add global scope (#604)

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
Henrique Dias 2019-01-06 13:01:42 +00:00 committed by GitHub
parent 07f3ee38e5
commit f55f205ced
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 60 additions and 81 deletions

View File

@ -3,13 +3,12 @@ package auth
import ( import (
"net/http" "net/http"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
) )
// Auther is the authentication interface. // Auther is the authentication interface.
type Auther interface { type Auther interface {
// Auth is called to authenticate a request. // Auth is called to authenticate a request.
Auth(*http.Request) (*users.User, error) Auth(*http.Request, *users.Storage, *settings.Settings) (*users.User, error)
// SetStorage attaches the Storage instance.
SetStorage(*users.Storage)
} }

View File

@ -23,11 +23,10 @@ type jsonCred struct {
// JSONAuth is a json implementaion of an Auther. // JSONAuth is a json implementaion of an Auther.
type JSONAuth struct { type JSONAuth struct {
ReCaptcha *ReCaptcha ReCaptcha *ReCaptcha
storage *users.Storage
} }
// Auth authenticates the user via a json in content body. // 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 var cred jsonCred
if r.Body == nil { 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) { if err != nil || !users.CheckPwd(cred.Password, u.Password) {
return nil, os.ErrPermission return nil, os.ErrPermission
} }
@ -60,11 +59,6 @@ func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) {
return u, nil 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" const reCaptchaAPI = "/recaptcha/api/siteverify"
// ReCaptcha identifies a recaptcha conenction. // ReCaptcha identifies a recaptcha conenction.

View File

@ -12,15 +12,9 @@ const MethodNoAuth settings.AuthMethod = "noauth"
// NoAuth is no auth implementation of auther. // NoAuth is no auth implementation of auther.
type NoAuth struct { type NoAuth struct {
storage *users.Storage
} }
// Auth uses authenticates user 1. // Auth uses authenticates user 1.
func (a *NoAuth) Auth(r *http.Request) (*users.User, error) { func (a *NoAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) {
return a.storage.Get(1) return sto.Get(set.Scope, 1)
}
// SetStorage attaches the storage to the auther.
func (a *NoAuth) SetStorage(s *users.Storage) {
a.storage = s
} }

View File

@ -14,22 +14,16 @@ const MethodProxyAuth settings.AuthMethod = "proxy"
// ProxyAuth is a proxy implementation of an auther. // ProxyAuth is a proxy implementation of an auther.
type ProxyAuth struct { type ProxyAuth struct {
Header string Header string
storage *users.Storage
} }
// Auth authenticates the user via an HTTP header. // 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) username := r.Header.Get(a.Header)
user, err := a.storage.Get(username) user, err := sto.Get(set.Scope, username)
if err == errors.ErrNotExist { if err == errors.ErrNotExist {
return nil, os.ErrPermission return nil, os.ErrPermission
} }
return user, err 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} 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) { func (s *Storage) Get(t settings.AuthMethod) (Auther, error) {
auther, err := s.back.Get(t) return s.back.Get(t)
if err != nil {
return nil, err
}
auther.SetStorage(s.users)
return auther, nil
} }
// Save wraps a StorageBackend.Save. // Save wraps a StorageBackend.Save.

View File

@ -15,7 +15,6 @@ func init() {
configCmd.AddCommand(configInitCmd) configCmd.AddCommand(configInitCmd)
rootCmd.AddCommand(configInitCmd) rootCmd.AddCommand(configInitCmd)
addConfigFlags(configInitCmd) addConfigFlags(configInitCmd)
configInitCmd.MarkFlagRequired("scope")
} }
var configInitCmd = &cobra.Command{ var configInitCmd = &cobra.Command{

View File

@ -2,12 +2,12 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strconv" "strconv"
"github.com/asdine/storm" "github.com/asdine/storm"
@ -32,7 +32,7 @@ func init() {
rootCmd.Flags().IntP("port", "p", 8080, "port to listen on") rootCmd.Flags().IntP("port", "p", 8080, "port to listen on")
rootCmd.Flags().StringP("cert", "c", "", "tls certificate") rootCmd.Flags().StringP("cert", "c", "", "tls certificate")
rootCmd.Flags().StringP("key", "k", "", "tls key") rootCmd.Flags().StringP("key", "k", "", "tls key")
rootCmd.Flags().StringP("scope", "s", "", "root scope to which user's scope are relative too") rootCmd.Flags().StringP("scope", "s", ".", "scope to prepend to a user's scope when it is relative")
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -76,6 +76,15 @@ func serveAndListen(cmd *cobra.Command, args []string) {
address := mustGetString(cmd, "address") address := mustGetString(cmd, "address")
cert := mustGetString(cmd, "cert") cert := mustGetString(cmd, "cert")
key := mustGetString(cmd, "key") key := mustGetString(cmd, "key")
scope := mustGetString(cmd, "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) handler, err := fbhttp.NewHandler(st)
checkErr(err) checkErr(err)
@ -100,11 +109,6 @@ func serveAndListen(cmd *cobra.Command, args []string) {
} }
func quickSetup(cmd *cobra.Command) { func quickSetup(cmd *cobra.Command) {
scope := mustGetString(cmd, "scope")
if scope == "" {
panic(errors.New("scope flag must be set for quick setup"))
}
db, err := storm.Open(databasePath) db, err := storm.Open(databasePath)
checkErr(err) checkErr(err)
defer db.Close() defer db.Close()
@ -115,7 +119,7 @@ func quickSetup(cmd *cobra.Command) {
Signup: false, Signup: false,
AuthMethod: auth.MethodJSONAuth, AuthMethod: auth.MethodJSONAuth,
Defaults: settings.UserDefaults{ Defaults: settings.UserDefaults{
Scope: scope, Scope: ".",
Locale: "en", Locale: "en",
Perm: users.Permissions{ Perm: users.Permissions{
Admin: false, Admin: false,

View File

@ -39,7 +39,7 @@ func runRules(cmd *cobra.Command, users func(*users.User, *storage.Storage), glo
id := getUserIdentifier(cmd) id := getUserIdentifier(cmd)
if id != nil { if id != nil {
user, err := st.Users.Get(id) user, err := st.Users.Get("", id)
checkErr(err) checkErr(err)
if users != nil { if users != nil {

View File

@ -77,7 +77,7 @@ func addUserFlags(cmd *cobra.Command) {
cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order") cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order")
cmd.Flags().Bool("lockPassword", false, "lock password") cmd.Flags().Bool("lockPassword", false, "lock password")
cmd.Flags().StringSlice("commands", nil, "a list of the commands a user can execute") 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("locale", "en", "locale for users")
cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users") cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users")
} }

View File

@ -32,19 +32,21 @@ var findUsers = func(cmd *cobra.Command, args []string) {
defer db.Close() defer db.Close()
st := getStorage(db) st := getStorage(db)
settings, err := st.Settings.Get()
checkErr(err)
username, _ := cmd.Flags().GetString("username") username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id") id, _ := cmd.Flags().GetUint("id")
var err error
var list []*users.User var list []*users.User
var user *users.User var user *users.User
if username != "" { if username != "" {
user, err = st.Users.Get(username) user, err = st.Users.Get(settings.Scope, username)
} else if id != 0 { } else if id != 0 {
user, err = st.Users.Get(id) user, err = st.Users.Get(settings.Scope, id)
} else { } else {
list, err = st.Users.Gets() list, err = st.Users.Gets(settings.Scope)
} }
checkErr(err) checkErr(err)

View File

@ -26,17 +26,19 @@ options you want to change.`,
defer db.Close() defer db.Close()
st := getStorage(db) st := getStorage(db)
set, err := st.Settings.Get()
checkErr(err)
id, _ := cmd.Flags().GetUint("id") id, _ := cmd.Flags().GetUint("id")
username := mustGetString(cmd, "username") username := mustGetString(cmd, "username")
password := mustGetString(cmd, "password") password := mustGetString(cmd, "password")
var user *users.User var user *users.User
var err error
if id != 0 { if id != 0 {
user, err = st.Users.Get(id) user, err = st.Users.Get(set.Scope, id)
} else { } else {
user, err = st.Users.Get(username) user, err = st.Users.Get(set.Scope, username)
} }
checkErr(err) checkErr(err)

View File

@ -67,7 +67,7 @@ func withUser(fn handleFunc) handleFunc {
w.Header().Add("X-Renew-Token", "true") 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 { if err != nil {
return http.StatusInternalServerError, err 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 return http.StatusInternalServerError, err
} }
user, err := auther.Auth(r) user, err := auther.Auth(r, d.store.Users, d.Settings)
if err == os.ErrPermission { if err == os.ErrPermission {
return http.StatusForbidden, nil return http.StatusForbidden, nil
} else if err != nil { } else if err != nil {

View File

@ -13,7 +13,7 @@ var withHashFile = func(fn handleFunc) handleFunc {
return errToStatus(err), err 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 { if err != nil {
return errToStatus(err), err 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) { 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 { if err != nil {
return http.StatusInternalServerError, err 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) { 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 { if err == errors.ErrNotExist {
return http.StatusNotFound, err 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) req.Data.Password, err = users.HashPwd(req.Data.Password)
} else { } else {
var suser *users.User 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 req.Data.Password = suser.Password
} }

View File

@ -9,6 +9,7 @@ type AuthMethod string
type Settings struct { type Settings struct {
Key []byte `json:"key"` Key []byte `json:"key"`
BaseURL string `json:"baseURL"` BaseURL string `json:"baseURL"`
Scope string `json:"scope"`
Signup bool `json:"signup"` Signup bool `json:"signup"`
Defaults UserDefaults `json:"defaults"` Defaults UserDefaults `json:"defaults"`
AuthMethod AuthMethod `json:"authMethod"` AuthMethod AuthMethod `json:"authMethod"`

View File

@ -3,7 +3,6 @@ package importer
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
@ -52,7 +51,6 @@ func readOldUsers(db *storm.DB) ([]*oldUser, error) {
} }
func convertUsersToNew(old []*oldUser) ([]*users.User, error) { func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
var err error
list := []*users.User{} list := []*users.User{}
for _, oldUser := range old { for _, oldUser := range old {
@ -82,12 +80,7 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
user.Rules = append(user.Rules, *rule) user.Rules = append(user.Rules, *rule)
} }
user.Scope, err = filepath.Abs(user.Scope) err := user.Clean("")
if err != nil {
return nil, err
}
err = user.Clean()
if err != nil { if err != nil {
return nil, err 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 // 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 // id must be a string for username lookup or a uint for id lookup. If id
// is neither, a ErrInvalidDataType will be returned. // 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 ( var (
user *User user *User
err error err error
@ -55,19 +55,19 @@ func (s *Storage) Get(id interface{}) (*User, error) {
return nil, err return nil, err
} }
user.Clean() user.Clean(baseScope)
return user, err return user, err
} }
// Gets gets a list of all users. // 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() users, err := s.back.Gets()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, user := range users { for _, user := range users {
user.Clean() user.Clean(baseScope)
} }
return users, err return users, err
@ -75,7 +75,7 @@ func (s *Storage) Gets() ([]*User, error) {
// Update updates a user in the database. // Update updates a user in the database.
func (s *Storage) Update(user *User, fields ...string) error { func (s *Storage) Update(user *User, fields ...string) error {
err := user.Clean(fields...) err := user.Clean("", fields...)
if err != nil { if err != nil {
return err return err
} }
@ -93,7 +93,7 @@ func (s *Storage) Update(user *User, fields ...string) error {
// Save saves the user in a storage. // Save saves the user in a storage.
func (s *Storage) Save(user *User) error { func (s *Storage) Save(user *User) error {
if err := user.Clean(); err != nil { if err := user.Clean(""); err != nil {
return err return err
} }

View File

@ -1,10 +1,11 @@
package users package users
import ( import (
"github.com/filebrowser/filebrowser/v2/errors"
"path/filepath" "path/filepath"
"regexp" "regexp"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/files" "github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/rules"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -51,7 +52,7 @@ var checkableFields = []string{
// Clean cleans up a user and verifies if all its fields // Clean cleans up a user and verifies if all its fields
// are alright to be saved. // 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 { if len(fields) == 0 {
fields = checkableFields fields = checkableFields
} }
@ -66,10 +67,6 @@ func (u *User) Clean(fields ...string) error {
if u.Password == "" { if u.Password == "" {
return errors.ErrEmptyPassword return errors.ErrEmptyPassword
} }
case "Scope":
if !filepath.IsAbs(u.Scope) {
return errors.ErrScopeIsRelative
}
case "ViewMode": case "ViewMode":
if u.ViewMode == "" { if u.ViewMode == "" {
u.ViewMode = ListViewMode u.ViewMode = ListViewMode
@ -90,7 +87,13 @@ func (u *User) Clean(fields ...string) error {
} }
if u.Fs == nil { 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 return nil