diff --git a/go.mod b/go.mod
index c8a85982..0db5a5f9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,13 @@ module github.com/mickael-kerjean/filestash
go 1.16
require (
+ github.com/abbot/go-http-auth v0.4.1-0.20220112235402-e1cee1c72f2f
github.com/aws/aws-sdk-go v1.40.41
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
github.com/creack/pty v1.1.18
github.com/cretz/bine v0.1.0
github.com/go-sql-driver/mysql v1.5.0
- github.com/google/go-cmp v0.5.6 // indirect
+ github.com/google/go-cmp v0.5.8 // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.1
github.com/h2non/bimg v1.1.5
@@ -28,12 +29,14 @@ require (
github.com/tidwall/gjson v1.13.0
github.com/tidwall/sjson v1.0.4
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
- golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
+ golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c
+ golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
+ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 // indirect
google.golang.org/api v0.15.0
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
diff --git a/go.sum b/go.sum
index acc55de6..8afb48d0 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/abbot/go-http-auth v0.4.1-0.20220112235402-e1cee1c72f2f h1:R2ZVGCZzU95oXFJxncosHS9LsX8N4/MYUdGGWOb2cFk=
+github.com/abbot/go-http-auth v0.4.1-0.20220112235402-e1cee1c72f2f/go.mod h1:l2P3JyHa+fjy5Bxol6y1u2o4DV/mv3QMBdBu2cNR53w=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
@@ -43,8 +45,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@@ -155,8 +157,9 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
-golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA=
+golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c h1:FRR4fGZm/CMwZka5baQ4z8c8StbxJOMjS/45e0BAxK0=
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
@@ -165,8 +168,9 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -177,6 +181,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -225,8 +230,9 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k=
+golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/server/common/response.go b/server/common/response.go
index c730bd60..8190923f 100644
--- a/server/common/response.go
+++ b/server/common/response.go
@@ -133,17 +133,22 @@ func Page(stuff string) string {
-
+
` + stuff + `
+
`
}
diff --git a/server/ctrl/session.go b/server/ctrl/session.go
index 6330f9f8..59e6662d 100644
--- a/server/ctrl/session.go
+++ b/server/ctrl/session.go
@@ -302,11 +302,17 @@ func SessionAuthMiddleware(ctx *App, res http.ResponseWriter, req *http.Request)
// - target of a html form. eg: ldap, mysql, ...
// - identity provider redirection uri. eg: oauth2, openid, ...
templateBind, err := plugin.Callback(formData, idpParams, res)
- if err != nil {
+ if err == ErrAuthenticationFailed {
+ http.Redirect(
+ res, req,
+ req.URL.Path+"?action=redirect",
+ http.StatusSeeOther,
+ )
+ return
+ } else if err != nil {
Log.Error("session::authMiddleware 'callback error - %s'", err.Error())
http.Redirect(
- res,
- req,
+ res, req,
"/?error="+ErrNotAllowed.Error()+"&trace=redirect request failed - "+err.Error(),
http.StatusSeeOther,
)
diff --git a/server/plugin/index.go b/server/plugin/index.go
index 2920f713..4f390bf4 100644
--- a/server/plugin/index.go
+++ b/server/plugin/index.go
@@ -3,6 +3,7 @@ package plugin
import (
. "github.com/mickael-kerjean/filestash/server/common"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_admin"
+ _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_ldap"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_openid"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_passthrough"
diff --git a/server/plugin/plg_authenticate_htpasswd/index.go b/server/plugin/plg_authenticate_htpasswd/index.go
new file mode 100644
index 00000000..92408112
--- /dev/null
+++ b/server/plugin/plg_authenticate_htpasswd/index.go
@@ -0,0 +1,94 @@
+package plg_authenticate_passthrough
+
+import (
+ "fmt"
+ auth "github.com/abbot/go-http-auth"
+ . "github.com/mickael-kerjean/filestash/server/common"
+ "net/http"
+ "strings"
+)
+
+func init() {
+ Hooks.Register.AuthenticationMiddleware("htpasswd", Htpasswd{})
+}
+
+type Htpasswd struct{}
+
+func (this Htpasswd) Setup() Form {
+ return Form{
+ Elmnts: []FormElement{
+ {
+ Name: "type",
+ Type: "hidden",
+ Value: "htpasswd",
+ },
+ {
+ Name: "users",
+ Type: "long_text",
+ Placeholder: "eg:\nbob123:$apr1$FaPCZHMe$jYiw5.9UevKx25pBH4AsT/\nnancy456:$apr1$mrCHcVhc$oNdJeRcWKPk2z8dlzQI0x/",
+ Default: "",
+ Description: "The list of users that will be granted access using the htpasswd file format. This plugin exposes the following variables which you can use from the attribute mapping: {{ .user }}, {{ .password }}",
+ },
+ },
+ }
+}
+
+func (this Htpasswd) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error {
+ getFlash := func() string {
+ c, err := req.Cookie("flash")
+ if err != nil {
+ return ""
+ }
+ http.SetCookie(res, &http.Cookie{
+ Name: "flash",
+ MaxAge: -1,
+ Path: "/",
+ })
+ return fmt.Sprintf(`%s
`, c.Value)
+ }
+ res.Header().Set("Content-Type", "text/html; charset=utf-8")
+ res.WriteHeader(http.StatusOK)
+ res.Write([]byte(Page(`
+ `)))
+ return nil
+}
+
+func (this Htpasswd) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) {
+ lines := strings.Split(idpParams["users"], "\n")
+ if len(lines) == 0 {
+ Log.Error("plg_authenticate_htpasswd::callback there is no user configured")
+ return nil, ErrAuthenticationFailed
+ }
+
+ for _, line := range lines {
+ pair := strings.SplitN(line, ":", 2)
+ if len(pair) != 2 {
+ continue
+ } else if formData["user"] != pair[0] {
+ continue
+ } else if auth.CheckSecret(formData["password"], pair[1]) == false {
+ continue
+ }
+ return map[string]string{
+ "user": formData["user"],
+ "password": formData["password"],
+ }, nil
+ }
+ http.SetCookie(res, &http.Cookie{
+ Name: "flash",
+ Value: "Invalid username or password",
+ MaxAge: 1,
+ Path: "/",
+ })
+ return nil, ErrAuthenticationFailed
+}
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 00000000..112ea395
--- /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 00000000..e454a525
--- /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 00000000..25f208da
--- /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 00000000..73ae9852
--- /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 00000000..bbe5a129
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/auth.go
@@ -0,0 +1,110 @@
+// Package auth is an implementation of HTTP Basic and HTTP Digest authentication.
+package auth
+
+import (
+ "context"
+ "net/http"
+)
+
+// AuthenticatedRequest is passed to AuthenticatedHandlerFunc instead
+// of *http.Request.
+type AuthenticatedRequest struct {
+ http.Request
+ // Username is the 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
+
+// AuthenticatorInterface is the interface implemented by BasicAuth
+// and DigestAuth authenticators.
+//
+// Deprecated: this interface is not coherent. New code should define
+// and use your own interfaces with a required subset of authenticator
+// methods.
+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"
+
+// JustCheck returns a new http.HandlerFunc, which requires
+// authenticator to successfully authenticate a user before calling
+// wrapped http.HandlerFunc.
+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 00000000..f328a328
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/basic.go
@@ -0,0 +1,161 @@
+package auth
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha1"
+ "crypto/subtle"
+ "encoding/base64"
+ "errors"
+ "net/http"
+ "strings"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+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},
+ }
+)
+
+// BasicAuth is an authenticator implementation for 'Basic' HTTP
+// Authentication scheme (RFC 7617).
+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))
+
+// CheckAuth checks the username/password combination from the
+// request. Returns either an empty string (authentication failed) or
+// the name of the authenticated user.
+func (a *BasicAuth) CheckAuth(r *http.Request) string {
+ user, password, ok := r.BasicAuth()
+ if !ok {
+ return ""
+ }
+
+ secret := a.Secrets(user, a.Realm)
+ if secret == "" {
+ return ""
+ }
+
+ if !CheckSecret(password, secret) {
+ return ""
+ }
+
+ return user
+}
+
+// CheckSecret returns true if the password matches the encrypted
+// secret.
+func CheckSecret(password, secret string) bool {
+ compare := compareFuncs[0].compare
+ for _, cmp := range compareFuncs[1:] {
+ if strings.HasPrefix(secret, cmp.prefix) {
+ compare = cmp.compare
+ break
+ }
+ }
+ return compare([]byte(secret), []byte(password)) == nil
+}
+
+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
+}
+
+// RequireAuth is an http.HandlerFunc 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))
+}
+
+// Wrap returns an http.HandlerFunc, which wraps
+// AuthenticatedHandlerFunc with this BasicAuth authenticator's
+// authentication checks. Once the request contains valid credentials,
+// it calls wrapped AuthenticatedHandlerFunc.
+//
+// Deprecated: new code should use NewContext instead.
+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)
+}
+
+// NewBasicAuthenticator returns a BasicAuth initialized with provided
+// realm and secrets.
+//
+// Deprecated: new code should construct BasicAuth values directly.
+func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth {
+ return &BasicAuth{Realm: realm, Secrets: secrets}
+}
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 00000000..88ac4bd4
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/digest.go
@@ -0,0 +1,288 @@
+package auth
+
+import (
+ "context"
+ "crypto/subtle"
+ "fmt"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+type digestClient struct {
+ nc uint64
+ lastSeen int64
+}
+
+// DigestAuth is an authenticator implementation for 'Digest' HTTP Authentication scheme (RFC 7616).
+//
+// Note: this implementation was written following now deprecated RFC
+// 2617, and supports only MD5 algorithm.
+//
+// TODO: Add support for SHA-256 and SHA-512/256 algorithms.
+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]*digestClient
+ mutex sync.RWMutex
+}
+
+// check that DigestAuth implements AuthenticatorInterface
+var _ = (AuthenticatorInterface)((*DigestAuth)(nil))
+
+type digestCacheEntry struct {
+ nonce string
+ lastSeen int64
+}
+
+type digestCache []digestCacheEntry
+
+func (c digestCache) Less(i, j int) bool {
+ return c[i].lastSeen < c[j].lastSeen
+}
+
+func (c digestCache) Len() int {
+ return len(c)
+}
+
+func (c digestCache) Swap(i, j int) {
+ c[i], c[j] = c[j], c[i]
+}
+
+// Purge removes count oldest entries from DigestAuth.clients
+func (da *DigestAuth) Purge(count int) {
+ da.mutex.Lock()
+ da.purgeLocked(count)
+ da.mutex.Unlock()
+}
+
+func (da *DigestAuth) purgeLocked(count int) {
+ entries := make([]digestCacheEntry, 0, len(da.clients))
+ for nonce, client := range da.clients {
+ entries = append(entries, digestCacheEntry{nonce, client.lastSeen})
+ }
+ cache := digestCache(entries)
+ sort.Sort(cache)
+ for _, client := range cache[:count] {
+ delete(da.clients, client.nonce)
+ }
+}
+
+// RequireAuth is an http.HandlerFunc which initiates the
+// authentication process (or requires reauthentication).
+func (da *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
+ da.mutex.RLock()
+ clientsLen := len(da.clients)
+ da.mutex.RUnlock()
+
+ if clientsLen > da.ClientCacheSize+da.ClientCacheTolerance {
+ da.Purge(da.ClientCacheTolerance * 2)
+ }
+ nonce := RandomKey()
+
+ da.mutex.Lock()
+ da.clients[nonce] = &digestClient{nc: 0, lastSeen: time.Now().UnixNano()}
+ da.mutex.Unlock()
+
+ da.mutex.RLock()
+ w.Header().Set(contentType, da.Headers.V().UnauthContentType)
+ w.Header().Set(da.Headers.V().Authenticate,
+ fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm=MD5, qop="auth"`,
+ da.Realm, nonce, da.Opaque))
+ w.WriteHeader(da.Headers.V().UnauthCode)
+ w.Write([]byte(da.Headers.V().UnauthResponse))
+ da.mutex.RUnlock()
+}
+
+// DigestAuthParams parses 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])
+}
+
+// CheckAuth checks whether the 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.RLock()
+ defer da.mutex.RUnlock()
+ 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
+ }
+
+ client, ok := da.clients[auth["nonce"]]
+ if !ok {
+ return "", nil
+ }
+ if client.nc != 0 && client.nc >= nc && !da.IgnoreNonceCount {
+ return "", nil
+ }
+ client.nc = nc
+ client.lastSeen = time.Now().UnixNano()
+
+ respHA2 := H(":" + auth["uri"])
+ rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], respHA2}, ":"))
+
+ 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
+ DefaultClientCacheTolerance = 100
+)
+
+// Wrap returns an http.HandlerFunc wraps AuthenticatedHandlerFunc
+// with this DigestAuth authentication checks. Once the request
+// contains valid credentials, it calls wrapped
+// AuthenticatedHandlerFunc.
+//
+// Deprecated: new code should use NewContext instead.
+func (da *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if username, authinfo := da.CheckAuth(r); username == "" {
+ da.RequireAuth(w, r)
+ } else {
+ ar := &AuthenticatedRequest{Request: *r, Username: username}
+ if authinfo != nil {
+ w.Header().Set(da.Headers.V().AuthInfo, *authinfo)
+ }
+ wrapped(w, ar)
+ }
+ }
+}
+
+// JustCheck returns a new http.HandlerFunc, which requires
+// DigestAuth to successfully authenticate a user before calling
+// wrapped http.HandlerFunc.
+//
+// Authenticated Username is passed as an extra
+// X-Authenticated-Username header to the wrapped HandlerFunc.
+func (da *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
+ return da.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 (da *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
+ username, authinfo := da.CheckAuth(r)
+ da.mutex.Lock()
+ defer da.mutex.Unlock()
+ info := &Info{Username: username, ResponseHeaders: make(http.Header)}
+ if username != "" {
+ info.Authenticated = true
+ info.ResponseHeaders.Set(da.Headers.V().AuthInfo, *authinfo)
+ } else {
+ // return back digest WWW-Authenticate header
+ if len(da.clients) > da.ClientCacheSize+da.ClientCacheTolerance {
+ da.purgeLocked(da.ClientCacheTolerance * 2)
+ }
+ nonce := RandomKey()
+ da.clients[nonce] = &digestClient{nc: 0, lastSeen: time.Now().UnixNano()}
+ info.ResponseHeaders.Set(da.Headers.V().Authenticate,
+ fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm=MD5, qop="auth"`,
+ da.Realm, nonce, da.Opaque))
+ }
+ return context.WithValue(ctx, infoKey, info)
+}
+
+// NewDigestAuthenticator generates a new DigestAuth object
+func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth {
+ da := &DigestAuth{
+ Opaque: RandomKey(),
+ Realm: realm,
+ Secrets: secrets,
+ PlainTextSecrets: false,
+ ClientCacheSize: DefaultClientCacheSize,
+ ClientCacheTolerance: DefaultClientCacheTolerance,
+ clients: map[string]*digestClient{}}
+ return da
+}
diff --git a/vendor/github.com/abbot/go-http-auth/go.mod b/vendor/github.com/abbot/go-http-auth/go.mod
new file mode 100644
index 00000000..80cdc218
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/go.mod
@@ -0,0 +1,5 @@
+module github.com/abbot/go-http-auth
+
+go 1.14
+
+require golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
diff --git a/vendor/github.com/abbot/go-http-auth/go.sum b/vendor/github.com/abbot/go-http-auth/go.sum
new file mode 100644
index 00000000..814b1a38
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/go.sum
@@ -0,0 +1,8 @@
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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 00000000..ed7ccc37
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/md5crypt.go
@@ -0,0 +1,73 @@
+package auth
+
+import "crypto/md5"
+
+const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+
+var md5CryptSwaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11}
+
+// MD5Crypt is the 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 md5CryptSwaps {
+ 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/misc.go b/vendor/github.com/abbot/go-http-auth/misc.go
new file mode 100644
index 00000000..c4380255
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/misc.go
@@ -0,0 +1,146 @@
+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)) {
+ switch i := strings.Index(pair, "="); {
+ case i < 0:
+ // No '=' in pair, treat whole string as a 'key'.
+ m[pair] = ""
+ case i == len(pair)-1:
+ // Malformed pair ('key=' with no value), keep key with empty value.
+ m[pair[:i]] = ""
+ default:
+ 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/test.htdigest b/vendor/github.com/abbot/go-http-auth/test.htdigest
new file mode 100644
index 00000000..6c8c75b4
--- /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 00000000..4844a3cc
--- /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 00000000..ba032fe9
--- /dev/null
+++ b/vendor/github.com/abbot/go-http-auth/users.go
@@ -0,0 +1,151 @@
+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
+
+// File handles automatic file reloading on changes.
+type File struct {
+ Path string
+ Info os.FileInfo
+ /* must be set in inherited types during initialization */
+ Reload func()
+ mu sync.Mutex
+}
+
+// ReloadIfNeeded checks file Stat and calls Reload() if any changes
+// were detected. File mutex is Locked for the duration of Reload()
+// call.
+//
+// This function will panic() if Stat fails.
+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()
+ }
+}
+
+// HtdigestFile is a File holding htdigest authentication data.
+type HtdigestFile struct {
+ // File is used for automatic reloading of the authentication data.
+ File
+ // Users is a map of realms to users to HA1 digests.
+ Users map[string]map[string]string
+ mu sync.RWMutex
+}
+
+func reloadHTDigest(hf *HtdigestFile) {
+ r, err := os.Open(hf.Path)
+ if err != nil {
+ panic(err)
+ }
+ reader := csv.NewReader(r)
+ reader.Comma = ':'
+ reader.Comment = '#'
+ reader.TrimLeadingSpace = true
+
+ records, err := 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]
+ }
+}
+
+// HtdigestFileProvider is a SecretProvider implementation based on
+// htdigest-formated files. It will automatically reload htdigest file
+// on changes. It panics on syntax errors in htdigest files.
+func HtdigestFileProvider(filename string) SecretProvider {
+ hf := &HtdigestFile{File: File{Path: filename}}
+ hf.Reload = func() { reloadHTDigest(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
+ }
+}
+
+// HtpasswdFile is a File holding basic authentication data.
+type HtpasswdFile struct {
+ // File is used for automatic reloading of the authentication data.
+ File
+ // Users is a map of users to their secrets (salted encrypted
+ // passwords).
+ Users map[string]string
+ mu sync.RWMutex
+}
+
+func reloadHTPasswd(h *HtpasswdFile) {
+ r, err := os.Open(h.Path)
+ if err != nil {
+ panic(err)
+ }
+ reader := csv.NewReader(r)
+ reader.Comma = ':'
+ reader.Comment = '#'
+ reader.TrimLeadingSpace = true
+
+ records, err := 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]
+ }
+}
+
+// HtpasswdFileProvider is a SecretProvider implementation based on
+// htpasswd-formated files. It will automatically reload htpasswd file
+// on changes. It panics 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() { reloadHTPasswd(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/modules.txt b/vendor/modules.txt
index c411e7ce..cf48ea8a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,5 +1,8 @@
# cloud.google.com/go v0.38.0
cloud.google.com/go/compute/metadata
+# github.com/abbot/go-http-auth v0.4.1-0.20220112235402-e1cee1c72f2f
+## explicit
+github.com/abbot/go-http-auth
# github.com/aws/aws-sdk-go v1.40.41
## explicit
github.com/aws/aws-sdk-go/aws
@@ -85,7 +88,7 @@ github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any
github.com/golang/protobuf/ptypes/duration
github.com/golang/protobuf/ptypes/timestamp
-# github.com/google/go-cmp v0.5.6
+# github.com/google/go-cmp v0.5.8
## explicit
# github.com/google/uuid v1.3.0
github.com/google/uuid
@@ -208,7 +211,7 @@ go.opencensus.io/trace
go.opencensus.io/trace/internal
go.opencensus.io/trace/propagation
go.opencensus.io/trace/tracestate
-# golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
+# golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
## explicit
golang.org/x/crypto/acme
golang.org/x/crypto/acme/autocert
@@ -238,7 +241,8 @@ golang.org/x/crypto/ssh/knownhosts
## explicit
golang.org/x/image/draw
golang.org/x/image/math/f64
-# golang.org/x/mod v0.3.0
+# golang.org/x/mod v0.4.1
+## explicit
golang.org/x/mod/semver
# golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
## explicit
@@ -275,7 +279,8 @@ golang.org/x/text/unicode/norm
# golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
## explicit
golang.org/x/time/rate
-# golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78
+# golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963
+## explicit
golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/internal/gcimporter
golang.org/x/tools/go/internal/packagesdriver