forked from filebrowser/filebrowser
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f80b016
commit dda9a38
Showing
12 changed files
with
340 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"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) //nolint: gomnd | ||
|
||
// skips non key and value format | ||
if len(v) != 2 { //nolint: gomnd | ||
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 && err != errors.ErrNotExist { | ||
return nil, err | ||
} | ||
|
||
if u == nil { | ||
pass, err := users.HashPwd(a.Cred.Password) | ||
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.HashPwd(a.Cred.Password) | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.