mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-27 01:02:29 -04:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user