git.iko.soy/typeable/oauth2-proxy
on 71dc702 help sign in
96 lines 2.6 kB history raw
1package basic
3import (
4 "crypto/sha1"
5 "encoding/base64"
6 "encoding/csv"
7 "fmt"
8 "io"
9 "os"
11 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
12 "golang.org/x/crypto/bcrypt"
15// htpasswdMap represents the structure of an htpasswd file.
16// Passwords must be generated with -B for bcrypt or -s for SHA1.
17type htpasswdMap struct {
18 users map[string]interface{}
21// bcryptPass is used to identify bcrypt passwords in the
22// htpasswdMap users.
23type bcryptPass string
25// sha1Pass os used to identify sha1 passwords in the
26// htpasswdMap users.
27type sha1Pass string
29// NewHTPasswdValidator constructs an httpasswd based validator from the file
30// at the path given.
31func NewHTPasswdValidator(path string) (Validator, error) {
32 r, err := os.Open(path)
33 if err != nil {
34 return nil, fmt.Errorf("could not open htpasswd file: %v", err)
35 }
36 defer r.Close()
37 return newHtpasswd(r)
40// newHtpasswd consctructs an htpasswd from an io.Reader (an opened file).
41func newHtpasswd(file io.Reader) (*htpasswdMap, error) {
42 csvReader := csv.NewReader(file)
43 csvReader.Comma = ':'
44 csvReader.Comment = '#'
45 csvReader.TrimLeadingSpace = true
47 records, err := csvReader.ReadAll()
48 if err != nil {
49 return nil, fmt.Errorf("could not read htpasswd file: %v", err)
50 }
52 return createHtpasswdMap(records)
55// createHtasswdMap constructs an htpasswdMap from the given records
56func createHtpasswdMap(records [][]string) (*htpasswdMap, error) {
57 h := &htpasswdMap{users: make(map[string]interface{})}
58 for _, record := range records {
59 user, realPassword := record[0], record[1]
60 shaPrefix := realPassword[:5]
61 if shaPrefix == "{SHA}" {
62 h.users[user] = sha1Pass(realPassword[5:])
63 continue
64 }
66 bcryptPrefix := realPassword[:4]
67 if bcryptPrefix == "$2a$" || bcryptPrefix == "$2b$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" {
68 h.users[user] = bcryptPass(realPassword)
69 continue
70 }
72 // Password is neither sha1 or bcrypt
73 // TODO(JoelSpeed): In the next breaking release, make this return an error.
74 logger.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
75 }
76 return h, nil
79// Validate checks a users password against the htpasswd entries
80func (h *htpasswdMap) Validate(user string, password string) bool {
81 realPassword, exists := h.users[user]
82 if !exists {
83 return false
84 }
86 switch real := realPassword.(type) {
87 case sha1Pass:
88 d := sha1.New()
89 d.Write([]byte(password))
90 return string(real) == base64.StdEncoding.EncodeToString(d.Sum(nil))
91 case bcryptPass:
92 return bcrypt.CompareHashAndPassword([]byte(real), []byte(password)) == nil
93 default:
94 return false
95 }