feat: add global scope (#604)

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

Former-commit-id: a44ebfc7a5687b5f51f0ff791335f66ab9f2e8e0 [formerly 9a044fadd8f2ebbb7dbb773273799c26a797f513] [formerly 7f374d016eaf756cfce215dd16a2274bbabe1915 [formerly f55f205ced]]
Former-commit-id: 31015ddc5f4fc28c895743f6fe9dcf5488bb4b01 [formerly e439027304a1e49667fafde011e07d043ef0d2ee]
Former-commit-id: 0394c60358673b56991364260b1cbe41fa457593
This commit is contained in:
Henrique Dias 2019-01-06 13:01:42 +00:00 committed by GitHub
parent f2d952bf0e
commit f1a89f5ec4
18 changed files with 60 additions and 81 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

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

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

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

View File

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

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

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

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

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

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

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

View File

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

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