mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-07 17:02:29 +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
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/abbot/go-http-auth v0.4.1-0.20220112235402-e1cee1c72f2f
|
||||||
github.com/aws/aws-sdk-go v1.40.41
|
github.com/aws/aws-sdk-go v1.40.41
|
||||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
|
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/cretz/bine v0.1.0
|
github.com/cretz/bine v0.1.0
|
||||||
github.com/go-sql-driver/mysql v1.5.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/mux v1.7.3
|
||||||
github.com/gorilla/websocket v1.4.1
|
github.com/gorilla/websocket v1.4.1
|
||||||
github.com/h2non/bimg v1.1.5
|
github.com/h2non/bimg v1.1.5
|
||||||
|
|
@ -28,12 +29,14 @@ require (
|
||||||
github.com/tidwall/gjson v1.13.0
|
github.com/tidwall/gjson v1.13.0
|
||||||
github.com/tidwall/sjson v1.0.4
|
github.com/tidwall/sjson v1.0.4
|
||||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
|
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/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/net v0.0.0-20211112202133-69e39bad7dc2
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
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
|
google.golang.org/api v0.15.0
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // 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 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
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/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 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
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=
|
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.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.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.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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
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/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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
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-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-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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
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/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 h1:FRR4fGZm/CMwZka5baQ4z8c8StbxJOMjS/45e0BAxK0=
|
||||||
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
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-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-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/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.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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-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-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-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-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 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
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-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-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-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-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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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 http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||||
<style>
|
<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; }
|
body { text-align: center; padding-top: 50px; text-align: center; margin: 0; }
|
||||||
h1 { font-weight: 200; line-height: 1em; font-size: 40px; }
|
h1 { font-weight: 200; line-height: 1em; font-size: 40px; }
|
||||||
p { opacity: 0.8; font-size: 1.05em; }
|
p { opacity: 0.8; font-size: 1.05em; }
|
||||||
form { max-width: 500px; margin: 0 auto; padding: 0 10px; text-align: left; }
|
form { max-width: 450px; margin: 0 auto; padding: 0 10px; text-align: left; }
|
||||||
button { padding: 7px 0px; width: 100%; margin-top: 5px; cursor: pointer; }
|
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 { 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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="common_response_page">
|
||||||
` + stuff + `
|
` + stuff + `
|
||||||
|
<style>
|
||||||
|
` + Hooks.Get.CSS() + `
|
||||||
|
` + Config.Get("general.custom_css").String() + `
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -302,11 +302,17 @@ func SessionAuthMiddleware(ctx *App, res http.ResponseWriter, req *http.Request)
|
||||||
// - target of a html form. eg: ldap, mysql, ...
|
// - target of a html form. eg: ldap, mysql, ...
|
||||||
// - identity provider redirection uri. eg: oauth2, openid, ...
|
// - identity provider redirection uri. eg: oauth2, openid, ...
|
||||||
templateBind, err := plugin.Callback(formData, idpParams, res)
|
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())
|
Log.Error("session::authMiddleware 'callback error - %s'", err.Error())
|
||||||
http.Redirect(
|
http.Redirect(
|
||||||
res,
|
res, req,
|
||||||
req,
|
|
||||||
"/?error="+ErrNotAllowed.Error()+"&trace=redirect request failed - "+err.Error(),
|
"/?error="+ErrNotAllowed.Error()+"&trace=redirect request failed - "+err.Error(),
|
||||||
http.StatusSeeOther,
|
http.StatusSeeOther,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package plugin
|
||||||
import (
|
import (
|
||||||
. "github.com/mickael-kerjean/filestash/server/common"
|
. "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_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_ldap"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_openid"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_openid"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_passthrough"
|
_ "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 v0.38.0
|
||||||
cloud.google.com/go/compute/metadata
|
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
|
# github.com/aws/aws-sdk-go v1.40.41
|
||||||
## explicit
|
## explicit
|
||||||
github.com/aws/aws-sdk-go/aws
|
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/any
|
||||||
github.com/golang/protobuf/ptypes/duration
|
github.com/golang/protobuf/ptypes/duration
|
||||||
github.com/golang/protobuf/ptypes/timestamp
|
github.com/golang/protobuf/ptypes/timestamp
|
||||||
# github.com/google/go-cmp v0.5.6
|
# github.com/google/go-cmp v0.5.8
|
||||||
## explicit
|
## explicit
|
||||||
# github.com/google/uuid v1.3.0
|
# github.com/google/uuid v1.3.0
|
||||||
github.com/google/uuid
|
github.com/google/uuid
|
||||||
|
|
@ -208,7 +211,7 @@ go.opencensus.io/trace
|
||||||
go.opencensus.io/trace/internal
|
go.opencensus.io/trace/internal
|
||||||
go.opencensus.io/trace/propagation
|
go.opencensus.io/trace/propagation
|
||||||
go.opencensus.io/trace/tracestate
|
go.opencensus.io/trace/tracestate
|
||||||
# golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
# golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
|
||||||
## explicit
|
## explicit
|
||||||
golang.org/x/crypto/acme
|
golang.org/x/crypto/acme
|
||||||
golang.org/x/crypto/acme/autocert
|
golang.org/x/crypto/acme/autocert
|
||||||
|
|
@ -238,7 +241,8 @@ golang.org/x/crypto/ssh/knownhosts
|
||||||
## explicit
|
## explicit
|
||||||
golang.org/x/image/draw
|
golang.org/x/image/draw
|
||||||
golang.org/x/image/math/f64
|
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/mod/semver
|
||||||
# golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
# golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||||
## explicit
|
## explicit
|
||||||
|
|
@ -275,7 +279,8 @@ golang.org/x/text/unicode/norm
|
||||||
# golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
# golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
||||||
## explicit
|
## explicit
|
||||||
golang.org/x/time/rate
|
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/gcexportdata
|
||||||
golang.org/x/tools/go/internal/gcimporter
|
golang.org/x/tools/go/internal/gcimporter
|
||||||
golang.org/x/tools/go/internal/packagesdriver
|
golang.org/x/tools/go/internal/packagesdriver
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue