mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (htpasswd): new authentication middleware
This commit is contained in:
parent
92219db637
commit
e0f2f29307
21 changed files with 1352 additions and 19 deletions
7
go.mod
7
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
|
||||
|
|
|
|||
18
go.sum
18
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=
|
||||
|
|
|
|||
|
|
@ -133,17 +133,22 @@ func Page(stuff string) string {
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<style>
|
||||
html { background: #f4f4f4; color: #455164; font-size: 16px; font-family: -apple-system,system-ui,BlinkMacSystemFont,Roboto,"Helvetica Neue",Arial,sans-serif; }
|
||||
html { background: #f4f4f4; font-size: 16px; font-family: -apple-system,system-ui,BlinkMacSystemFont,Roboto,"Helvetica Neue",Arial,sans-serif; }
|
||||
body { text-align: center; padding-top: 50px; text-align: center; margin: 0; }
|
||||
h1 { font-weight: 200; line-height: 1em; font-size: 40px; }
|
||||
p { opacity: 0.8; font-size: 1.05em; }
|
||||
form { max-width: 500px; margin: 0 auto; padding: 0 10px; text-align: left; }
|
||||
button { padding: 7px 0px; width: 100%; margin-top: 5px; cursor: pointer; }
|
||||
form { max-width: 450px; margin: 0 auto; padding: 0 10px; text-align: left; }
|
||||
button { padding: 7px 0px; width: 100%; margin-top: 5px; cursor: pointer; font-weight: bold; background: rgba(0,0,0,0.2); border-color: rgba(0,0,0,0.2); border-radius: 3px; }
|
||||
input, textarea { display: block; margin: 5px 0; border-radius: 3px; border: 2px solid rgba(0,0,0,0.1); outline: none; padding: 8px 10px; min-width: 100%; max-width: 100%; max-height: 80px; box-sizing: border-box; }
|
||||
input, textarea, body, button { color: #313538; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="common_response_page">
|
||||
` + stuff + `
|
||||
<style>
|
||||
` + Hooks.Get.CSS() + `
|
||||
` + Config.Get("general.custom_css").String() + `
|
||||
</style>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
94
server/plugin/plg_authenticate_htpasswd/index.go
Normal file
94
server/plugin/plg_authenticate_htpasswd/index.go
Normal file
|
|
@ -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(`<p class="flash">%s</p>`, c.Value)
|
||||
}
|
||||
res.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte(Page(`
|
||||
<form action="/api/session/auth/" method="post" class="component_middleware">
|
||||
<label>
|
||||
<input type="text" name="user" value="" placeholder="User" />
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" name="password" value="" placeholder="Password" />
|
||||
</label>
|
||||
<button>CONNECT</button>
|
||||
` + getFlash() + `
|
||||
<style>.flash{ color: #f26d6d; font-weight: bold; }</style>
|
||||
</form>`)))
|
||||
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
|
||||
}
|
||||
5
vendor/github.com/abbot/go-http-auth/.gitignore
generated
vendored
Normal file
5
vendor/github.com/abbot/go-http-auth/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
*~
|
||||
*.a
|
||||
*.6
|
||||
*.out
|
||||
_testmain.go
|
||||
178
vendor/github.com/abbot/go-http-auth/LICENSE
generated
vendored
Normal file
178
vendor/github.com/abbot/go-http-auth/LICENSE
generated
vendored
Normal file
|
|
@ -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
|
||||
|
||||
12
vendor/github.com/abbot/go-http-auth/Makefile
generated
vendored
Normal file
12
vendor/github.com/abbot/go-http-auth/Makefile
generated
vendored
Normal file
|
|
@ -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
|
||||
71
vendor/github.com/abbot/go-http-auth/README.md
generated
vendored
Normal file
71
vendor/github.com/abbot/go-http-auth/README.md
generated
vendored
Normal file
|
|
@ -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, "<html><body><h1>Hello, %s!</h1></body></html>", 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.
|
||||
110
vendor/github.com/abbot/go-http-auth/auth.go
generated
vendored
Normal file
110
vendor/github.com/abbot/go-http-auth/auth.go
generated
vendored
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
161
vendor/github.com/abbot/go-http-auth/basic.go
generated
vendored
Normal file
161
vendor/github.com/abbot/go-http-auth/basic.go
generated
vendored
Normal file
|
|
@ -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}
|
||||
}
|
||||
288
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
Normal file
288
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
5
vendor/github.com/abbot/go-http-auth/go.mod
generated
vendored
Normal file
5
vendor/github.com/abbot/go-http-auth/go.mod
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/abbot/go-http-auth
|
||||
|
||||
go 1.14
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
8
vendor/github.com/abbot/go-http-auth/go.sum
generated
vendored
Normal file
8
vendor/github.com/abbot/go-http-auth/go.sum
generated
vendored
Normal file
|
|
@ -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=
|
||||
73
vendor/github.com/abbot/go-http-auth/md5crypt.go
generated
vendored
Normal file
73
vendor/github.com/abbot/go-http-auth/md5crypt.go
generated
vendored
Normal file
|
|
@ -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...)
|
||||
}
|
||||
146
vendor/github.com/abbot/go-http-auth/misc.go
generated
vendored
Normal file
146
vendor/github.com/abbot/go-http-auth/misc.go
generated
vendored
Normal file
|
|
@ -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"
|
||||
1
vendor/github.com/abbot/go-http-auth/test.htdigest
generated
vendored
Normal file
1
vendor/github.com/abbot/go-http-auth/test.htdigest
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9
|
||||
4
vendor/github.com/abbot/go-http-auth/test.htpasswd
generated
vendored
Normal file
4
vendor/github.com/abbot/go-http-auth/test.htpasswd
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=
|
||||
test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0
|
||||
test16:$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0
|
||||
test3:$2y$05$ih3C91zUBSTFcAh2mQnZYuob0UOZVEf16wl/ukgjDhjvj.xgM1WwS
|
||||
151
vendor/github.com/abbot/go-http-auth/users.go
generated
vendored
Normal file
151
vendor/github.com/abbot/go-http-auth/users.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
13
vendor/modules.txt
vendored
13
vendor/modules.txt
vendored
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue