feat: add global scope (#604)
License: MIT Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
parent
07f3ee38e5
commit
f55f205ced
|
@ -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)
|
||||
}
|
||||
|
|
10
auth/json.go
10
auth/json.go
|
@ -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.
|
||||
|
|
10
auth/none.go
10
auth/none.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -14,22 +14,16 @@ const MethodProxyAuth settings.AuthMethod = "proxy"
|
|||
|
||||
// ProxyAuth is a proxy implementation of an auther.
|
||||
type ProxyAuth struct {
|
||||
Header string
|
||||
storage *users.Storage
|
||||
Header string
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -15,7 +15,6 @@ func init() {
|
|||
configCmd.AddCommand(configInitCmd)
|
||||
rootCmd.AddCommand(configInitCmd)
|
||||
addConfigFlags(configInitCmd)
|
||||
configInitCmd.MarkFlagRequired("scope")
|
||||
}
|
||||
|
||||
var configInitCmd = &cobra.Command{
|
||||
|
|
20
cmd/root.go
20
cmd/root.go
|
@ -2,12 +2,12 @@ package cmd
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/asdine/storm"
|
||||
|
@ -32,7 +32,7 @@ func init() {
|
|||
rootCmd.Flags().IntP("port", "p", 8080, "port to listen on")
|
||||
rootCmd.Flags().StringP("cert", "c", "", "tls certificate")
|
||||
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{
|
||||
|
@ -76,6 +76,15 @@ func serveAndListen(cmd *cobra.Command, args []string) {
|
|||
address := mustGetString(cmd, "address")
|
||||
cert := mustGetString(cmd, "cert")
|
||||
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)
|
||||
checkErr(err)
|
||||
|
@ -100,11 +109,6 @@ func serveAndListen(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
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)
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
@ -115,7 +119,7 @@ func quickSetup(cmd *cobra.Command) {
|
|||
Signup: false,
|
||||
AuthMethod: auth.MethodJSONAuth,
|
||||
Defaults: settings.UserDefaults{
|
||||
Scope: scope,
|
||||
Scope: ".",
|
||||
Locale: "en",
|
||||
Perm: users.Permissions{
|
||||
Admin: false,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -77,7 +77,7 @@ func addUserFlags(cmd *cobra.Command) {
|
|||
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("scope", ".", "scope for users")
|
||||
cmd.Flags().String("locale", "en", "locale for users")
|
||||
cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ type AuthMethod string
|
|||
type Settings struct {
|
||||
Key []byte `json:"key"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Scope string `json:"scope"`
|
||||
Signup bool `json:"signup"`
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
AuthMethod AuthMethod `json:"authMethod"`
|
||||
|
|
|
@ -3,7 +3,6 @@ package importer
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/asdine/storm"
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
|
@ -52,7 +51,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 +80,7 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
|
|||
user.Rules = append(user.Rules, *rule)
|
||||
}
|
||||
|
||||
user.Scope, err = filepath.Abs(user.Scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = user.Clean()
|
||||
err := user.Clean("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue