feat: filebrowser with encfs
This commit is contained in:
17
users/password.go
Normal file
17
users/password.go
Normal 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
13
users/permissions.go
Normal 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
133
users/storage.go
Normal 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
4
users/storage_test.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package users
|
||||
|
||||
// Interface is implemented by storage
|
||||
var _ Store = &Storage{}
|
||||
127
users/users.go
Normal file
127
users/users.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user