mirror of
				https://github.com/bolucat/Archive.git
				synced 2025-10-31 03:46:52 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			304 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package auth
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"strings"
 | |
| 
 | |
| 	fbErrors "github.com/filebrowser/filebrowser/v2/errors"
 | |
| 	"github.com/filebrowser/filebrowser/v2/files"
 | |
| 	"github.com/filebrowser/filebrowser/v2/settings"
 | |
| 	"github.com/filebrowser/filebrowser/v2/users"
 | |
| )
 | |
| 
 | |
| // MethodHookAuth is used to identify hook auth.
 | |
| const MethodHookAuth settings.AuthMethod = "hook"
 | |
| 
 | |
| type hookCred struct {
 | |
| 	Password string `json:"password"`
 | |
| 	Username string `json:"username"`
 | |
| }
 | |
| 
 | |
| // HookAuth is a hook implementation of an Auther.
 | |
| type HookAuth struct {
 | |
| 	Users    users.Store        `json:"-"`
 | |
| 	Settings *settings.Settings `json:"-"`
 | |
| 	Server   *settings.Server   `json:"-"`
 | |
| 	Cred     hookCred           `json:"-"`
 | |
| 	Fields   hookFields         `json:"-"`
 | |
| 	Command  string             `json:"command"`
 | |
| }
 | |
| 
 | |
| // Auth authenticates the user via a json in content body.
 | |
| func (a *HookAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) {
 | |
| 	var cred hookCred
 | |
| 
 | |
| 	if r.Body == nil {
 | |
| 		return nil, os.ErrPermission
 | |
| 	}
 | |
| 
 | |
| 	err := json.NewDecoder(r.Body).Decode(&cred)
 | |
| 	if err != nil {
 | |
| 		return nil, os.ErrPermission
 | |
| 	}
 | |
| 
 | |
| 	a.Users = usr
 | |
| 	a.Settings = stg
 | |
| 	a.Server = srv
 | |
| 	a.Cred = cred
 | |
| 
 | |
| 	action, err := a.RunCommand()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	switch action {
 | |
| 	case "auth":
 | |
| 		u, err := a.SaveUser()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return u, nil
 | |
| 	case "block":
 | |
| 		return nil, os.ErrPermission
 | |
| 	case "pass":
 | |
| 		u, err := a.Users.Get(a.Server.Root, a.Cred.Username)
 | |
| 		if err != nil || !users.CheckPwd(a.Cred.Password, u.Password) {
 | |
| 			return nil, os.ErrPermission
 | |
| 		}
 | |
| 		return u, nil
 | |
| 	default:
 | |
| 		return nil, fmt.Errorf("invalid hook action: %s", action)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // LoginPage tells that hook auth requires a login page.
 | |
| func (a *HookAuth) LoginPage() bool {
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // RunCommand starts the hook command and returns the action
 | |
| func (a *HookAuth) RunCommand() (string, error) {
 | |
| 	command := strings.Split(a.Command, " ")
 | |
| 	envMapping := func(key string) string {
 | |
| 		switch key {
 | |
| 		case "USERNAME":
 | |
| 			return a.Cred.Username
 | |
| 		case "PASSWORD":
 | |
| 			return a.Cred.Password
 | |
| 		default:
 | |
| 			return os.Getenv(key)
 | |
| 		}
 | |
| 	}
 | |
| 	for i, arg := range command {
 | |
| 		if i == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		command[i] = os.Expand(arg, envMapping)
 | |
| 	}
 | |
| 
 | |
| 	cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
 | |
| 	cmd.Env = append(os.Environ(), fmt.Sprintf("USERNAME=%s", a.Cred.Username))
 | |
| 	cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", a.Cred.Password))
 | |
| 	out, err := cmd.Output()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	a.GetValues(string(out))
 | |
| 
 | |
| 	return a.Fields.Values["hook.action"], nil
 | |
| }
 | |
| 
 | |
| // GetValues creates a map with values from the key-value format string
 | |
| func (a *HookAuth) GetValues(s string) {
 | |
| 	m := map[string]string{}
 | |
| 
 | |
| 	// make line breaks consistent on Windows platform
 | |
| 	s = strings.ReplaceAll(s, "\r\n", "\n")
 | |
| 
 | |
| 	// iterate input lines
 | |
| 	for _, val := range strings.Split(s, "\n") {
 | |
| 		v := strings.SplitN(val, "=", 2)
 | |
| 
 | |
| 		// skips non key and value format
 | |
| 		if len(v) != 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		fieldKey := strings.TrimSpace(v[0])
 | |
| 		fieldValue := strings.TrimSpace(v[1])
 | |
| 
 | |
| 		if a.Fields.IsValid(fieldKey) {
 | |
| 			m[fieldKey] = fieldValue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	a.Fields.Values = m
 | |
| }
 | |
| 
 | |
| // SaveUser updates the existing user or creates a new one when not found
 | |
| func (a *HookAuth) SaveUser() (*users.User, error) {
 | |
| 	u, err := a.Users.Get(a.Server.Root, a.Cred.Username)
 | |
| 	if err != nil && !errors.Is(err, fbErrors.ErrNotExist) {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if u == nil {
 | |
| 		pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		// create user with the provided credentials
 | |
| 		d := &users.User{
 | |
| 			Username:     a.Cred.Username,
 | |
| 			Password:     pass,
 | |
| 			Scope:        a.Settings.Defaults.Scope,
 | |
| 			Locale:       a.Settings.Defaults.Locale,
 | |
| 			ViewMode:     a.Settings.Defaults.ViewMode,
 | |
| 			SingleClick:  a.Settings.Defaults.SingleClick,
 | |
| 			Sorting:      a.Settings.Defaults.Sorting,
 | |
| 			Perm:         a.Settings.Defaults.Perm,
 | |
| 			Commands:     a.Settings.Defaults.Commands,
 | |
| 			HideDotfiles: a.Settings.Defaults.HideDotfiles,
 | |
| 		}
 | |
| 		u = a.GetUser(d)
 | |
| 
 | |
| 		userHome, err := a.Settings.MakeUserDir(u.Username, u.Scope, a.Server.Root)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("user: failed to mkdir user home dir: [%s]", userHome)
 | |
| 		}
 | |
| 		u.Scope = userHome
 | |
| 		log.Printf("user: %s, home dir: [%s].", u.Username, userHome)
 | |
| 
 | |
| 		err = a.Users.Save(u)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	} else if p := !users.CheckPwd(a.Cred.Password, u.Password); len(a.Fields.Values) > 1 || p {
 | |
| 		u = a.GetUser(u)
 | |
| 
 | |
| 		// update the password when it doesn't match the current
 | |
| 		if p {
 | |
| 			pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			u.Password = pass
 | |
| 		}
 | |
| 
 | |
| 		// update user with provided fields
 | |
| 		err := a.Users.Update(u)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return u, nil
 | |
| }
 | |
| 
 | |
| // GetUser returns a User filled with hook values or provided defaults
 | |
| func (a *HookAuth) GetUser(d *users.User) *users.User {
 | |
| 	// adds all permissions when user is admin
 | |
| 	isAdmin := a.Fields.GetBoolean("user.perm.admin", d.Perm.Admin)
 | |
| 	perms := users.Permissions{
 | |
| 		Admin:    isAdmin,
 | |
| 		Execute:  isAdmin || a.Fields.GetBoolean("user.perm.execute", d.Perm.Execute),
 | |
| 		Create:   isAdmin || a.Fields.GetBoolean("user.perm.create", d.Perm.Create),
 | |
| 		Rename:   isAdmin || a.Fields.GetBoolean("user.perm.rename", d.Perm.Rename),
 | |
| 		Modify:   isAdmin || a.Fields.GetBoolean("user.perm.modify", d.Perm.Modify),
 | |
| 		Delete:   isAdmin || a.Fields.GetBoolean("user.perm.delete", d.Perm.Delete),
 | |
| 		Share:    isAdmin || a.Fields.GetBoolean("user.perm.share", d.Perm.Share),
 | |
| 		Download: isAdmin || a.Fields.GetBoolean("user.perm.download", d.Perm.Download),
 | |
| 	}
 | |
| 	user := users.User{
 | |
| 		ID:          d.ID,
 | |
| 		Username:    d.Username,
 | |
| 		Password:    d.Password,
 | |
| 		Scope:       a.Fields.GetString("user.scope", d.Scope),
 | |
| 		Locale:      a.Fields.GetString("user.locale", d.Locale),
 | |
| 		ViewMode:    users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))),
 | |
| 		SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick),
 | |
| 		Sorting: files.Sorting{
 | |
| 			Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc),
 | |
| 			By:  a.Fields.GetString("user.sorting.by", d.Sorting.By),
 | |
| 		},
 | |
| 		Commands:     a.Fields.GetArray("user.commands", d.Commands),
 | |
| 		HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles),
 | |
| 		Perm:         perms,
 | |
| 		LockPassword: true,
 | |
| 	}
 | |
| 
 | |
| 	return &user
 | |
| }
 | |
