You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-07-15 01:44:22 +02:00
Merge pull request #687 from oauth2-proxy/htpasswd-validator
Refactor HTPasswd Validator
This commit is contained in:
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
## Changes since v6.0.0
|
## Changes since v6.0.0
|
||||||
|
|
||||||
|
- [#687](https://github.com/oauth2-proxy/oauth2-proxy/pull/687) Refactor HTPasswd Validator (@JoelSpeed)
|
||||||
- [#624](https://github.com/oauth2-proxy/oauth2-proxy/pull/624) Allow stripping authentication headers from whitelisted requests with `--skip-auth-strip-headers` (@NickMeves)
|
- [#624](https://github.com/oauth2-proxy/oauth2-proxy/pull/624) Allow stripping authentication headers from whitelisted requests with `--skip-auth-strip-headers` (@NickMeves)
|
||||||
- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
|
- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
|
||||||
- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
|
- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
|
||||||
|
72
htpasswd.go
72
htpasswd.go
@ -1,72 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/csv"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lookup passwords in a htpasswd file
|
|
||||||
// Passwords must be generated with -B for bcrypt or -s for SHA1.
|
|
||||||
|
|
||||||
// HtpasswdFile represents the structure of an htpasswd file
|
|
||||||
type HtpasswdFile struct {
|
|
||||||
Users map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHtpasswdFromFile constructs an HtpasswdFile from the file at the path given
|
|
||||||
func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
|
|
||||||
r, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
return NewHtpasswd(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHtpasswd consctructs an HtpasswdFile from an io.Reader (opened file)
|
|
||||||
func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
|
|
||||||
csvReader := csv.NewReader(file)
|
|
||||||
csvReader.Comma = ':'
|
|
||||||
csvReader.Comment = '#'
|
|
||||||
csvReader.TrimLeadingSpace = true
|
|
||||||
|
|
||||||
records, err := csvReader.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
h := &HtpasswdFile{Users: make(map[string]string)}
|
|
||||||
for _, record := range records {
|
|
||||||
h.Users[record[0]] = record[1]
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks a users password against the HtpasswdFile entries
|
|
||||||
func (h *HtpasswdFile) Validate(user string, password string) bool {
|
|
||||||
realPassword, exists := h.Users[user]
|
|
||||||
if !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
shaPrefix := realPassword[:5]
|
|
||||||
if shaPrefix == "{SHA}" {
|
|
||||||
shaValue := realPassword[5:]
|
|
||||||
d := sha1.New()
|
|
||||||
d.Write([]byte(password))
|
|
||||||
return shaValue == base64.StdEncoding.EncodeToString(d.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
bcryptPrefix := realPassword[:4]
|
|
||||||
if bcryptPrefix == "$2a$" || bcryptPrefix == "$2b$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" {
|
|
||||||
return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password)) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSHA(t *testing.T) {
|
|
||||||
file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\n"))
|
|
||||||
h, err := NewHtpasswd(file)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
|
|
||||||
valid := h.Validate("testuser", "asdf")
|
|
||||||
assert.Equal(t, valid, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBcrypt(t *testing.T) {
|
|
||||||
hash1, err := bcrypt.GenerateFromPassword([]byte("password"), 1)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
hash2, err := bcrypt.GenerateFromPassword([]byte("top-secret"), 2)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
|
|
||||||
contents := fmt.Sprintf("testuser1:%s\ntestuser2:%s\n", hash1, hash2)
|
|
||||||
file := bytes.NewBuffer([]byte(contents))
|
|
||||||
|
|
||||||
h, err := NewHtpasswd(file)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
|
|
||||||
valid := h.Validate("testuser1", "password")
|
|
||||||
assert.Equal(t, valid, true)
|
|
||||||
|
|
||||||
valid = h.Validate("testuser2", "top-secret")
|
|
||||||
assert.Equal(t, valid, true)
|
|
||||||
}
|
|
9
main.go
9
main.go
@ -66,15 +66,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.HtpasswdFile != "" {
|
|
||||||
logger.Printf("using htpasswd file %s", opts.HtpasswdFile)
|
|
||||||
oauthproxy.HtpasswdFile, err = NewHtpasswdFromFile(opts.HtpasswdFile)
|
|
||||||
oauthproxy.DisplayHtpasswdForm = opts.DisplayHtpasswdForm
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
chain := alice.New()
|
chain := alice.New()
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
|
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/authentication/basic"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
|
||||||
@ -96,8 +97,8 @@ type OAuthProxy struct {
|
|||||||
sessionStore sessionsapi.SessionStore
|
sessionStore sessionsapi.SessionStore
|
||||||
ProxyPrefix string
|
ProxyPrefix string
|
||||||
SignInMessage string
|
SignInMessage string
|
||||||
HtpasswdFile *HtpasswdFile
|
basicAuthValidator basic.Validator
|
||||||
DisplayHtpasswdForm bool
|
displayHtpasswdForm bool
|
||||||
serveMux http.Handler
|
serveMux http.Handler
|
||||||
SetXAuthRequest bool
|
SetXAuthRequest bool
|
||||||
PassBasicAuth bool
|
PassBasicAuth bool
|
||||||
@ -314,6 +315,16 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var basicAuthValidator basic.Validator
|
||||||
|
if opts.HtpasswdFile != "" {
|
||||||
|
logger.Printf("using htpasswd file: %s", opts.HtpasswdFile)
|
||||||
|
var err error
|
||||||
|
basicAuthValidator, err = basic.NewHTPasswdValidator(opts.HtpasswdFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not load htpasswdfile: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &OAuthProxy{
|
return &OAuthProxy{
|
||||||
CookieName: opts.Cookie.Name,
|
CookieName: opts.Cookie.Name,
|
||||||
CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
|
CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
|
||||||
@ -364,6 +375,9 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
trustedIPs: trustedIPs,
|
trustedIPs: trustedIPs,
|
||||||
Banner: opts.Banner,
|
Banner: opts.Banner,
|
||||||
Footer: opts.Footer,
|
Footer: opts.Footer,
|
||||||
|
|
||||||
|
basicAuthValidator: basicAuthValidator,
|
||||||
|
displayHtpasswdForm: basicAuthValidator != nil,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,10 +400,6 @@ func (p *OAuthProxy) GetRedirectURI(host string) string {
|
|||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OAuthProxy) displayCustomLoginForm() bool {
|
|
||||||
return p.HtpasswdFile != nil && p.DisplayHtpasswdForm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *OAuthProxy) redeemCode(ctx context.Context, host, code string) (s *sessionsapi.SessionState, err error) {
|
func (p *OAuthProxy) redeemCode(ctx context.Context, host, code string) (s *sessionsapi.SessionState, err error) {
|
||||||
if code == "" {
|
if code == "" {
|
||||||
return nil, errors.New("missing code")
|
return nil, errors.New("missing code")
|
||||||
@ -526,7 +536,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
|
|||||||
}{
|
}{
|
||||||
ProviderName: p.provider.Data().ProviderName,
|
ProviderName: p.provider.Data().ProviderName,
|
||||||
SignInMessage: template.HTML(p.SignInMessage),
|
SignInMessage: template.HTML(p.SignInMessage),
|
||||||
CustomLogin: p.displayCustomLoginForm(),
|
CustomLogin: p.displayHtpasswdForm,
|
||||||
Redirect: redirectURL,
|
Redirect: redirectURL,
|
||||||
Version: VERSION,
|
Version: VERSION,
|
||||||
ProxyPrefix: p.ProxyPrefix,
|
ProxyPrefix: p.ProxyPrefix,
|
||||||
@ -540,7 +550,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
|
|||||||
|
|
||||||
// ManualSignIn handles basic auth logins to the proxy
|
// ManualSignIn handles basic auth logins to the proxy
|
||||||
func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
|
func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
|
||||||
if req.Method != "POST" || p.HtpasswdFile == nil {
|
if req.Method != "POST" || p.basicAuthValidator == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
user := req.FormValue("username")
|
user := req.FormValue("username")
|
||||||
@ -549,7 +559,7 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
// check auth
|
// check auth
|
||||||
if p.HtpasswdFile.Validate(user, passwd) {
|
if p.basicAuthValidator.Validate(user, passwd) {
|
||||||
logger.PrintAuthf(user, req, logger.AuthSuccess, "Authenticated via HtpasswdFile")
|
logger.PrintAuthf(user, req, logger.AuthSuccess, "Authenticated via HtpasswdFile")
|
||||||
return user, true
|
return user, true
|
||||||
}
|
}
|
||||||
@ -1159,7 +1169,7 @@ func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
|
|||||||
// CheckBasicAuth checks the requests Authorization header for basic auth
|
// CheckBasicAuth checks the requests Authorization header for basic auth
|
||||||
// credentials and authenticates these against the proxies HtpasswdFile
|
// credentials and authenticates these against the proxies HtpasswdFile
|
||||||
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
|
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
|
||||||
if p.HtpasswdFile == nil {
|
if p.basicAuthValidator == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
auth := req.Header.Get("Authorization")
|
auth := req.Header.Get("Authorization")
|
||||||
@ -1178,7 +1188,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionStat
|
|||||||
if len(pair) != 2 {
|
if len(pair) != 2 {
|
||||||
return nil, fmt.Errorf("invalid format %s", b)
|
return nil, fmt.Errorf("invalid format %s", b)
|
||||||
}
|
}
|
||||||
if p.HtpasswdFile.Validate(pair[0], pair[1]) {
|
if p.basicAuthValidator.Validate(pair[0], pair[1]) {
|
||||||
logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
|
logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
|
||||||
return &sessionsapi.SessionState{User: pair[0]}, nil
|
return &sessionsapi.SessionState{User: pair[0]}, nil
|
||||||
}
|
}
|
||||||
|
16
pkg/authentication/basic/basic_suite_test.go
Normal file
16
pkg/authentication/basic/basic_suite_test.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasicSuite(t *testing.T) {
|
||||||
|
logger.SetOutput(GinkgoWriter)
|
||||||
|
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Basic")
|
||||||
|
}
|
96
pkg/authentication/basic/htpasswd.go
Normal file
96
pkg/authentication/basic/htpasswd.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
96
pkg/authentication/basic/htpasswd_test.go
Normal file
96
pkg/authentication/basic/htpasswd_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
adminUser = "admin"
|
||||||
|
adminPassword = "Adm1n1str$t0r"
|
||||||
|
user1 = "user1"
|
||||||
|
user1Password = "UsErOn3P455"
|
||||||
|
user2 = "user2"
|
||||||
|
user2Password = "us3r2P455W0Rd!"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("HTPasswd Suite", func() {
|
||||||
|
Context("with an HTPassword Validator", func() {
|
||||||
|
assertHtpasswdMapFromFile := func(filePath string) {
|
||||||
|
var htpasswd *htpasswdMap
|
||||||
|
var err error
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var validator Validator
|
||||||
|
validator, err = NewHTPasswdValidator(filePath)
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
htpasswd, ok = validator.(*htpasswdMap)
|
||||||
|
Expect(ok).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("does not return an error", func() {
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("has the correct number of users", func() {
|
||||||
|
Expect(htpasswd.users).To(HaveLen(3))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("accepts the correct passwords", func() {
|
||||||
|
Expect(htpasswd.Validate(adminUser, adminPassword)).To(BeTrue())
|
||||||
|
Expect(htpasswd.Validate(user1, user1Password)).To(BeTrue())
|
||||||
|
Expect(htpasswd.Validate(user2, user2Password)).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("rejects incorrect passwords", func() {
|
||||||
|
Expect(htpasswd.Validate(adminUser, "asvdfda")).To(BeFalse())
|
||||||
|
Expect(htpasswd.Validate(user1, "BHEdgbtr")).To(BeFalse())
|
||||||
|
Expect(htpasswd.Validate(user2, "12345")).To(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("rejects a non existent user", func() {
|
||||||
|
// Users are case sensitive
|
||||||
|
Expect(htpasswd.Validate("ADMIN", adminPassword)).To(BeFalse())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Context("load from file", func() {
|
||||||
|
Context("with sha1 entries", func() {
|
||||||
|
const filePath = "./test/htpasswd-sha1.txt"
|
||||||
|
|
||||||
|
assertHtpasswdMapFromFile(filePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with bcrypt entries", func() {
|
||||||
|
const filePath = "./test/htpasswd-bcrypt.txt"
|
||||||
|
|
||||||
|
assertHtpasswdMapFromFile(filePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with mixed entries", func() {
|
||||||
|
const filePath = "./test/htpasswd-mixed.txt"
|
||||||
|
|
||||||
|
assertHtpasswdMapFromFile(filePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with a non existent file", func() {
|
||||||
|
const filePath = "./test/htpasswd-doesnt-exist.txt"
|
||||||
|
var validator Validator
|
||||||
|
var err error
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
validator, err = NewHTPasswdValidator(filePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns an error", func() {
|
||||||
|
Expect(err).To(MatchError("could not open htpasswd file: open ./test/htpasswd-doesnt-exist.txt: no such file or directory"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns a nil validator", func() {
|
||||||
|
Expect(validator).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
8
pkg/authentication/basic/test/htpasswd-bcrypt.txt
Normal file
8
pkg/authentication/basic/test/htpasswd-bcrypt.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# admin:Adm1n1str$t0r
|
||||||
|
admin:$2y$05$SXWrNM7ldtbRzBvUC3VXyOvUeiUcP45XPwM93P5eeGOEPIiAZmJjC
|
||||||
|
|
||||||
|
# user1:UsErOn3P455
|
||||||
|
user1:$2y$05$/sZYJOk8.3Etg4V6fV7puuXfCJLmV5Q7u3xvKpjBSJUka.t2YtmmG
|
||||||
|
|
||||||
|
# user2: us3r2P455W0Rd!
|
||||||
|
user2:$2y$05$l22MubgKTZFTjTs8TNg5k.YKvcnM2.bA/.iwl0idef5CbekdvBxva
|
8
pkg/authentication/basic/test/htpasswd-mixed.txt
Normal file
8
pkg/authentication/basic/test/htpasswd-mixed.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# admin:Adm1n1str$t0r
|
||||||
|
admin:$2y$05$SXWrNM7ldtbRzBvUC3VXyOvUeiUcP45XPwM93P5eeGOEPIiAZmJjC
|
||||||
|
|
||||||
|
# user1:UsErOn3P455
|
||||||
|
user1:{SHA}Dvs/L78raajL4jEAHPkwflQXJzI=
|
||||||
|
|
||||||
|
# user2: us3r2P455W0Rd!
|
||||||
|
user2:{SHA}MoN9/JCJEcYUb6GCQ+2buDvn9pI=
|
8
pkg/authentication/basic/test/htpasswd-sha1.txt
Normal file
8
pkg/authentication/basic/test/htpasswd-sha1.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# admin:Adm1n1str$t0r
|
||||||
|
admin:{SHA}gXQeRH0bcaCfhAk2gOLm1uaePMA=
|
||||||
|
|
||||||
|
# user1:UsErOn3P455
|
||||||
|
user1:{SHA}Dvs/L78raajL4jEAHPkwflQXJzI=
|
||||||
|
|
||||||
|
# user2: us3r2P455W0Rd!
|
||||||
|
user2:{SHA}MoN9/JCJEcYUb6GCQ+2buDvn9pI=
|
7
pkg/authentication/basic/validator.go
Normal file
7
pkg/authentication/basic/validator.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
// Validator is a minimal interface for something that can validate a
|
||||||
|
// username and password combination.
|
||||||
|
type Validator interface {
|
||||||
|
Validate(user, password string) bool
|
||||||
|
}
|
Reference in New Issue
Block a user