mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-27 17:22:30 -04:00
Complete test coverage for replacer for Go
This commit is contained in:
@@ -2,9 +2,17 @@
|
||||
package basicauth
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jimstudt/http-authentication/basic"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
@@ -14,8 +22,9 @@ import (
|
||||
// security of HTTP Basic Auth is disputed. Use discretion when deciding
|
||||
// what to protect with BasicAuth.
|
||||
type BasicAuth struct {
|
||||
Next middleware.Handler
|
||||
Rules []Rule
|
||||
Next middleware.Handler
|
||||
SiteRoot string
|
||||
Rules []Rule
|
||||
}
|
||||
|
||||
// ServeHTTP implements the middleware.Handler interface.
|
||||
@@ -37,7 +46,8 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||
// Check credentials
|
||||
if !ok ||
|
||||
username != rule.Username ||
|
||||
subtle.ConstantTimeCompare([]byte(password), []byte(rule.Password)) != 1 {
|
||||
!rule.Password(password) {
|
||||
//subtle.ConstantTimeCompare([]byte(password), []byte(rule.Password)) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -64,6 +74,71 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||
// file or directory paths.
|
||||
type Rule struct {
|
||||
Username string
|
||||
Password string
|
||||
Password func(string) bool
|
||||
Resources []string
|
||||
}
|
||||
|
||||
type PasswordMatcher func(pw string) bool
|
||||
|
||||
var (
|
||||
htpasswords map[string]map[string]PasswordMatcher
|
||||
htpasswordsMu sync.Mutex
|
||||
)
|
||||
|
||||
func GetHtpasswdMatcher(filename, username, siteRoot string) (PasswordMatcher, error) {
|
||||
filename = filepath.Join(siteRoot, filename)
|
||||
htpasswordsMu.Lock()
|
||||
if htpasswords == nil {
|
||||
htpasswords = make(map[string]map[string]PasswordMatcher)
|
||||
}
|
||||
pm := htpasswords[filename]
|
||||
if pm == nil {
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open %q: %v", filename, err)
|
||||
}
|
||||
defer fh.Close()
|
||||
pm = make(map[string]PasswordMatcher)
|
||||
if err = parseHtpasswd(pm, fh); err != nil {
|
||||
return nil, fmt.Errorf("parsing htpasswd %q: %v", fh.Name(), err)
|
||||
}
|
||||
htpasswords[filename] = pm
|
||||
}
|
||||
htpasswordsMu.Unlock()
|
||||
if pm[username] == nil {
|
||||
return nil, fmt.Errorf("username %q not found in %q", username, filename)
|
||||
}
|
||||
return pm[username], nil
|
||||
}
|
||||
|
||||
func parseHtpasswd(pm map[string]PasswordMatcher, r io.Reader) error {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.IndexByte(line, '#') == 0 {
|
||||
continue
|
||||
}
|
||||
i := strings.IndexByte(line, ':')
|
||||
if i <= 0 {
|
||||
return fmt.Errorf("malformed line, no color: %q", line)
|
||||
}
|
||||
user, encoded := line[:i], line[i+1:]
|
||||
for _, p := range basic.DefaultSystems {
|
||||
matcher, err := p(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if matcher != nil {
|
||||
pm[user] = matcher.MatchesPassword
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func PlainMatcher(passw string) PasswordMatcher {
|
||||
return func(pw string) bool {
|
||||
return subtle.ConstantTimeCompare([]byte(pw), []byte(passw)) == 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package basicauth
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
@@ -15,7 +17,7 @@ func TestBasicAuth(t *testing.T) {
|
||||
rw := BasicAuth{
|
||||
Next: middleware.HandlerFunc(contentHandler),
|
||||
Rules: []Rule{
|
||||
{Username: "test", Password: "ttest", Resources: []string{"/testing"}},
|
||||
{Username: "test", Password: PlainMatcher("ttest"), Resources: []string{"/testing"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -66,8 +68,8 @@ func TestMultipleOverlappingRules(t *testing.T) {
|
||||
rw := BasicAuth{
|
||||
Next: middleware.HandlerFunc(contentHandler),
|
||||
Rules: []Rule{
|
||||
{Username: "t", Password: "p1", Resources: []string{"/t"}},
|
||||
{Username: "t1", Password: "p2", Resources: []string{"/t/t"}},
|
||||
{Username: "t", Password: PlainMatcher("p1"), Resources: []string{"/t"}},
|
||||
{Username: "t1", Password: PlainMatcher("p2"), Resources: []string{"/t/t"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -111,3 +113,31 @@ func contentHandler(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
fmt.Fprintf(w, r.URL.String())
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func TestHtpasswd(t *testing.T) {
|
||||
htpasswdPasswd := "IedFOuGmTpT8"
|
||||
htpasswdFile := `sha1:{SHA}dcAUljwz99qFjYR0YLTXx0RqLww=
|
||||
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
|
||||
|
||||
htfh, err := ioutil.TempFile("", "basicauth-")
|
||||
if err != nil {
|
||||
t.Skipf("Error creating temp file (%v), will skip htpassword test")
|
||||
return
|
||||
}
|
||||
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())
|
||||
|
||||
for i, username := range []string{"sha1", "md5"} {
|
||||
rule := Rule{Username: username, Resources: []string{"/testing"}}
|
||||
if rule.Password, err = GetHtpasswdMatcher(htfh.Name(), rule.Username, "/"); err != nil {
|
||||
t.Fatalf("GetHtpasswdMatcher(%q, %q): %v", htfh.Name(), rule.Username, err)
|
||||
}
|
||||
t.Logf("%d. username=%q password=%v", i, rule.Username, rule.Password)
|
||||
if !rule.Password(htpasswdPasswd) || rule.Password(htpasswdPasswd+"!") {
|
||||
t.Errorf("%d (%s) password does not match.", i, rule.Username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,6 @@ func TestBrowseJson(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to Marshal the listing ")
|
||||
}
|
||||
|
||||
expectedJsonString := string(marsh)
|
||||
if actualJsonResponseString != expectedJsonString {
|
||||
t.Errorf("Json response string doesnt match the expected Json response ")
|
||||
|
||||
@@ -20,6 +20,7 @@ type ErrorHandler struct {
|
||||
ErrorPages map[int]string // map of status code to filename
|
||||
LogFile string
|
||||
Log *log.Logger
|
||||
LogRoller *middleware.LogRoller
|
||||
}
|
||||
|
||||
func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
|
||||
@@ -47,6 +47,7 @@ type Rule struct {
|
||||
OutputFile string
|
||||
Format string
|
||||
Log *log.Logger
|
||||
Roller *middleware.LogRoller
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestNewResponseRecorder(t *testing.T) {
|
||||
t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n")
|
||||
}
|
||||
if recordRequest.status != http.StatusOK {
|
||||
t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", recordRequest.status)
|
||||
t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", http.StatusOK, recordRequest.status)
|
||||
}
|
||||
}
|
||||
func TestWriteHeader(t *testing.T) {
|
||||
@@ -35,6 +35,6 @@ func TestWrite(t *testing.T) {
|
||||
t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size)
|
||||
}
|
||||
if w.Body.String() != responseTestString {
|
||||
t.Fatalf("Expected Response Body to be %s , but found %s\n", w.Body.String())
|
||||
t.Fatalf("Expected Response Body to be %s , but found %s\n", responseTestString, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func TestNewReplacer(t *testing.T) {
|
||||
|
||||
switch v := replaceValues.(type) {
|
||||
case replacer:
|
||||
|
||||
if v.replacements["{host}"] != "caddyserver.com" {
|
||||
t.Errorf("Expected host to be caddyserver.com")
|
||||
}
|
||||
@@ -36,3 +37,35 @@ func TestNewReplacer(t *testing.T) {
|
||||
t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
recordRequest := NewResponseRecorder(w)
|
||||
userJson := `{"username": "dennis"}`
|
||||
|
||||
reader := strings.NewReader(userJson) //Convert string to reader
|
||||
|
||||
request, err := http.NewRequest("POST", "http://caddyserver.com", reader) //Create request with JSON body
|
||||
if err != nil {
|
||||
t.Fatalf("Request Formation Failed \n")
|
||||
}
|
||||
replaceValues := NewReplacer(request, recordRequest, "")
|
||||
|
||||
switch v := replaceValues.(type) {
|
||||
case replacer:
|
||||
|
||||
if v.Replace("This host is {host}") != "This host is caddyserver.com" {
|
||||
t.Errorf("Expected host replacement failed")
|
||||
}
|
||||
if v.Replace("This request method is {method}") != "This request method is POST" {
|
||||
t.Errorf("Expected method replacement failed")
|
||||
}
|
||||
if v.Replace("The response status is {status}") != "The response status is 200" {
|
||||
t.Errorf("Expected status replacement failed")
|
||||
}
|
||||
|
||||
default:
|
||||
t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
type LogRoller struct {
|
||||
Filename string
|
||||
MaxSize int
|
||||
MaxAge int
|
||||
MaxBackups int
|
||||
LocalTime bool
|
||||
}
|
||||
|
||||
func (l LogRoller) GetLogWriter() io.Writer {
|
||||
return &lumberjack.Logger{
|
||||
Filename: l.Filename,
|
||||
MaxSize: l.MaxSize,
|
||||
MaxAge: l.MaxAge,
|
||||
MaxBackups: l.MaxBackups,
|
||||
LocalTime: l.LocalTime,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user