From 02235c36bc904b20cdd2adf2de50dee62c4eea98 Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Fri, 29 Sep 2023 18:52:56 +0000 Subject: [PATCH] htpasswd: clean up errors Signed-off-by: Alejandro Mery --- htpasswd/errors.go | 49 +++++++++++++++++++++++++++++++++++++ htpasswd/htpasswd.go | 57 ++++++++++++++++++++++++++++++-------------- 2 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 htpasswd/errors.go diff --git a/htpasswd/errors.go b/htpasswd/errors.go new file mode 100644 index 0000000..70e78d1 --- /dev/null +++ b/htpasswd/errors.go @@ -0,0 +1,49 @@ +package htpasswd + +import ( + "errors" + "strings" +) + +var ( + // ErrExists indicates the specified user already exists + ErrExists = errors.New("user already exists") + // ErrNotExists indicates the specified user does not exist + ErrNotExists = errors.New("user does not exist") + // ErrInvalidAlgorithm indicates the htpasswd entry doesn't contain a + // valid algorithm signature + ErrInvalidAlgorithm = errors.New("invalid algorithm") + // ErrInvalidPassword indicates the offered password doesn't match + ErrInvalidPassword = errors.New("invalid password") +) + +// UserError indicates an error associated to the specified user +type UserError struct { + Name string + Hint string + Err error +} + +func (e *UserError) Error() string { + var buf strings.Builder + var reason string + + if e.Hint != "" { + reason = e.Hint + } else { + reason = e.Err.Error() + } + + if e.Name == "" { + return reason + } + + _, _ = buf.WriteString(e.Name) + _, _ = buf.WriteString(": ") + _, _ = buf.WriteString(reason) + return buf.String() +} + +func (e *UserError) Unwrap() error { + return e.Err +} diff --git a/htpasswd/htpasswd.go b/htpasswd/htpasswd.go index 1d3b867..dd402a5 100644 --- a/htpasswd/htpasswd.go +++ b/htpasswd/htpasswd.go @@ -51,8 +51,10 @@ func Parse(htpasswdBytes []byte) (Passwds, error) { _, exists := passwords[parts[0]] if exists { - err = errors.New("invalid htpasswd file - user " + - parts[0] + " defined more than once") + err = &UserError{ + Name: parts[0], + Err: ErrExists, + } return passwords, err } passwords[parts[0]] = parts[1] @@ -78,8 +80,12 @@ func CreateUser(file, user, passwd string, algo Hasher) error { // using the given name, password and hashing algorithm func (pp Passwds) CreateUser(user, passwd string, algo Hasher) error { if _, exists := pp[user]; exists { - return fmt.Errorf("user %s already exists", user) + return &UserError{ + Name: user, + Err: ErrExists, + } } + h, err := algo.Hash(passwd) if err != nil { return err @@ -106,8 +112,12 @@ func UpdateUser(file, user, passwd string, algo Hasher) error { // using the given name, password and hashing algorithm func (pp Passwds) UpdateUser(user, passwd string, algo Hasher) error { if _, exists := pp[user]; !exists { - return fmt.Errorf("user %s does not exist", user) + return &UserError{ + Name: user, + Err: ErrNotExists, + } } + h, err := algo.Hash(passwd) if err != nil { return err @@ -133,7 +143,10 @@ func DeleteUser(file, user string) error { // DeleteUser deletes the named user from the named file func (pp Passwds) DeleteUser(user string) error { if _, exists := pp[user]; !exists { - return fmt.Errorf("user %s does not exist", user) + return &UserError{ + Name: user, + Err: ErrNotExists, + } } delete(pp, user) @@ -154,12 +167,20 @@ func VerifyUser(file, user, passwd string) error { // with the given Passwd object func (pp Passwds) VerifyUser(user, passwd string) error { if _, ok := pp[user]; !ok { - return fmt.Errorf("user %s does not exist", user) + return &UserError{ + Name: user, + Err: ErrNotExists, + } } - alg, err := identifyHash(pp[user]) - if err != nil { - return fmt.Errorf("cannot identify algo %v", alg) + + alg := identifyHash(pp[user]) + if alg == nil { + return &UserError{ + Name: user, + Err: ErrInvalidAlgorithm, + } } + return alg.Match(passwd, pp[user]) } @@ -177,24 +198,24 @@ func (pp Passwds) Bytes() []byte { return pass } -func identifyHash(h string) (Hasher, error) { +func identifyHash(h string) Hasher { switch { case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"), strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"): - return new(Bcrypt), nil + return new(Bcrypt) case strings.HasPrefix(h, "$apr1$"): - return new(Apr1), nil + return new(Apr1) case strings.HasPrefix(h, "{SHA}"): - return new(Sha), nil + return new(Sha) case strings.HasPrefix(h, "{SSHA}"): - return new(Ssha), nil + return new(Ssha) case strings.HasPrefix(h, "$5$"): - return new(Sha256), nil + return new(Sha256) case strings.HasPrefix(h, "$6$"): - return new(Sha512), nil + return new(Sha512) + default: + return nil } - - return nil, fmt.Errorf("unsupported hash algorithm") } func validLine(parts []string, lineNumber int, line string) (bool, error) {