83 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			83 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			Go
		
	
	
package htpasswd
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/docker/distribution/registry/auth"
 | 
						|
 | 
						|
	"golang.org/x/crypto/bcrypt"
 | 
						|
)
 | 
						|
 | 
						|
// htpasswd holds a path to a system .htpasswd file and the machinery to parse
 | 
						|
// it. Only bcrypt hash entries are supported.
 | 
						|
type htpasswd struct {
 | 
						|
	entries map[string][]byte // maps username to password byte slice.
 | 
						|
}
 | 
						|
 | 
						|
// newHTPasswd parses the reader and returns an htpasswd or an error.
 | 
						|
func newHTPasswd(rd io.Reader) (*htpasswd, error) {
 | 
						|
	entries, err := parseHTPasswd(rd)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return &htpasswd{entries: entries}, nil
 | 
						|
}
 | 
						|
 | 
						|
// AuthenticateUser checks a given user:password credential against the
 | 
						|
// receiving HTPasswd's file. If the check passes, nil is returned.
 | 
						|
func (htpasswd *htpasswd) authenticateUser(username string, password string) error {
 | 
						|
	credentials, ok := htpasswd.entries[username]
 | 
						|
	if !ok {
 | 
						|
		// timing attack paranoia
 | 
						|
		bcrypt.CompareHashAndPassword([]byte{}, []byte(password))
 | 
						|
 | 
						|
		return auth.ErrAuthenticationFailure
 | 
						|
	}
 | 
						|
 | 
						|
	err := bcrypt.CompareHashAndPassword(credentials, []byte(password))
 | 
						|
	if err != nil {
 | 
						|
		return auth.ErrAuthenticationFailure
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// parseHTPasswd parses the contents of htpasswd. This will read all the
 | 
						|
// entries in the file, whether or not they are needed. An error is returned
 | 
						|
// if a syntax errors are encountered or if the reader fails.
 | 
						|
func parseHTPasswd(rd io.Reader) (map[string][]byte, error) {
 | 
						|
	entries := map[string][]byte{}
 | 
						|
	scanner := bufio.NewScanner(rd)
 | 
						|
	var line int
 | 
						|
	for scanner.Scan() {
 | 
						|
		line++ // 1-based line numbering
 | 
						|
		t := strings.TrimSpace(scanner.Text())
 | 
						|
 | 
						|
		if len(t) < 1 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// lines that *begin* with a '#' are considered comments
 | 
						|
		if t[0] == '#' {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		i := strings.Index(t, ":")
 | 
						|
		if i < 0 || i >= len(t) {
 | 
						|
			return nil, fmt.Errorf("htpasswd: invalid entry at line %d: %q", line, scanner.Text())
 | 
						|
		}
 | 
						|
 | 
						|
		entries[t[:i]] = []byte(t[i+1:])
 | 
						|
	}
 | 
						|
 | 
						|
	if err := scanner.Err(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return entries, nil
 | 
						|
}
 |