| 
 | |
| // hookFields is used to access fields from the hook
 | |
| type hookFields struct {
 | |
| 	Values map[string]string
 | |
| }
 | |
| 
 | |
| // validHookFields contains names of the fields that can be used
 | |
| var validHookFields = []string{
 | |
| 	"hook.action",
 | |
| 	"user.scope",
 | |
| 	"user.locale",
 | |
| 	"user.viewMode",
 | |
| 	"user.singleClick",
 | |
| 	"user.sorting.by",
 | |
| 	"user.sorting.asc",
 | |
| 	"user.commands",
 | |
| 	"user.hideDotfiles",
 | |
| 	"user.perm.admin",
 | |
| 	"user.perm.execute",
 | |
| 	"user.perm.create",
 | |
| 	"user.perm.rename",
 | |
| 	"user.perm.modify",
 | |
| 	"user.perm.delete",
 | |
| 	"user.perm.share",
 | |
| 	"user.perm.download",
 | |
| }
 | |
| 
 | |
| // IsValid checks if the provided field is on the valid fields list
 | |
| func (hf *hookFields) IsValid(field string) bool {
 | |
| 	for _, val := range validHookFields {
 | |
| 		if field == val {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // GetString returns the string value or provided default
 | |
| func (hf *hookFields) GetString(k, dv string) string {
 | |
| 	val, ok := hf.Values[k]
 | |
| 	if ok {
 | |
| 		return val
 | |
| 	}
 | |
| 	return dv
 | |
| }
 | |
| 
 | |
| // GetBoolean returns the bool value or provided default
 | |
| func (hf *hookFields) GetBoolean(k string, dv bool) bool {
 | |
| 	val, ok := hf.Values[k]
 | |
| 	if ok {
 | |
| 		return val == "true"
 | |
| 	}
 | |
| 	return dv
 | |
| }
 | |
| 
 | |
| // GetArray returns the array value or provided default
 | |
| func (hf *hookFields) GetArray(k string, dv []string) []string {
 | |
| 	val, ok := hf.Values[k]
 | |
| 	if ok && strings.TrimSpace(val) != "" {
 | |
| 		return strings.Split(val, " ")
 | |
| 	}
 | |
| 	return dv
 | |
| }
 | 
![github-action[bot]](/assets/img/avatar_default.png)