Add htpasswd support for basic auth

If the password arg starts with htpasswd=, then the rest is treated as
the file name of the htpasswd file, and used for md5 and sha1 hashes.
This commit is contained in:
Tamás Gulácsi
2015-08-30 20:07:43 +02:00
parent d79d2611ca
commit 392f1d70eb
4 changed files with 178 additions and 23 deletions
+18 -2
View File
@@ -1,6 +1,8 @@
package setup
import (
"strings"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/basicauth"
)
@@ -23,6 +25,7 @@ func BasicAuth(c *Controller) (middleware.Middleware, error) {
func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
var rules []basicauth.Rule
var err error
for c.Next() {
var rule basicauth.Rule
@@ -31,7 +34,10 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
switch len(args) {
case 2:
rule.Username = args[0]
rule.Password = args[1]
if rule.Password, err = passwordMatcher(rule.Username, args[1]); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
for c.NextBlock() {
rule.Resources = append(rule.Resources, c.Val())
if c.NextArg() {
@@ -41,7 +47,9 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
case 3:
rule.Resources = append(rule.Resources, args[0])
rule.Username = args[1]
rule.Password = args[2]
if rule.Password, err = passwordMatcher(rule.Username, args[2]); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
default:
return rules, c.ArgErr()
}
@@ -51,3 +59,11 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
return rules, nil
}
func passwordMatcher(username, passw string) (basicauth.PasswordMatcher, error) {
if !strings.HasPrefix(passw, "htpasswd=") {
return basicauth.PlainMatcher(passw), nil
}
return basicauth.GetHtpasswdMatcher(passw[9:], username)
}
+48 -16
View File
@@ -2,6 +2,9 @@ package setup
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/mholt/caddy/middleware/basicauth"
@@ -30,35 +33,57 @@ func TestBasicAuth(t *testing.T) {
}
func TestBasicAuthParse(t *testing.T) {
htpasswdPasswd := "IedFOuGmTpT8"
htpasswdFile := `sha1:{SHA}dcAUljwz99qFjYR0YLTXx0RqLww=
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
var skipHtpassword bool
htfh, err := ioutil.TempFile("", "basicauth-")
if err != nil {
t.Logf("Error creating temp file (%v), will skip htpassword test")
skipHtpassword = true
} else {
if _, err = htfh.Write([]byte(htpasswdFile)); err != nil {
t.Fatalf("write htpasswd file %q: %v", htfh.Name(), err)
}
htfh.Close()
defer os.Remove(htfh.Name())
}
tests := []struct {
input string
shouldErr bool
password string
expected []basicauth.Rule
}{
{`basicauth user pwd`, false, []basicauth.Rule{
{Username: "user", Password: "pwd"},
{`basicauth user pwd`, false, "pwd", []basicauth.Rule{
{Username: "user"},
}},
{`basicauth user pwd {
}`, false, []basicauth.Rule{
{Username: "user", Password: "pwd"},
}`, false, "pwd", []basicauth.Rule{
{Username: "user"},
}},
{`basicauth user pwd {
/resource1
/resource2
}`, false, []basicauth.Rule{
{Username: "user", Password: "pwd", Resources: []string{"/resource1", "/resource2"}},
}`, false, "pwd", []basicauth.Rule{
{Username: "user", Resources: []string{"/resource1", "/resource2"}},
}},
{`basicauth /resource user pwd`, false, []basicauth.Rule{
{Username: "user", Password: "pwd", Resources: []string{"/resource"}},
{`basicauth /resource user pwd`, false, "pwd", []basicauth.Rule{
{Username: "user", Resources: []string{"/resource"}},
}},
{`basicauth /res1 user1 pwd1
basicauth /res2 user2 pwd2`, false, []basicauth.Rule{
{Username: "user1", Password: "pwd1", Resources: []string{"/res1"}},
{Username: "user2", Password: "pwd2", Resources: []string{"/res2"}},
basicauth /res2 user2 pwd2`, false, "pwd", []basicauth.Rule{
{Username: "user1", Resources: []string{"/res1"}},
{Username: "user2", Resources: []string{"/res2"}},
}},
{`basicauth user`, true, "", []basicauth.Rule{}},
{`basicauth`, true, "", []basicauth.Rule{}},
{`basicauth /resource user pwd asdf`, true, "", []basicauth.Rule{}},
{`basicauth sha1 htpasswd=` + htfh.Name(), false, htpasswdPasswd, []basicauth.Rule{
{Username: "sha1"},
}},
{`basicauth user`, true, []basicauth.Rule{}},
{`basicauth`, true, []basicauth.Rule{}},
{`basicauth /resource user pwd asdf`, true, []basicauth.Rule{}},
}
for i, test := range tests {
@@ -84,9 +109,16 @@ func TestBasicAuthParse(t *testing.T) {
i, j, expectedRule.Username, actualRule.Username)
}
if actualRule.Password != expectedRule.Password {
if strings.Contains(test.input, "htpasswd=") && skipHtpassword {
continue
}
pwd := test.password
if len(actual) > 1 {
pwd = fmt.Sprintf("%s%d", pwd, j+1)
}
if !actualRule.Password(pwd) || actualRule.Password(test.password+"!") {
t.Errorf("Test %d, rule %d: Expected password '%s', got '%s'",
i, j, expectedRule.Password, actualRule.Password)
i, j, test.password, actualRule.Password)
}
expectedRes := fmt.Sprintf("%v", expectedRule.Resources)