mirror of
https://github.com/openfaas/faas.git
synced 2025-06-26 08:43:24 +00:00
Vendoring with Glide and delete function handler
This commit is contained in:
115
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/access.go
generated
vendored
Normal file
115
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/access.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
// Package htpasswd provides a simple authentication scheme that checks for the
|
||||
// user credential hash in an htpasswd formatted file in a configuration-determined
|
||||
// location.
|
||||
//
|
||||
// This authentication method MUST be used under TLS, as simple token-replay attack is possible.
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/registry/auth"
|
||||
)
|
||||
|
||||
type accessController struct {
|
||||
realm string
|
||||
path string
|
||||
modtime time.Time
|
||||
mu sync.Mutex
|
||||
htpasswd *htpasswd
|
||||
}
|
||||
|
||||
var _ auth.AccessController = &accessController{}
|
||||
|
||||
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
|
||||
realm, present := options["realm"]
|
||||
if _, ok := realm.(string); !present || !ok {
|
||||
return nil, fmt.Errorf(`"realm" must be set for htpasswd access controller`)
|
||||
}
|
||||
|
||||
path, present := options["path"]
|
||||
if _, ok := path.(string); !present || !ok {
|
||||
return nil, fmt.Errorf(`"path" must be set for htpasswd access controller`)
|
||||
}
|
||||
|
||||
return &accessController{realm: realm.(string), path: path.(string)}, nil
|
||||
}
|
||||
|
||||
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
|
||||
req, err := context.GetRequest(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username, password, ok := req.BasicAuth()
|
||||
if !ok {
|
||||
return nil, &challenge{
|
||||
realm: ac.realm,
|
||||
err: auth.ErrInvalidCredential,
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically parsing the latest account list
|
||||
fstat, err := os.Stat(ac.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lastModified := fstat.ModTime()
|
||||
ac.mu.Lock()
|
||||
if ac.htpasswd == nil || !ac.modtime.Equal(lastModified) {
|
||||
ac.modtime = lastModified
|
||||
|
||||
f, err := os.Open(ac.path)
|
||||
if err != nil {
|
||||
ac.mu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h, err := newHTPasswd(f)
|
||||
if err != nil {
|
||||
ac.mu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
ac.htpasswd = h
|
||||
}
|
||||
localHTPasswd := ac.htpasswd
|
||||
ac.mu.Unlock()
|
||||
|
||||
if err := localHTPasswd.authenticateUser(username, password); err != nil {
|
||||
context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
|
||||
return nil, &challenge{
|
||||
realm: ac.realm,
|
||||
err: auth.ErrAuthenticationFailure,
|
||||
}
|
||||
}
|
||||
|
||||
return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
|
||||
}
|
||||
|
||||
// challenge implements the auth.Challenge interface.
|
||||
type challenge struct {
|
||||
realm string
|
||||
err error
|
||||
}
|
||||
|
||||
var _ auth.Challenge = challenge{}
|
||||
|
||||
// SetHeaders sets the basic challenge header on the response.
|
||||
func (ch challenge) SetHeaders(w http.ResponseWriter) {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm))
|
||||
}
|
||||
|
||||
func (ch challenge) Error() string {
|
||||
return fmt.Sprintf("basic authentication challenge for realm %q: %s", ch.realm, ch.err)
|
||||
}
|
||||
|
||||
func init() {
|
||||
auth.Register("htpasswd", auth.InitFunc(newAccessController))
|
||||
}
|
122
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/access_test.go
generated
vendored
Normal file
122
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/access_test.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/registry/auth"
|
||||
)
|
||||
|
||||
func TestBasicAccessController(t *testing.T) {
|
||||
testRealm := "The-Shire"
|
||||
testUsers := []string{"bilbo", "frodo", "MiShil", "DeokMan"}
|
||||
testPasswords := []string{"baggins", "baggins", "새주", "공주님"}
|
||||
testHtpasswdContent := `bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=
|
||||
frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W
|
||||
MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2
|
||||
DeokMan:공주님`
|
||||
|
||||
tempFile, err := ioutil.TempFile("", "htpasswd-test")
|
||||
if err != nil {
|
||||
t.Fatal("could not create temporary htpasswd file")
|
||||
}
|
||||
if _, err = tempFile.WriteString(testHtpasswdContent); err != nil {
|
||||
t.Fatal("could not write temporary htpasswd file")
|
||||
}
|
||||
|
||||
options := map[string]interface{}{
|
||||
"realm": testRealm,
|
||||
"path": tempFile.Name(),
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
accessController, err := newAccessController(options)
|
||||
if err != nil {
|
||||
t.Fatal("error creating access controller")
|
||||
}
|
||||
|
||||
tempFile.Close()
|
||||
|
||||
var userNumber = 0
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithRequest(ctx, r)
|
||||
authCtx, err := accessController.Authorized(ctx)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case auth.Challenge:
|
||||
err.SetHeaders(w)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
default:
|
||||
t.Fatalf("unexpected error authorizing request: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
userInfo, ok := authCtx.Value(auth.UserKey).(auth.UserInfo)
|
||||
if !ok {
|
||||
t.Fatal("basic accessController did not set auth.user context")
|
||||
}
|
||||
|
||||
if userInfo.Name != testUsers[userNumber] {
|
||||
t.Fatalf("expected user name %q, got %q", testUsers[userNumber], userInfo.Name)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
client := &http.Client{
|
||||
CheckRedirect: nil,
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", server.URL, nil)
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error during GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Request should not be authorized
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("unexpected non-fail response status: %v != %v", resp.StatusCode, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
nonbcrypt := map[string]struct{}{
|
||||
"bilbo": {},
|
||||
"DeokMan": {},
|
||||
}
|
||||
|
||||
for i := 0; i < len(testUsers); i++ {
|
||||
userNumber = i
|
||||
req, err := http.NewRequest("GET", server.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error allocating new request: %v", err)
|
||||
}
|
||||
|
||||
req.SetBasicAuth(testUsers[i], testPasswords[i])
|
||||
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error during GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if _, ok := nonbcrypt[testUsers[i]]; ok {
|
||||
// these are not allowed.
|
||||
// Request should be authorized
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("unexpected non-success response status: %v != %v for %s %s", resp.StatusCode, http.StatusUnauthorized, testUsers[i], testPasswords[i])
|
||||
}
|
||||
} else {
|
||||
// Request should be authorized
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("unexpected non-success response status: %v != %v for %s %s", resp.StatusCode, http.StatusNoContent, testUsers[i], testPasswords[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
82
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/htpasswd.go
generated
vendored
Normal file
82
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/htpasswd.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
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([]byte(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
|
||||
}
|
85
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/htpasswd_test.go
generated
vendored
Normal file
85
gateway/vendor/github.com/docker/distribution/registry/auth/htpasswd/htpasswd_test.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseHTPasswd(t *testing.T) {
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input string
|
||||
err error
|
||||
entries map[string][]byte
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
input: `
|
||||
# This is a comment in a basic example.
|
||||
bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=
|
||||
frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W
|
||||
MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2
|
||||
DeokMan:공주님
|
||||
`,
|
||||
entries: map[string][]byte{
|
||||
"bilbo": []byte("{SHA}5siv5c0SHx681xU6GiSx9ZQryqs="),
|
||||
"frodo": []byte("$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W"),
|
||||
"MiShil": []byte("$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2"),
|
||||
"DeokMan": []byte("공주님"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ensures comments are filtered",
|
||||
input: `
|
||||
# asdf:asdf
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "ensure midline hash is not comment",
|
||||
input: `
|
||||
asdf:as#df
|
||||
`,
|
||||
entries: map[string][]byte{
|
||||
"asdf": []byte("as#df"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ensure midline hash is not comment",
|
||||
input: `
|
||||
# A valid comment
|
||||
valid:entry
|
||||
asdf
|
||||
`,
|
||||
err: fmt.Errorf(`htpasswd: invalid entry at line 4: "asdf"`),
|
||||
},
|
||||
} {
|
||||
|
||||
entries, err := parseHTPasswd(strings.NewReader(tc.input))
|
||||
if err != tc.err {
|
||||
if tc.err == nil {
|
||||
t.Fatalf("%s: unexpected error: %v", tc.desc, err)
|
||||
} else {
|
||||
if err.Error() != tc.err.Error() { // use string equality here.
|
||||
t.Fatalf("%s: expected error not returned: %v != %v", tc.desc, err, tc.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tc.err != nil {
|
||||
continue // don't test output
|
||||
}
|
||||
|
||||
// allow empty and nil to be equal
|
||||
if tc.entries == nil {
|
||||
tc.entries = map[string][]byte{}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(entries, tc.entries) {
|
||||
t.Fatalf("%s: entries not parsed correctly: %v != %v", tc.desc, entries, tc.entries)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user