feat: filebrowser with encfs

This commit is contained in:
2024-09-03 00:26:36 +08:00
parent 14024c56a3
commit b0b9f70129
330 changed files with 44745 additions and 87 deletions

17
users/password.go Normal file
View File

@@ -0,0 +1,17 @@
package users
import (
"golang.org/x/crypto/bcrypt"
)
// HashPwd hashes a password.
func HashPwd(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// CheckPwd checks if a password is correct.
func CheckPwd(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

13
users/permissions.go Normal file
View File

@@ -0,0 +1,13 @@
package users
// Permissions describe a user's permissions.
type Permissions struct {
Admin bool `json:"admin"`
Execute bool `json:"execute"`
Create bool `json:"create"`
Rename bool `json:"rename"`
Modify bool `json:"modify"`
Delete bool `json:"delete"`
Share bool `json:"share"`
Download bool `json:"download"`
}

133
users/storage.go Normal file
View File

@@ -0,0 +1,133 @@
package users
import (
"sync"
"time"
"github.com/filebrowser/filebrowser/v2/errors"
)
// StorageBackend is the interface to implement for a users storage.
type StorageBackend interface {
GetBy(interface{}) (*User, error)
Gets() ([]*User, error)
Save(u *User) error
Update(u *User, fields ...string) error
DeleteByID(uint) error
DeleteByUsername(string) error
}
type Store interface {
Get(baseScope string, id interface{}) (user *User, err error)
Gets(baseScope string) ([]*User, error)
Update(user *User, fields ...string) error
Save(user *User) error
Delete(id interface{}) error
LastUpdate(id uint) int64
}
// Storage is a users storage.
type Storage struct {
back StorageBackend
updated map[uint]int64
mux sync.RWMutex
}
// NewStorage creates a users storage from a backend.
func NewStorage(back StorageBackend) *Storage {
return &Storage{
back: back,
updated: map[uint]int64{},
}
}
// 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(baseScope string, id interface{}) (user *User, err error) {
user, err = s.back.GetBy(id)
if err != nil {
return
}
if err := user.Clean(baseScope); err != nil {
return nil, err
}
return
}
// Gets gets a list of all users.
func (s *Storage) Gets(baseScope string) ([]*User, error) {
users, err := s.back.Gets()
if err != nil {
return nil, err
}
for _, user := range users {
if err := user.Clean(baseScope); err != nil { //nolint:govet
return nil, err
}
}
return users, err
}
// Update updates a user in the database.
func (s *Storage) Update(user *User, fields ...string) error {
err := user.Clean("", fields...)
if err != nil {
return err
}
err = s.back.Update(user, fields...)
if err != nil {
return err
}
s.mux.Lock()
s.updated[user.ID] = time.Now().Unix()
s.mux.Unlock()
return nil
}
// Save saves the user in a storage.
func (s *Storage) Save(user *User) error {
if err := user.Clean(""); err != nil {
return err
}
return s.back.Save(user)
}
// Delete allows you to delete 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) Delete(id interface{}) error {
switch id := id.(type) {
case string:
user, err := s.back.GetBy(id)
if err != nil {
return err
}
if user.ID == 1 {
return errors.ErrRootUserDeletion
}
return s.back.DeleteByUsername(id)
case uint:
if id == 1 {
return errors.ErrRootUserDeletion
}
return s.back.DeleteByID(id)
default:
return errors.ErrInvalidDataType
}
}
// LastUpdate gets the timestamp for the last update of an user.
func (s *Storage) LastUpdate(id uint) int64 {
s.mux.RLock()
defer s.mux.RUnlock()
if val, ok := s.updated[id]; ok {
return val
}
return 0
}

4
users/storage_test.go Normal file
View File

@@ -0,0 +1,4 @@
package users
// Interface is implemented by storage
var _ Store = &Storage{}

127
users/users.go Normal file
View File

@@ -0,0 +1,127 @@
package users
import (
"path/filepath"
"regexp"
"github.com/filebrowser/filebrowser/v2/encfs"
"github.com/spf13/afero"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/rules"
)
// ViewMode describes a view mode.
type ViewMode string
const (
ListViewMode ViewMode = "list"
MosaicViewMode ViewMode = "mosaic"
)
// User describes a user.
type User struct {
ID uint `storm:"id,increment" json:"id"`
Username string `storm:"unique" json:"username"`
Password string `json:"password"`
Scope string `json:"scope"`
Locale string `json:"locale"`
LockPassword bool `json:"lockPassword"`
ViewMode ViewMode `json:"viewMode"`
SingleClick bool `json:"singleClick"`
Perm Permissions `json:"perm"`
Commands []string `json:"commands"`
Sorting files.Sorting `json:"sorting"`
Fs afero.Fs `json:"-" yaml:"-"`
Rules []rules.Rule `json:"rules"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
}
// GetRules implements rules.Provider.
func (u *User) GetRules() []rules.Rule {
return u.Rules
}
var checkableFields = []string{
"Username",
"Password",
"Scope",
"ViewMode",
"Commands",
"Sorting",
"Rules",
}
// Clean cleans up a user and verifies if all its fields
// are alright to be saved.
//
//nolint:gocyclo
func (u *User) Clean(baseScope string, fields ...string) error {
if len(fields) == 0 {
fields = checkableFields
}
for _, field := range fields {
switch field {
case "Username":
if u.Username == "" {
return errors.ErrEmptyUsername
}
case "Password":
if u.Password == "" {
return errors.ErrEmptyPassword
}
case "ViewMode":
if u.ViewMode == "" {
u.ViewMode = ListViewMode
}
case "Commands":
if u.Commands == nil {
u.Commands = []string{}
}
case "Sorting":
if u.Sorting.By == "" {
u.Sorting.By = "name"
}
case "Rules":
if u.Rules == nil {
u.Rules = []rules.Rule{}
}
}
}
if u.Fs == nil {
scope := u.Scope
scope = filepath.Join(baseScope, filepath.Join("/", scope)) //nolint:gocritic
// u.Fs = afero.NewBasePathFs(afero.NewOsFs(), scope)
encryptionMasterKey, err := encfs.GetCachedEncryptionMasterKey()
if err != nil {
return err
}
u.Fs = afero.NewBasePathFs(encfs.NewEncFs(encryptionMasterKey), scope)
}
return nil
}
// FullPath gets the full path for a user's relative path.
func (u *User) FullPath(path string) string {
return afero.FullBaseFsPath(u.Fs.(*afero.BasePathFs), path)
}
// CanExecute checks if an user can execute a specific command.
func (u *User) CanExecute(command string) bool {
if !u.Perm.Execute {
return false
}
for _, cmd := range u.Commands {
if regexp.MustCompile(cmd).MatchString(command) {
return true
}
}
return false
}