mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-18 22:41:04 +02:00
Add HTPasswdValidator to basic authentication package
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// htpasswdMap represents the structure of an htpasswd file.
|
||||
// Passwords must be generated with -B for bcrypt or -s for SHA1.
|
||||
type htpasswdMap struct {
|
||||
users map[string]interface{}
|
||||
}
|
||||
|
||||
// bcryptPass is used to identify bcrypt passwords in the
|
||||
// htpasswdMap users.
|
||||
type bcryptPass string
|
||||
|
||||
// sha1Pass os used to identify sha1 passwords in the
|
||||
// htpasswdMap users.
|
||||
type sha1Pass string
|
||||
|
||||
// NewHTPasswdValidator constructs an httpasswd based validator from the file
|
||||
// at the path given.
|
||||
func NewHTPasswdValidator(path string) (Validator, error) {
|
||||
r, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open htpasswd file: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
return newHtpasswd(r)
|
||||
}
|
||||
|
||||
// newHtpasswd consctructs an htpasswd from an io.Reader (an opened file).
|
||||
func newHtpasswd(file io.Reader) (*htpasswdMap, error) {
|
||||
csvReader := csv.NewReader(file)
|
||||
csvReader.Comma = ':'
|
||||
csvReader.Comment = '#'
|
||||
csvReader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csvReader.ReadAll()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read htpasswd file: %v", err)
|
||||
}
|
||||
|
||||
return createHtpasswdMap(records)
|
||||
}
|
||||
|
||||
// createHtasswdMap constructs an htpasswdMap from the given records
|
||||
func createHtpasswdMap(records [][]string) (*htpasswdMap, error) {
|
||||
h := &htpasswdMap{users: make(map[string]interface{})}
|
||||
for _, record := range records {
|
||||
user, realPassword := record[0], record[1]
|
||||
shaPrefix := realPassword[:5]
|
||||
if shaPrefix == "{SHA}" {
|
||||
h.users[user] = sha1Pass(realPassword[5:])
|
||||
continue
|
||||
}
|
||||
|
||||
bcryptPrefix := realPassword[:4]
|
||||
if bcryptPrefix == "$2a$" || bcryptPrefix == "$2b$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" {
|
||||
h.users[user] = bcryptPass(realPassword)
|
||||
continue
|
||||
}
|
||||
|
||||
// Password is neither sha1 or bcrypt
|
||||
// TODO(JoelSpeed): In the next breaking release, make this return an error.
|
||||
logger.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Validate checks a users password against the htpasswd entries
|
||||
func (h *htpasswdMap) Validate(user string, password string) bool {
|
||||
realPassword, exists := h.users[user]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
switch real := realPassword.(type) {
|
||||
case sha1Pass:
|
||||
d := sha1.New()
|
||||
d.Write([]byte(password))
|
||||
return string(real) == base64.StdEncoding.EncodeToString(d.Sum(nil))
|
||||
case bcryptPass:
|
||||
return bcrypt.CompareHashAndPassword([]byte(real), []byte(password)) == nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user