144 lines
2.8 KiB
Go
144 lines
2.8 KiB
Go
package users
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"strings"
|
|
|
|
"github.com/FAU-CDI/wisski-distillery/internal/phpx"
|
|
)
|
|
|
|
var errGetValidator = errors.New("GetPasswordValidator: Unknown Error")
|
|
|
|
func (u *Users) GetPasswordValidator(ctx context.Context, username string) (pv PasswordValidator, err error) {
|
|
server := u.Dependencies.PHP.NewServer()
|
|
|
|
var hash string
|
|
err = u.Dependencies.PHP.ExecScript(ctx, server, &hash, usersPHP, "get_password_hash", username)
|
|
if err != nil {
|
|
server.Close()
|
|
return pv, err
|
|
}
|
|
if len(hash) == 0 {
|
|
server.Close()
|
|
return pv, errGetValidator
|
|
}
|
|
|
|
pv.server = server
|
|
pv.username = username
|
|
pv.hash = hash
|
|
return pv, nil
|
|
}
|
|
|
|
type PasswordValidator struct {
|
|
server *phpx.Server
|
|
|
|
username string
|
|
hash string
|
|
}
|
|
|
|
func (pv PasswordValidator) Close() error {
|
|
return pv.server.Close()
|
|
}
|
|
|
|
func (pv PasswordValidator) Check(ctx context.Context, password string) bool {
|
|
var result phpx.Boolean
|
|
err := pv.server.MarshalCall(ctx, &result, "check_password_hash", password, string(pv.hash))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return bool(result)
|
|
}
|
|
|
|
var errPasswordUsername = errors.New("username === password")
|
|
|
|
type CommonPasswordError struct {
|
|
Password CommonPassword
|
|
}
|
|
|
|
func (cpe CommonPasswordError) Error() string {
|
|
return fmt.Sprintf("%q from %q", cpe.Password.Password, cpe.Password.Source)
|
|
}
|
|
|
|
func (pv PasswordValidator) CheckDictionary(ctx context.Context, writer io.Writer) error {
|
|
var counter int
|
|
|
|
if pv.Check(ctx, pv.username) {
|
|
if writer != nil {
|
|
counter++
|
|
fmt.Fprintln(writer, counter)
|
|
}
|
|
return errPasswordUsername
|
|
}
|
|
for candidate := range CommonPasswords() {
|
|
if ctx.Err() != nil {
|
|
continue
|
|
}
|
|
result := pv.Check(ctx, candidate.Password)
|
|
if writer != nil {
|
|
counter++
|
|
fmt.Fprintln(writer, counter)
|
|
}
|
|
|
|
if result {
|
|
return &CommonPasswordError{Password: candidate}
|
|
}
|
|
}
|
|
|
|
return ctx.Err()
|
|
}
|
|
|
|
//go:embed passwords
|
|
var passwordsEmbed embed.FS
|
|
|
|
type CommonPassword struct {
|
|
Password string
|
|
Source string
|
|
}
|
|
|
|
// CommonPasswords returns a channel of most common passwords
|
|
func CommonPasswords() <-chan CommonPassword {
|
|
pChan := make(chan CommonPassword, 10)
|
|
go func() {
|
|
defer close(pChan)
|
|
|
|
fs.WalkDir(passwordsEmbed, ".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// get the full path
|
|
if d.IsDir() || !strings.HasSuffix(path, ".txt") {
|
|
return nil
|
|
}
|
|
|
|
// open it
|
|
file, err := passwordsEmbed.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
// scan it line by line
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "//") {
|
|
continue
|
|
}
|
|
pChan <- CommonPassword{
|
|
Password: line,
|
|
Source: path,
|
|
}
|
|
}
|
|
|
|
return scanner.Err()
|
|
})
|
|
}()
|
|
return pChan
|
|
}
|