diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 67c32c1ae..0a73f5cad 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -237,7 +237,7 @@ The `vendor` directory is entirely managed by the `dep` tool.
To add a new dependency
- dep ensure github.com/pkg/errors
+ dep ensure -add github.com/pkg/errors
You can add constraints on that package (see the `dep` documentation),
but don't unless you really need to.
diff --git a/Gopkg.lock b/Gopkg.lock
index a327676b7..282739bd8 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -52,6 +52,12 @@
packages = ["."]
revision = "cf42b1e486f0b025942a768a9ad59c9939d6ca40"
+[[projects]]
+ name = "github.com/abbot/go-http-auth"
+ packages = ["."]
+ revision = "0ddd408d5d60ea76e320503cc7dd091992dee608"
+ version = "v0.4.0"
+
[[projects]]
branch = "master"
name = "github.com/aws/aws-sdk-go"
@@ -321,6 +327,8 @@
branch = "master"
name = "golang.org/x/crypto"
packages = [
+ "bcrypt",
+ "blowfish",
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
@@ -427,6 +435,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "ad6c96ca62ee6a305b6083c53f2f4939d8ae22ee6163ad84b1f09d8fcbcd1ff7"
+ inputs-digest = "bbb981a57fa3540cbfdf3f4255a8eb6b7b9980af2259cffab2f0eddcc3818bcc"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/vendor/github.com/abbot/go-http-auth/.gitignore b/vendor/github.com/abbot/go-http-auth/.gitignore
new file mode 100644
index 000000000..112ea3951
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/.gitignore
@@ -0,0 +1,5 @@
+*~
+*.a
+*.6
+*.out
+_testmain.go
diff --git a/vendor/github.com/abbot/go-http-auth/LICENSE b/vendor/github.com/abbot/go-http-auth/LICENSE
new file mode 100644
index 000000000..e454a5258
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/LICENSE
@@ -0,0 +1,178 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/vendor/github.com/abbot/go-http-auth/Makefile b/vendor/github.com/abbot/go-http-auth/Makefile
new file mode 100644
index 000000000..25f208da0
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/Makefile
@@ -0,0 +1,12 @@
+include $(GOROOT)/src/Make.inc
+
+TARG=auth_digest
+GOFILES=\
+ auth.go\
+ digest.go\
+ basic.go\
+ misc.go\
+ md5crypt.go\
+ users.go\
+
+include $(GOROOT)/src/Make.pkg
diff --git a/vendor/github.com/abbot/go-http-auth/README.md b/vendor/github.com/abbot/go-http-auth/README.md
new file mode 100644
index 000000000..73ae98522
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/README.md
@@ -0,0 +1,71 @@
+HTTP Authentication implementation in Go
+========================================
+
+This is an implementation of HTTP Basic and HTTP Digest authentication
+in Go language. It is designed as a simple wrapper for
+http.RequestHandler functions.
+
+Features
+--------
+
+ * Supports HTTP Basic and HTTP Digest authentication.
+ * Supports htpasswd and htdigest formatted files.
+ * Automatic reloading of password files.
+ * Pluggable interface for user/password storage.
+ * Supports MD5, SHA1 and BCrypt for Basic authentication password storage.
+ * Configurable Digest nonce cache size with expiration.
+ * Wrapper for legacy http handlers (http.HandlerFunc interface)
+
+Example usage
+-------------
+
+This is a complete working example for Basic auth:
+
+ package main
+
+ import (
+ "fmt"
+ "net/http"
+
+ auth "github.com/abbot/go-http-auth"
+ )
+
+ func Secret(user, realm string) string {
+ if user == "john" {
+ // password is "hello"
+ return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
+ }
+ return ""
+ }
+
+ func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
+ fmt.Fprintf(w, "
Hello, %s!
", r.Username)
+ }
+
+ func main() {
+ authenticator := auth.NewBasicAuthenticator("example.com", Secret)
+ http.HandleFunc("/", authenticator.Wrap(handle))
+ http.ListenAndServe(":8080", nil)
+ }
+
+See more examples in the "examples" directory.
+
+Legal
+-----
+
+This module is developed under Apache 2.0 license, and can be used for
+open and proprietary projects.
+
+Copyright 2012-2013 Lev Shamardin
+
+Licensed under the Apache License, Version 2.0 (the "License"); you
+may not use this file or any other part of this project except in
+compliance with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the License for the specific language governing
+permissions and limitations under the License.
diff --git a/vendor/github.com/abbot/go-http-auth/auth.go b/vendor/github.com/abbot/go-http-auth/auth.go
new file mode 100644
index 000000000..05ded165e
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/auth.go
@@ -0,0 +1,109 @@
+// Package auth is an implementation of HTTP Basic and HTTP Digest authentication.
+package auth
+
+import (
+ "net/http"
+
+ "golang.org/x/net/context"
+)
+
+/*
+ Request handlers must take AuthenticatedRequest instead of http.Request
+*/
+type AuthenticatedRequest struct {
+ http.Request
+ /*
+ Authenticated user name. Current API implies that Username is
+ never empty, which means that authentication is always done
+ before calling the request handler.
+ */
+ Username string
+}
+
+/*
+ AuthenticatedHandlerFunc is like http.HandlerFunc, but takes
+ AuthenticatedRequest instead of http.Request
+*/
+type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest)
+
+/*
+ Authenticator wraps an AuthenticatedHandlerFunc with
+ authentication-checking code.
+
+ Typical Authenticator usage is something like:
+
+ authenticator := SomeAuthenticator(...)
+ http.HandleFunc("/", authenticator(my_handler))
+
+ Authenticator wrapper checks the user authentication and calls the
+ wrapped function only after authentication has succeeded. Otherwise,
+ it returns a handler which initiates the authentication procedure.
+*/
+type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc
+
+// Info contains authentication information for the request.
+type Info struct {
+ // Authenticated is set to true when request was authenticated
+ // successfully, i.e. username and password passed in request did
+ // pass the check.
+ Authenticated bool
+
+ // Username contains a user name passed in the request when
+ // Authenticated is true. It's value is undefined if Authenticated
+ // is false.
+ Username string
+
+ // ResponseHeaders contains extra headers that must be set by server
+ // when sending back HTTP response.
+ ResponseHeaders http.Header
+}
+
+// UpdateHeaders updates headers with this Info's ResponseHeaders. It is
+// safe to call this function on nil Info.
+func (i *Info) UpdateHeaders(headers http.Header) {
+ if i == nil {
+ return
+ }
+ for k, values := range i.ResponseHeaders {
+ for _, v := range values {
+ headers.Add(k, v)
+ }
+ }
+}
+
+type key int // used for context keys
+
+var infoKey key = 0
+
+type AuthenticatorInterface interface {
+ // NewContext returns a new context carrying authentication
+ // information extracted from the request.
+ NewContext(ctx context.Context, r *http.Request) context.Context
+
+ // Wrap returns an http.HandlerFunc which wraps
+ // AuthenticatedHandlerFunc with this authenticator's
+ // authentication checks.
+ Wrap(AuthenticatedHandlerFunc) http.HandlerFunc
+}
+
+// FromContext returns authentication information from the context or
+// nil if no such information present.
+func FromContext(ctx context.Context) *Info {
+ info, ok := ctx.Value(infoKey).(*Info)
+ if !ok {
+ return nil
+ }
+ return info
+}
+
+// AuthUsernameHeader is the header set by JustCheck functions. It
+// contains an authenticated username (if authentication was
+// successful).
+const AuthUsernameHeader = "X-Authenticated-Username"
+
+func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc {
+ return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
+ ar.Header.Set(AuthUsernameHeader, ar.Username)
+ wrapped(w, &ar.Request)
+ })
+}
diff --git a/vendor/github.com/abbot/go-http-auth/basic.go b/vendor/github.com/abbot/go-http-auth/basic.go
new file mode 100644
index 000000000..b03dd5823
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/basic.go
@@ -0,0 +1,163 @@
+package auth
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "crypto/subtle"
+ "encoding/base64"
+ "errors"
+ "net/http"
+ "strings"
+
+ "golang.org/x/crypto/bcrypt"
+ "golang.org/x/net/context"
+)
+
+type compareFunc func(hashedPassword, password []byte) error
+
+var (
+ errMismatchedHashAndPassword = errors.New("mismatched hash and password")
+
+ compareFuncs = []struct {
+ prefix string
+ compare compareFunc
+ }{
+ {"", compareMD5HashAndPassword}, // default compareFunc
+ {"{SHA}", compareShaHashAndPassword},
+ // Bcrypt is complicated. According to crypt(3) from
+ // crypt_blowfish version 1.3 (fetched from
+ // http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz), there
+ // are three different has prefixes: "$2a$", used by versions up
+ // to 1.0.4, and "$2x$" and "$2y$", used in all later
+ // versions. "$2a$" has a known bug, "$2x$" was added as a
+ // migration path for systems with "$2a$" prefix and still has a
+ // bug, and only "$2y$" should be used by modern systems. The bug
+ // has something to do with handling of 8-bit characters. Since
+ // both "$2a$" and "$2x$" are deprecated, we are handling them the
+ // same way as "$2y$", which will yield correct results for 7-bit
+ // character passwords, but is wrong for 8-bit character
+ // passwords. You have to upgrade to "$2y$" if you want sant 8-bit
+ // character password support with bcrypt. To add to the mess,
+ // OpenBSD 5.5. introduced "$2b$" prefix, which behaves exactly
+ // like "$2y$" according to the same source.
+ {"$2a$", bcrypt.CompareHashAndPassword},
+ {"$2b$", bcrypt.CompareHashAndPassword},
+ {"$2x$", bcrypt.CompareHashAndPassword},
+ {"$2y$", bcrypt.CompareHashAndPassword},
+ }
+)
+
+type BasicAuth struct {
+ Realm string
+ Secrets SecretProvider
+ // Headers used by authenticator. Set to ProxyHeaders to use with
+ // proxy server. When nil, NormalHeaders are used.
+ Headers *Headers
+}
+
+// check that BasicAuth implements AuthenticatorInterface
+var _ = (AuthenticatorInterface)((*BasicAuth)(nil))
+
+/*
+ Checks the username/password combination from the request. Returns
+ either an empty string (authentication failed) or the name of the
+ authenticated user.
+
+ Supports MD5 and SHA1 password entries
+*/
+func (a *BasicAuth) CheckAuth(r *http.Request) string {
+ s := strings.SplitN(r.Header.Get(a.Headers.V().Authorization), " ", 2)
+ if len(s) != 2 || s[0] != "Basic" {
+ return ""
+ }
+
+ b, err := base64.StdEncoding.DecodeString(s[1])
+ if err != nil {
+ return ""
+ }
+ pair := strings.SplitN(string(b), ":", 2)
+ if len(pair) != 2 {
+ return ""
+ }
+ user, password := pair[0], pair[1]
+ secret := a.Secrets(user, a.Realm)
+ if secret == "" {
+ return ""
+ }
+ compare := compareFuncs[0].compare
+ for _, cmp := range compareFuncs[1:] {
+ if strings.HasPrefix(secret, cmp.prefix) {
+ compare = cmp.compare
+ break
+ }
+ }
+ if compare([]byte(secret), []byte(password)) != nil {
+ return ""
+ }
+ return pair[0]
+}
+
+func compareShaHashAndPassword(hashedPassword, password []byte) error {
+ d := sha1.New()
+ d.Write(password)
+ if subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 {
+ return errMismatchedHashAndPassword
+ }
+ return nil
+}
+
+func compareMD5HashAndPassword(hashedPassword, password []byte) error {
+ parts := bytes.SplitN(hashedPassword, []byte("$"), 4)
+ if len(parts) != 4 {
+ return errMismatchedHashAndPassword
+ }
+ magic := []byte("$" + string(parts[1]) + "$")
+ salt := parts[2]
+ if subtle.ConstantTimeCompare(hashedPassword, MD5Crypt(password, salt, magic)) != 1 {
+ return errMismatchedHashAndPassword
+ }
+ return nil
+}
+
+/*
+ http.Handler for BasicAuth which initiates the authentication process
+ (or requires reauthentication).
+*/
+func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set(contentType, a.Headers.V().UnauthContentType)
+ w.Header().Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`)
+ w.WriteHeader(a.Headers.V().UnauthCode)
+ w.Write([]byte(a.Headers.V().UnauthResponse))
+}
+
+/*
+ BasicAuthenticator returns a function, which wraps an
+ AuthenticatedHandlerFunc converting it to http.HandlerFunc. This
+ wrapper function checks the authentication and either sends back
+ required authentication headers, or calls the wrapped function with
+ authenticated username in the AuthenticatedRequest.
+*/
+func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if username := a.CheckAuth(r); username == "" {
+ a.RequireAuth(w, r)
+ } else {
+ ar := &AuthenticatedRequest{Request: *r, Username: username}
+ wrapped(w, ar)
+ }
+ }
+}
+
+// NewContext returns a context carrying authentication information for the request.
+func (a *BasicAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
+ info := &Info{Username: a.CheckAuth(r), ResponseHeaders: make(http.Header)}
+ info.Authenticated = (info.Username != "")
+ if !info.Authenticated {
+ info.ResponseHeaders.Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`)
+ }
+ return context.WithValue(ctx, infoKey, info)
+}
+
+func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth {
+ return &BasicAuth{Realm: realm, Secrets: secrets}
+}
diff --git a/vendor/github.com/abbot/go-http-auth/basic_test.go b/vendor/github.com/abbot/go-http-auth/basic_test.go
new file mode 100644
index 000000000..99f44fc32
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/basic_test.go
@@ -0,0 +1,40 @@
+package auth
+
+import (
+ "encoding/base64"
+ "net/http"
+ "testing"
+)
+
+func TestAuthBasic(t *testing.T) {
+ secrets := HtpasswdFileProvider("test.htpasswd")
+ a := &BasicAuth{Realm: "example.com", Secrets: secrets}
+ r := &http.Request{}
+ r.Method = "GET"
+ if a.CheckAuth(r) != "" {
+ t.Fatal("CheckAuth passed on empty headers")
+ }
+ r.Header = http.Header(make(map[string][]string))
+ r.Header.Set("Authorization", "Digest blabla ololo")
+ if a.CheckAuth(r) != "" {
+ t.Fatal("CheckAuth passed on bad headers")
+ }
+ r.Header.Set("Authorization", "Basic !@#")
+ if a.CheckAuth(r) != "" {
+ t.Fatal("CheckAuth passed on bad base64 data")
+ }
+
+ data := [][]string{
+ {"test", "hello"},
+ {"test2", "hello2"},
+ {"test3", "hello3"},
+ {"test16", "topsecret"},
+ }
+ for _, tc := range data {
+ auth := base64.StdEncoding.EncodeToString([]byte(tc[0] + ":" + tc[1]))
+ r.Header.Set("Authorization", "Basic "+auth)
+ if a.CheckAuth(r) != tc[0] {
+ t.Fatalf("CheckAuth failed for user '%s'", tc[0])
+ }
+ }
+}
diff --git a/vendor/github.com/abbot/go-http-auth/digest.go b/vendor/github.com/abbot/go-http-auth/digest.go
new file mode 100644
index 000000000..21b09334c
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/digest.go
@@ -0,0 +1,274 @@
+package auth
+
+import (
+ "crypto/subtle"
+ "fmt"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/net/context"
+)
+
+type digest_client struct {
+ nc uint64
+ last_seen int64
+}
+
+type DigestAuth struct {
+ Realm string
+ Opaque string
+ Secrets SecretProvider
+ PlainTextSecrets bool
+ IgnoreNonceCount bool
+ // Headers used by authenticator. Set to ProxyHeaders to use with
+ // proxy server. When nil, NormalHeaders are used.
+ Headers *Headers
+
+ /*
+ Approximate size of Client's Cache. When actual number of
+ tracked client nonces exceeds
+ ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2
+ older entries are purged.
+ */
+ ClientCacheSize int
+ ClientCacheTolerance int
+
+ clients map[string]*digest_client
+ mutex sync.Mutex
+}
+
+// check that DigestAuth implements AuthenticatorInterface
+var _ = (AuthenticatorInterface)((*DigestAuth)(nil))
+
+type digest_cache_entry struct {
+ nonce string
+ last_seen int64
+}
+
+type digest_cache []digest_cache_entry
+
+func (c digest_cache) Less(i, j int) bool {
+ return c[i].last_seen < c[j].last_seen
+}
+
+func (c digest_cache) Len() int {
+ return len(c)
+}
+
+func (c digest_cache) Swap(i, j int) {
+ c[i], c[j] = c[j], c[i]
+}
+
+/*
+ Remove count oldest entries from DigestAuth.clients
+*/
+func (a *DigestAuth) Purge(count int) {
+ entries := make([]digest_cache_entry, 0, len(a.clients))
+ for nonce, client := range a.clients {
+ entries = append(entries, digest_cache_entry{nonce, client.last_seen})
+ }
+ cache := digest_cache(entries)
+ sort.Sort(cache)
+ for _, client := range cache[:count] {
+ delete(a.clients, client.nonce)
+ }
+}
+
+/*
+ http.Handler for DigestAuth which initiates the authentication process
+ (or requires reauthentication).
+*/
+func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
+ if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
+ a.Purge(a.ClientCacheTolerance * 2)
+ }
+ nonce := RandomKey()
+ a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
+ w.Header().Set(contentType, a.Headers.V().UnauthContentType)
+ w.Header().Set(a.Headers.V().Authenticate,
+ fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
+ a.Realm, nonce, a.Opaque))
+ w.WriteHeader(a.Headers.V().UnauthCode)
+ w.Write([]byte(a.Headers.V().UnauthResponse))
+}
+
+/*
+ Parse Authorization header from the http.Request. Returns a map of
+ auth parameters or nil if the header is not a valid parsable Digest
+ auth header.
+*/
+func DigestAuthParams(authorization string) map[string]string {
+ s := strings.SplitN(authorization, " ", 2)
+ if len(s) != 2 || s[0] != "Digest" {
+ return nil
+ }
+
+ return ParsePairs(s[1])
+}
+
+/*
+ Check if request contains valid authentication data. Returns a pair
+ of username, authinfo where username is the name of the authenticated
+ user or an empty string and authinfo is the contents for the optional
+ Authentication-Info response header.
+*/
+func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
+ da.mutex.Lock()
+ defer da.mutex.Unlock()
+ username = ""
+ authinfo = nil
+ auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
+ if auth == nil {
+ return "", nil
+ }
+ // RFC2617 Section 3.2.1 specifies that unset value of algorithm in
+ // WWW-Authenticate Response header should be treated as
+ // "MD5". According to section 3.2.2 the "algorithm" value in
+ // subsequent Request Authorization header must be set to whatever
+ // was supplied in the WWW-Authenticate Response header. This
+ // implementation always returns an algorithm in WWW-Authenticate
+ // header, however there seems to be broken clients in the wild
+ // which do not set the algorithm. Assume the unset algorithm in
+ // Authorization header to be equal to MD5.
+ if _, ok := auth["algorithm"]; !ok {
+ auth["algorithm"] = "MD5"
+ }
+ if da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" {
+ return "", nil
+ }
+
+ // Check if the requested URI matches auth header
+ if r.RequestURI != auth["uri"] {
+ // We allow auth["uri"] to be a full path prefix of request-uri
+ // for some reason lost in history, which is probably wrong, but
+ // used to be like that for quite some time
+ // (https://tools.ietf.org/html/rfc2617#section-3.2.2 explicitly
+ // says that auth["uri"] is the request-uri).
+ //
+ // TODO: make an option to allow only strict checking.
+ switch u, err := url.Parse(auth["uri"]); {
+ case err != nil:
+ return "", nil
+ case r.URL == nil:
+ return "", nil
+ case len(u.Path) > len(r.URL.Path):
+ return "", nil
+ case !strings.HasPrefix(r.URL.Path, u.Path):
+ return "", nil
+ }
+ }
+
+ HA1 := da.Secrets(auth["username"], da.Realm)
+ if da.PlainTextSecrets {
+ HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1)
+ }
+ HA2 := H(r.Method + ":" + auth["uri"])
+ KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":"))
+
+ if subtle.ConstantTimeCompare([]byte(KD), []byte(auth["response"])) != 1 {
+ return "", nil
+ }
+
+ // At this point crypto checks are completed and validated.
+ // Now check if the session is valid.
+
+ nc, err := strconv.ParseUint(auth["nc"], 16, 64)
+ if err != nil {
+ return "", nil
+ }
+
+ if client, ok := da.clients[auth["nonce"]]; !ok {
+ return "", nil
+ } else {
+ if client.nc != 0 && client.nc >= nc && !da.IgnoreNonceCount {
+ return "", nil
+ }
+ client.nc = nc
+ client.last_seen = time.Now().UnixNano()
+ }
+
+ resp_HA2 := H(":" + auth["uri"])
+ rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":"))
+
+ info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"])
+ return auth["username"], &info
+}
+
+/*
+ Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth
+*/
+const DefaultClientCacheSize = 1000
+const DefaultClientCacheTolerance = 100
+
+/*
+ Wrap returns an Authenticator which uses HTTP Digest
+ authentication. Arguments:
+
+ realm: The authentication realm.
+
+ secrets: SecretProvider which must return HA1 digests for the same
+ realm as above.
+*/
+func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if username, authinfo := a.CheckAuth(r); username == "" {
+ a.RequireAuth(w, r)
+ } else {
+ ar := &AuthenticatedRequest{Request: *r, Username: username}
+ if authinfo != nil {
+ w.Header().Set(a.Headers.V().AuthInfo, *authinfo)
+ }
+ wrapped(w, ar)
+ }
+ }
+}
+
+/*
+ JustCheck returns function which converts an http.HandlerFunc into a
+ http.HandlerFunc which requires authentication. Username is passed as
+ an extra X-Authenticated-Username header.
+*/
+func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
+ return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
+ ar.Header.Set(AuthUsernameHeader, ar.Username)
+ wrapped(w, &ar.Request)
+ })
+}
+
+// NewContext returns a context carrying authentication information for the request.
+func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
+ username, authinfo := a.CheckAuth(r)
+ info := &Info{Username: username, ResponseHeaders: make(http.Header)}
+ if username != "" {
+ info.Authenticated = true
+ info.ResponseHeaders.Set(a.Headers.V().AuthInfo, *authinfo)
+ } else {
+ // return back digest WWW-Authenticate header
+ if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
+ a.Purge(a.ClientCacheTolerance * 2)
+ }
+ nonce := RandomKey()
+ a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
+ info.ResponseHeaders.Set(a.Headers.V().Authenticate,
+ fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
+ a.Realm, nonce, a.Opaque))
+ }
+ return context.WithValue(ctx, infoKey, info)
+}
+
+func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth {
+ da := &DigestAuth{
+ Opaque: RandomKey(),
+ Realm: realm,
+ Secrets: secrets,
+ PlainTextSecrets: false,
+ ClientCacheSize: DefaultClientCacheSize,
+ ClientCacheTolerance: DefaultClientCacheTolerance,
+ clients: map[string]*digest_client{}}
+ return da
+}
diff --git a/vendor/github.com/abbot/go-http-auth/digest_test.go b/vendor/github.com/abbot/go-http-auth/digest_test.go
new file mode 100644
index 000000000..ffbbd6b4a
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/digest_test.go
@@ -0,0 +1,76 @@
+package auth
+
+import (
+ "net/http"
+ "net/url"
+ "testing"
+ "time"
+)
+
+func TestAuthDigest(t *testing.T) {
+ secrets := HtdigestFileProvider("test.htdigest")
+ da := &DigestAuth{Opaque: "U7H+ier3Ae8Skd/g",
+ Realm: "example.com",
+ Secrets: secrets,
+ clients: map[string]*digest_client{}}
+ r := &http.Request{}
+ r.Method = "GET"
+ if u, _ := da.CheckAuth(r); u != "" {
+ t.Fatal("non-empty auth for empty request header")
+ }
+ r.Header = http.Header(make(map[string][]string))
+ r.Header.Set("Authorization", "Digest blabla")
+ if u, _ := da.CheckAuth(r); u != "" {
+ t.Fatal("non-empty auth for bad request header")
+ }
+ r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="Vb9BP/h81n3GpTTB", uri="/t2", cnonce="NjE4MTM2", nc=00000001, qop="auth", response="ffc357c4eba74773c8687e0bc724c9a3", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`)
+ if u, _ := da.CheckAuth(r); u != "" {
+ t.Fatal("non-empty auth for unknown client")
+ }
+
+ r.URL, _ = url.Parse("/t2")
+ da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
+ if u, _ := da.CheckAuth(r); u != "test" {
+ t.Fatal("empty auth for legitimate client")
+ }
+
+ // our nc is now 0, client nc is 1
+ if u, _ := da.CheckAuth(r); u != "" {
+ t.Fatal("non-empty auth for outdated nc")
+ }
+
+ // try again with nc checking off
+ da.IgnoreNonceCount = true
+ if u, _ := da.CheckAuth(r); u != "test" {
+ t.Fatal("empty auth for outdated nc even though nc checking is off")
+ }
+ da.IgnoreNonceCount = false
+
+ r.URL, _ = url.Parse("/")
+ da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
+ if u, _ := da.CheckAuth(r); u != "" {
+ t.Fatal("non-empty auth for bad request path")
+ }
+
+ r.URL, _ = url.Parse("/t3")
+ da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
+ if u, _ := da.CheckAuth(r); u != "" {
+ t.Fatal("non-empty auth for bad request path")
+ }
+
+ da.clients["+RbVXSbIoa1SaJk1"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
+ r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="+RbVXSbIoa1SaJk1", uri="/", cnonce="NjE4NDkw", nc=00000001, qop="auth", response="c08918024d7faaabd5424654c4e3ad1c", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`)
+ if u, _ := da.CheckAuth(r); u != "test" {
+ t.Fatal("empty auth for valid request in subpath")
+ }
+}
+
+func TestDigestAuthParams(t *testing.T) {
+ const authorization = `Digest username="test", realm="", nonce="FRPnGdb8lvM1UHhi", uri="/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro", algorithm=MD5, response="fdcdd78e5b306ffed343d0ec3967f2e5", opaque="lEgVjogmIar2fg/t", qop=auth, nc=00000001, cnonce="e76b05db27a3b323"`
+
+ params := DigestAuthParams(authorization)
+ want := "/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro"
+ if params["uri"] != want {
+ t.Fatalf("failed to parse uri with embedded commas, got %q want %q", params["uri"], want)
+ }
+}
diff --git a/vendor/github.com/abbot/go-http-auth/examples/basic.go b/vendor/github.com/abbot/go-http-auth/examples/basic.go
new file mode 100644
index 000000000..49d3989da
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/examples/basic.go
@@ -0,0 +1,35 @@
+// +build ignore
+
+/*
+ Example application using Basic auth
+
+ Build with:
+
+ go build basic.go
+*/
+
+package main
+
+import (
+ auth ".."
+ "fmt"
+ "net/http"
+)
+
+func Secret(user, realm string) string {
+ if user == "john" {
+ // password is "hello"
+ return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
+ }
+ return ""
+}
+
+func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
+ fmt.Fprintf(w, "Hello, %s!
", r.Username)
+}
+
+func main() {
+ authenticator := auth.NewBasicAuthenticator("example.com", Secret)
+ http.HandleFunc("/", authenticator.Wrap(handle))
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/vendor/github.com/abbot/go-http-auth/examples/context.go b/vendor/github.com/abbot/go-http-auth/examples/context.go
new file mode 100644
index 000000000..b3ca8c6b2
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/examples/context.go
@@ -0,0 +1,60 @@
+// +build ignore
+
+/*
+ Example application using NewContext/FromContext
+
+ Build with:
+
+ go build context.go
+*/
+
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ auth ".."
+ "golang.org/x/net/context"
+)
+
+func Secret(user, realm string) string {
+ if user == "john" {
+ // password is "hello"
+ return "b98e16cbc3d01734b264adba7baa3bf9"
+ }
+ return ""
+}
+
+type ContextHandler interface {
+ ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request)
+}
+
+type ContextHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request)
+
+func (f ContextHandlerFunc) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ f(ctx, w, r)
+}
+
+func handle(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ authInfo := auth.FromContext(ctx)
+ authInfo.UpdateHeaders(w.Header())
+ if authInfo == nil || !authInfo.Authenticated {
+ http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+ return
+ }
+ fmt.Fprintf(w, "Hello, %s!
", authInfo.Username)
+}
+
+func authenticatedHandler(a auth.AuthenticatorInterface, h ContextHandler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := a.NewContext(context.Background(), r)
+ h.ServeHTTP(ctx, w, r)
+ })
+}
+
+func main() {
+ authenticator := auth.NewDigestAuthenticator("example.com", Secret)
+ http.Handle("/", authenticatedHandler(authenticator, ContextHandlerFunc(handle)))
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/vendor/github.com/abbot/go-http-auth/examples/digest.go b/vendor/github.com/abbot/go-http-auth/examples/digest.go
new file mode 100644
index 000000000..385989338
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/examples/digest.go
@@ -0,0 +1,35 @@
+// +build ignore
+
+/*
+ Example application using Digest auth
+
+ Build with:
+
+ go build digest.go
+*/
+
+package main
+
+import (
+ auth ".."
+ "fmt"
+ "net/http"
+)
+
+func Secret(user, realm string) string {
+ if user == "john" {
+ // password is "hello"
+ return "b98e16cbc3d01734b264adba7baa3bf9"
+ }
+ return ""
+}
+
+func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
+ fmt.Fprintf(w, "Hello, %s!
", r.Username)
+}
+
+func main() {
+ authenticator := auth.NewDigestAuthenticator("example.com", Secret)
+ http.HandleFunc("/", authenticator.Wrap(handle))
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/vendor/github.com/abbot/go-http-auth/examples/wrapped.go b/vendor/github.com/abbot/go-http-auth/examples/wrapped.go
new file mode 100644
index 000000000..aa95ec38f
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/examples/wrapped.go
@@ -0,0 +1,36 @@
+// +build ignore
+
+/*
+ Example demonstrating how to wrap an application which is unaware of
+ authenticated requests with a "pass-through" authentication
+
+ Build with:
+
+ go build wrapped.go
+*/
+
+package main
+
+import (
+ auth ".."
+ "fmt"
+ "net/http"
+)
+
+func Secret(user, realm string) string {
+ if user == "john" {
+ // password is "hello"
+ return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
+ }
+ return ""
+}
+
+func regular_handler(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "This application is unaware of authentication
")
+}
+
+func main() {
+ authenticator := auth.NewBasicAuthenticator("example.com", Secret)
+ http.HandleFunc("/", auth.JustCheck(authenticator, regular_handler))
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/vendor/github.com/abbot/go-http-auth/md5crypt.go b/vendor/github.com/abbot/go-http-auth/md5crypt.go
new file mode 100644
index 000000000..a7a031c41
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/md5crypt.go
@@ -0,0 +1,92 @@
+package auth
+
+import "crypto/md5"
+import "strings"
+
+const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+
+var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11}
+
+type MD5Entry struct {
+ Magic, Salt, Hash []byte
+}
+
+func NewMD5Entry(e string) *MD5Entry {
+ parts := strings.SplitN(e, "$", 4)
+ if len(parts) != 4 {
+ return nil
+ }
+ return &MD5Entry{
+ Magic: []byte("$" + parts[1] + "$"),
+ Salt: []byte(parts[2]),
+ Hash: []byte(parts[3]),
+ }
+}
+
+/*
+ MD5 password crypt implementation
+*/
+func MD5Crypt(password, salt, magic []byte) []byte {
+ d := md5.New()
+
+ d.Write(password)
+ d.Write(magic)
+ d.Write(salt)
+
+ d2 := md5.New()
+ d2.Write(password)
+ d2.Write(salt)
+ d2.Write(password)
+
+ for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
+ d.Write([]byte{mixin[i%16]})
+ }
+
+ for i := len(password); i != 0; i >>= 1 {
+ if i&1 == 0 {
+ d.Write([]byte{password[0]})
+ } else {
+ d.Write([]byte{0})
+ }
+ }
+
+ final := d.Sum(nil)
+
+ for i := 0; i < 1000; i++ {
+ d2 := md5.New()
+ if i&1 == 0 {
+ d2.Write(final)
+ } else {
+ d2.Write(password)
+ }
+
+ if i%3 != 0 {
+ d2.Write(salt)
+ }
+
+ if i%7 != 0 {
+ d2.Write(password)
+ }
+
+ if i&1 == 0 {
+ d2.Write(password)
+ } else {
+ d2.Write(final)
+ }
+ final = d2.Sum(nil)
+ }
+
+ result := make([]byte, 0, 22)
+ v := uint(0)
+ bits := uint(0)
+ for _, i := range md5_crypt_swaps {
+ v |= (uint(final[i]) << bits)
+ for bits = bits + 8; bits > 6; bits -= 6 {
+ result = append(result, itoa64[v&0x3f])
+ v >>= 6
+ }
+ }
+ result = append(result, itoa64[v&0x3f])
+
+ return append(append(append(magic, salt...), '$'), result...)
+}
diff --git a/vendor/github.com/abbot/go-http-auth/md5crypt_test.go b/vendor/github.com/abbot/go-http-auth/md5crypt_test.go
new file mode 100644
index 000000000..2fc9bc6dc
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/md5crypt_test.go
@@ -0,0 +1,19 @@
+package auth
+
+import "testing"
+
+func Test_MD5Crypt(t *testing.T) {
+ test_cases := [][]string{
+ {"apache", "$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1"},
+ {"pass", "$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90"},
+ {"topsecret", "$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0"},
+ }
+ for _, tc := range test_cases {
+ e := NewMD5Entry(tc[1])
+ result := MD5Crypt([]byte(tc[0]), e.Salt, e.Magic)
+ if string(result) != tc[1] {
+ t.Fatalf("MD5Crypt returned '%s' instead of '%s'", string(result), tc[1])
+ }
+ t.Logf("MD5Crypt: '%s' (%s%s$) -> %s", tc[0], e.Magic, e.Salt, result)
+ }
+}
diff --git a/vendor/github.com/abbot/go-http-auth/misc.go b/vendor/github.com/abbot/go-http-auth/misc.go
new file mode 100644
index 000000000..4536ce673
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/misc.go
@@ -0,0 +1,141 @@
+package auth
+
+import (
+ "bytes"
+ "crypto/md5"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "strings"
+)
+
+// RandomKey returns a random 16-byte base64 alphabet string
+func RandomKey() string {
+ k := make([]byte, 12)
+ for bytes := 0; bytes < len(k); {
+ n, err := rand.Read(k[bytes:])
+ if err != nil {
+ panic("rand.Read() failed")
+ }
+ bytes += n
+ }
+ return base64.StdEncoding.EncodeToString(k)
+}
+
+// H function for MD5 algorithm (returns a lower-case hex MD5 digest)
+func H(data string) string {
+ digest := md5.New()
+ digest.Write([]byte(data))
+ return fmt.Sprintf("%x", digest.Sum(nil))
+}
+
+// ParseList parses a comma-separated list of values as described by
+// RFC 2068 and returns list elements.
+//
+// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go
+// which was ported from urllib2.parse_http_list, from the Python
+// standard library.
+func ParseList(value string) []string {
+ var list []string
+ var escape, quote bool
+ b := new(bytes.Buffer)
+ for _, r := range value {
+ switch {
+ case escape:
+ b.WriteRune(r)
+ escape = false
+ case quote:
+ if r == '\\' {
+ escape = true
+ } else {
+ if r == '"' {
+ quote = false
+ }
+ b.WriteRune(r)
+ }
+ case r == ',':
+ list = append(list, strings.TrimSpace(b.String()))
+ b.Reset()
+ case r == '"':
+ quote = true
+ b.WriteRune(r)
+ default:
+ b.WriteRune(r)
+ }
+ }
+ // Append last part.
+ if s := b.String(); s != "" {
+ list = append(list, strings.TrimSpace(s))
+ }
+ return list
+}
+
+// ParsePairs extracts key/value pairs from a comma-separated list of
+// values as described by RFC 2068 and returns a map[key]value. The
+// resulting values are unquoted. If a list element doesn't contain a
+// "=", the key is the element itself and the value is an empty
+// string.
+//
+// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go
+func ParsePairs(value string) map[string]string {
+ m := make(map[string]string)
+ for _, pair := range ParseList(strings.TrimSpace(value)) {
+ if i := strings.Index(pair, "="); i < 0 {
+ m[pair] = ""
+ } else {
+ v := pair[i+1:]
+ if v[0] == '"' && v[len(v)-1] == '"' {
+ // Unquote it.
+ v = v[1 : len(v)-1]
+ }
+ m[pair[:i]] = v
+ }
+ }
+ return m
+}
+
+// Headers contains header and error codes used by authenticator.
+type Headers struct {
+ Authenticate string // WWW-Authenticate
+ Authorization string // Authorization
+ AuthInfo string // Authentication-Info
+ UnauthCode int // 401
+ UnauthContentType string // text/plain
+ UnauthResponse string // Unauthorized.
+}
+
+// V returns NormalHeaders when h is nil, or h otherwise. Allows to
+// use uninitialized *Headers values in structs.
+func (h *Headers) V() *Headers {
+ if h == nil {
+ return NormalHeaders
+ }
+ return h
+}
+
+var (
+ // NormalHeaders are the regular Headers used by an HTTP Server for
+ // request authentication.
+ NormalHeaders = &Headers{
+ Authenticate: "WWW-Authenticate",
+ Authorization: "Authorization",
+ AuthInfo: "Authentication-Info",
+ UnauthCode: http.StatusUnauthorized,
+ UnauthContentType: "text/plain",
+ UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
+ }
+
+ // ProxyHeaders are Headers used by an HTTP Proxy server for proxy
+ // access authentication.
+ ProxyHeaders = &Headers{
+ Authenticate: "Proxy-Authenticate",
+ Authorization: "Proxy-Authorization",
+ AuthInfo: "Proxy-Authentication-Info",
+ UnauthCode: http.StatusProxyAuthRequired,
+ UnauthContentType: "text/plain",
+ UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusProxyAuthRequired, http.StatusText(http.StatusProxyAuthRequired)),
+ }
+)
+
+const contentType = "Content-Type"
diff --git a/vendor/github.com/abbot/go-http-auth/misc_test.go b/vendor/github.com/abbot/go-http-auth/misc_test.go
new file mode 100644
index 000000000..31a920f0c
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/misc_test.go
@@ -0,0 +1,37 @@
+package auth
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestH(t *testing.T) {
+ const hello = "Hello, world!"
+ const hello_md5 = "6cd3556deb0da54bca060b4c39479839"
+ h := H(hello)
+ if h != hello_md5 {
+ t.Fatal("Incorrect digest for test string:", h, "instead of", hello_md5)
+ }
+}
+
+func TestParsePairs(t *testing.T) {
+ const header = `username="\test", realm="a \"quoted\" string", nonce="FRPnGdb8lvM1UHhi", uri="/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro", algorithm=MD5, response="fdcdd78e5b306ffed343d0ec3967f2e5", opaque="lEgVjogmIar2fg/t", qop=auth, nc=00000001, cnonce="e76b05db27a3b323"`
+
+ want := map[string]string{
+ "username": "test",
+ "realm": `a "quoted" string`,
+ "nonce": "FRPnGdb8lvM1UHhi",
+ "uri": "/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro",
+ "algorithm": "MD5",
+ "response": "fdcdd78e5b306ffed343d0ec3967f2e5",
+ "opaque": "lEgVjogmIar2fg/t",
+ "qop": "auth",
+ "nc": "00000001",
+ "cnonce": "e76b05db27a3b323",
+ }
+ got := ParsePairs(header)
+
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("failed to correctly parse pairs, got %v, want %v\ndiff: %s", got, want)
+ }
+}
diff --git a/vendor/github.com/abbot/go-http-auth/test.htdigest b/vendor/github.com/abbot/go-http-auth/test.htdigest
new file mode 100644
index 000000000..6c8c75b4e
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/test.htdigest
@@ -0,0 +1 @@
+test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9
diff --git a/vendor/github.com/abbot/go-http-auth/test.htpasswd b/vendor/github.com/abbot/go-http-auth/test.htpasswd
new file mode 100644
index 000000000..4844a3ccb
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/test.htpasswd
@@ -0,0 +1,4 @@
+test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=
+test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0
+test16:$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0
+test3:$2y$05$ih3C91zUBSTFcAh2mQnZYuob0UOZVEf16wl/ukgjDhjvj.xgM1WwS
diff --git a/vendor/github.com/abbot/go-http-auth/users.go b/vendor/github.com/abbot/go-http-auth/users.go
new file mode 100644
index 000000000..377181243
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/users.go
@@ -0,0 +1,154 @@
+package auth
+
+import (
+ "encoding/csv"
+ "os"
+ "sync"
+)
+
+/*
+ SecretProvider is used by authenticators. Takes user name and realm
+ as an argument, returns secret required for authentication (HA1 for
+ digest authentication, properly encrypted password for basic).
+
+ Returning an empty string means failing the authentication.
+*/
+type SecretProvider func(user, realm string) string
+
+/*
+ Common functions for file auto-reloading
+*/
+type File struct {
+ Path string
+ Info os.FileInfo
+ /* must be set in inherited types during initialization */
+ Reload func()
+ mu sync.Mutex
+}
+
+func (f *File) ReloadIfNeeded() {
+ info, err := os.Stat(f.Path)
+ if err != nil {
+ panic(err)
+ }
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ if f.Info == nil || f.Info.ModTime() != info.ModTime() {
+ f.Info = info
+ f.Reload()
+ }
+}
+
+/*
+ Structure used for htdigest file authentication. Users map realms to
+ maps of users to their HA1 digests.
+*/
+type HtdigestFile struct {
+ File
+ Users map[string]map[string]string
+ mu sync.RWMutex
+}
+
+func reload_htdigest(hf *HtdigestFile) {
+ r, err := os.Open(hf.Path)
+ if err != nil {
+ panic(err)
+ }
+ csv_reader := csv.NewReader(r)
+ csv_reader.Comma = ':'
+ csv_reader.Comment = '#'
+ csv_reader.TrimLeadingSpace = true
+
+ records, err := csv_reader.ReadAll()
+ if err != nil {
+ panic(err)
+ }
+
+ hf.mu.Lock()
+ defer hf.mu.Unlock()
+ hf.Users = make(map[string]map[string]string)
+ for _, record := range records {
+ _, exists := hf.Users[record[1]]
+ if !exists {
+ hf.Users[record[1]] = make(map[string]string)
+ }
+ hf.Users[record[1]][record[0]] = record[2]
+ }
+}
+
+/*
+ SecretProvider implementation based on htdigest-formated files. Will
+ reload htdigest file on changes. Will panic on syntax errors in
+ htdigest files.
+*/
+func HtdigestFileProvider(filename string) SecretProvider {
+ hf := &HtdigestFile{File: File{Path: filename}}
+ hf.Reload = func() { reload_htdigest(hf) }
+ return func(user, realm string) string {
+ hf.ReloadIfNeeded()
+ hf.mu.RLock()
+ defer hf.mu.RUnlock()
+ _, exists := hf.Users[realm]
+ if !exists {
+ return ""
+ }
+ digest, exists := hf.Users[realm][user]
+ if !exists {
+ return ""
+ }
+ return digest
+ }
+}
+
+/*
+ Structure used for htdigest file authentication. Users map users to
+ their salted encrypted password
+*/
+type HtpasswdFile struct {
+ File
+ Users map[string]string
+ mu sync.RWMutex
+}
+
+func reload_htpasswd(h *HtpasswdFile) {
+ r, err := os.Open(h.Path)
+ if err != nil {
+ panic(err)
+ }
+ csv_reader := csv.NewReader(r)
+ csv_reader.Comma = ':'
+ csv_reader.Comment = '#'
+ csv_reader.TrimLeadingSpace = true
+
+ records, err := csv_reader.ReadAll()
+ if err != nil {
+ panic(err)
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.Users = make(map[string]string)
+ for _, record := range records {
+ h.Users[record[0]] = record[1]
+ }
+}
+
+/*
+ SecretProvider implementation based on htpasswd-formated files. Will
+ reload htpasswd file on changes. Will panic on syntax errors in
+ htpasswd files. Realm argument of the SecretProvider is ignored.
+*/
+func HtpasswdFileProvider(filename string) SecretProvider {
+ h := &HtpasswdFile{File: File{Path: filename}}
+ h.Reload = func() { reload_htpasswd(h) }
+ return func(user, realm string) string {
+ h.ReloadIfNeeded()
+ h.mu.RLock()
+ password, exists := h.Users[user]
+ h.mu.RUnlock()
+ if !exists {
+ return ""
+ }
+ return password
+ }
+}
diff --git a/vendor/github.com/abbot/go-http-auth/users_test.go b/vendor/github.com/abbot/go-http-auth/users_test.go
new file mode 100644
index 000000000..784ad7f22
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/users_test.go
@@ -0,0 +1,45 @@
+package auth
+
+import (
+ "os"
+ "testing"
+ "time"
+)
+
+func TestHtdigestFile(t *testing.T) {
+ secrets := HtdigestFileProvider("test.htdigest")
+ digest := secrets("test", "example.com")
+ if digest != "aa78524fceb0e50fd8ca96dd818b8cf9" {
+ t.Fatal("Incorrect digest for test user:", digest)
+ }
+ digest = secrets("test", "example1.com")
+ if digest != "" {
+ t.Fatal("Got digest for user in non-existant realm:", digest)
+ }
+ digest = secrets("test1", "example.com")
+ if digest != "" {
+ t.Fatal("Got digest for non-existant user:", digest)
+ }
+}
+
+func TestHtpasswdFile(t *testing.T) {
+ secrets := HtpasswdFileProvider("test.htpasswd")
+ passwd := secrets("test", "blah")
+ if passwd != "{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=" {
+ t.Fatal("Incorrect passwd for test user:", passwd)
+ }
+ passwd = secrets("nosuchuser", "blah")
+ if passwd != "" {
+ t.Fatal("Got passwd for non-existant user:", passwd)
+ }
+}
+
+// TestConcurrent verifies potential race condition in users reading logic
+func TestConcurrent(t *testing.T) {
+ secrets := HtpasswdFileProvider("test.htpasswd")
+ os.Chtimes("test.htpasswd", time.Now(), time.Now())
+ go func() {
+ secrets("test", "blah")
+ }()
+ secrets("test", "blah")
+}