git.iko.soy/typeable/oauth2-proxy
on master help sign in
Reduce SessionState size better with MessagePack + LZ4 (#632)
a09eeccNick Meves
CHANGELOG.md+4
@@ -4,10 +4,14 @@
4
4
5## Important Notes
5## Important Notes
6
6
7+- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) There is backwards compatibility to sessions from v5
8+ - Any unencrypted sessions from before v5 that only contained a Username & Email will trigger a reauthentication
9+
7## Breaking Changes
10## Breaking Changes
8
11
9## Changes since v6.0.0
12## Changes since v6.0.0
10
13
14+- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
11- [#675](https://github.com/oauth2-proxy/oauth2-proxy/pull/675) Fix required ruby version and deprecated option for building docs (@mkontani)
15- [#675](https://github.com/oauth2-proxy/oauth2-proxy/pull/675) Fix required ruby version and deprecated option for building docs (@mkontani)
12- [#669](https://github.com/oauth2-proxy/oauth2-proxy/pull/669) Reduce docker context to improve build times (@JoelSpeed)
16- [#669](https://github.com/oauth2-proxy/oauth2-proxy/pull/669) Reduce docker context to improve build times (@JoelSpeed)
13- [#668](https://github.com/oauth2-proxy/oauth2-proxy/pull/668) Use req.Host in --force-https when req.URL.Host is empty (@zucaritask)
17- [#668](https://github.com/oauth2-proxy/oauth2-proxy/pull/668) Use req.Host in --force-https when req.URL.Host is empty (@zucaritask)
go.mod+5−4
@@ -9,23 +9,24 @@ require (
9 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
9 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
10 github.com/coreos/go-oidc v2.2.1+incompatible
10 github.com/coreos/go-oidc v2.2.1+incompatible
11 github.com/dgrijalva/jwt-go v3.2.0+incompatible
11 github.com/dgrijalva/jwt-go v3.2.0+incompatible
12+ github.com/frankban/quicktest v1.10.0 // indirect
12 github.com/fsnotify/fsnotify v1.4.9
13 github.com/fsnotify/fsnotify v1.4.9
13 github.com/go-redis/redis/v7 v7.2.0
14 github.com/go-redis/redis/v7 v7.2.0
14 github.com/justinas/alice v1.2.0
15 github.com/justinas/alice v1.2.0
15− github.com/kr/pretty v0.2.0 // indirect
16 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
16 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
17 github.com/mitchellh/mapstructure v1.1.2
17 github.com/mitchellh/mapstructure v1.1.2
18 github.com/onsi/ginkgo v1.12.0
18 github.com/onsi/ginkgo v1.12.0
19 github.com/onsi/gomega v1.9.0
19 github.com/onsi/gomega v1.9.0
20+ github.com/pierrec/lz4 v2.5.2+incompatible
20 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
21 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
21 github.com/spf13/pflag v1.0.3
22 github.com/spf13/pflag v1.0.3
22 github.com/spf13/viper v1.6.3
23 github.com/spf13/viper v1.6.3
23 github.com/stretchr/testify v1.5.1
24 github.com/stretchr/testify v1.5.1
25+ github.com/vmihailenco/msgpack/v4 v4.3.11
24 github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
26 github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
25− golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
27+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
26− golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
28+ golang.org/x/net v0.0.0-20200301022130-244492dfa37a
27 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
29 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
28− golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
29 google.golang.org/api v0.20.0
30 google.golang.org/api v0.20.0
30 gopkg.in/natefinch/lumberjack.v2 v2.0.0
31 gopkg.in/natefinch/lumberjack.v2 v2.0.0
31 gopkg.in/square/go-jose.v2 v2.4.1
32 gopkg.in/square/go-jose.v2 v2.4.1
go.sum+17−6
@@ -47,6 +47,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
47github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
47github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
48github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
48github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
49github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
49github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
50+github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
51+github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
50github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
52github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
51github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
53github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
52github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
54github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@@ -69,6 +71,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
69github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
71github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
70github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
72github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
71github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
73github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
74+github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
75+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
72github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
76github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
73github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
77github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
74github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8=
78github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8=
@@ -78,6 +82,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
78github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
82github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
79github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
83github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
80github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
84github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
85+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
86+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
81github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
87github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
82github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
88github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
83github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
89github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -142,6 +148,8 @@ github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
142github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
148github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
143github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
149github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
144github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
150github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
151+github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
152+github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
145github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
153github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
146github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
154github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
147github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
155github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -184,6 +192,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
184github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
192github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
185github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
193github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
186github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
194github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
195+github.com/vmihailenco/msgpack/v4 v4.3.11 h1:Q47CePddpNGNhk4GCnAx9DDtASi2rasatE0cd26cZoE=
196+github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
197+github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
198+github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
187github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
199github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
188github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
200github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
189github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q=
201github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q=
@@ -201,8 +213,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
201golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
213golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
202golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
214golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
203golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
215golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
204−golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
205−golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
206golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
216golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
207golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
217golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
208golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
218golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -217,14 +227,14 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
217golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
227golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
218golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
228golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
219golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
229golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
220−golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
221golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
230golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
222golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
231golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
223golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
232golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
233+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
224golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
234golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
225golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
235golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
226−golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
236+golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
227−golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
237+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
228golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
238golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
229golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
239golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
230golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
240golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@@ -243,7 +253,6 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
243golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
253golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
244golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
254golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
245golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
255golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
246−golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
247golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
256golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
248golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
257golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
249golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
258golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -276,6 +285,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
276google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
285google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
277google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
286google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
278google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
287google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
288+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
289+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
279google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
290google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
280google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
291google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
281google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
292google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
oauthproxy_test.go+395−232
@@ -23,7 +23,7 @@ import (
23 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
23 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
24 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
24 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
25 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
25 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
26− "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
26+ sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
27 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
27 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
28 "github.com/oauth2-proxy/oauth2-proxy/providers"
28 "github.com/oauth2-proxy/oauth2-proxy/providers"
29 "github.com/stretchr/testify/assert"
29 "github.com/stretchr/testify/assert"
@@ -36,11 +36,12 @@ const (
36 // encoded version of this.
36 // encoded version of this.
37 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
37 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
38 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
38 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
39+ clientID = "3984n253984d7348dm8234yf982t"
40+ clientSecret = "gv3498mfc9t23y23974dm2394dm9"
39)
41)
40
42
41func init() {
43func init() {
42 logger.SetFlags(logger.Lshortfile)
44 logger.SetFlags(logger.Lshortfile)
43−
44}
45}
45
46
46type WebSocketOrRestHandler struct {
47type WebSocketOrRestHandler struct {
@@ -61,24 +62,30 @@ func TestWebSocketProxy(t *testing.T) {
61 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62 w.WriteHeader(200)
63 w.WriteHeader(200)
63 hostname, _, _ := net.SplitHostPort(r.Host)
64 hostname, _, _ := net.SplitHostPort(r.Host)
64− w.Write([]byte(hostname))
65+ _, err := w.Write([]byte(hostname))
66+ if err != nil {
67+ t.Fatal(err)
68+ }
65 }),
69 }),
66 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
70 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
71+ defer func(t *testing.T) {
67− defer ws.Close()
72+ if err := ws.Close(); err != nil {
73+ t.Fatal(err)
74+ }
75+ }(t)
68 var data []byte
76 var data []byte
69 err := websocket.Message.Receive(ws, &data)
77 err := websocket.Message.Receive(ws, &data)
70 if err != nil {
78 if err != nil {
71− t.Fatalf("err %s", err)
79+ t.Fatal(err)
72− return
73 }
80 }
74 err = websocket.Message.Send(ws, data)
81 err = websocket.Message.Send(ws, data)
75 if err != nil {
82 if err != nil {
76− t.Fatalf("err %s", err)
83+ t.Fatal(err)
77 }
84 }
78 }),
85 }),
79 }
86 }
80 backend := httptest.NewServer(&handler)
87 backend := httptest.NewServer(&handler)
81− defer backend.Close()
88+ t.Cleanup(backend.Close)
82
89
83 backendURL, _ := url.Parse(backend.URL)
90 backendURL, _ := url.Parse(backend.URL)
84
91
@@ -87,24 +94,24 @@ func TestWebSocketProxy(t *testing.T) {
87 opts.PassHostHeader = true
94 opts.PassHostHeader = true
88 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, opts, auth)
95 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, opts, auth)
89 frontend := httptest.NewServer(proxyHandler)
96 frontend := httptest.NewServer(proxyHandler)
90− defer frontend.Close()
97+ t.Cleanup(frontend.Close)
91
98
92 frontendURL, _ := url.Parse(frontend.URL)
99 frontendURL, _ := url.Parse(frontend.URL)
93 frontendWSURL := "ws://" + frontendURL.Host + "/"
100 frontendWSURL := "ws://" + frontendURL.Host + "/"
94
101
95 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
102 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
96 if err != nil {
103 if err != nil {
97− t.Fatalf("err %s", err)
104+ t.Fatal(err)
98 }
105 }
99 request := []byte("hello, world!")
106 request := []byte("hello, world!")
100 err = websocket.Message.Send(ws, request)
107 err = websocket.Message.Send(ws, request)
101 if err != nil {
108 if err != nil {
102− t.Fatalf("err %s", err)
109+ t.Fatal(err)
103 }
110 }
104 var response = make([]byte, 1024)
111 var response = make([]byte, 1024)
105− websocket.Message.Receive(ws, &response)
112+ err = websocket.Message.Receive(ws, &response)
106 if err != nil {
113 if err != nil {
107− t.Fatalf("err %s", err)
114+ t.Fatal(err)
108 }
115 }
109 if g, e := string(request), string(response); g != e {
116 if g, e := string(request), string(response); g != e {
110 t.Errorf("got body %q; expected %q", g, e)
117 t.Errorf("got body %q; expected %q", g, e)
@@ -123,9 +130,12 @@ func TestNewReverseProxy(t *testing.T) {
123 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
130 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124 w.WriteHeader(200)
131 w.WriteHeader(200)
125 hostname, _, _ := net.SplitHostPort(r.Host)
132 hostname, _, _ := net.SplitHostPort(r.Host)
126− w.Write([]byte(hostname))
133+ _, err := w.Write([]byte(hostname))
134+ if err != nil {
135+ t.Fatal(err)
136+ }
127 }))
137 }))
128− defer backend.Close()
138+ t.Cleanup(backend.Close)
129
139
130 backendURL, _ := url.Parse(backend.URL)
140 backendURL, _ := url.Parse(backend.URL)
131 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
141 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
@@ -135,7 +145,7 @@ func TestNewReverseProxy(t *testing.T) {
135 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
145 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
136 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
146 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
137 frontend := httptest.NewServer(proxyHandler)
147 frontend := httptest.NewServer(proxyHandler)
138− defer frontend.Close()
148+ t.Cleanup(frontend.Close)
139
149
140 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
150 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
141 res, _ := http.DefaultClient.Do(getReq)
151 res, _ := http.DefaultClient.Do(getReq)
@@ -151,20 +161,20 @@ func TestEncodedSlashes(t *testing.T) {
151 w.WriteHeader(200)
161 w.WriteHeader(200)
152 seen = r.RequestURI
162 seen = r.RequestURI
153 }))
163 }))
154− defer backend.Close()
164+ t.Cleanup(backend.Close)
155
165
156 b, _ := url.Parse(backend.URL)
166 b, _ := url.Parse(backend.URL)
157 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
167 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
158 setProxyDirector(proxyHandler)
168 setProxyDirector(proxyHandler)
159 frontend := httptest.NewServer(proxyHandler)
169 frontend := httptest.NewServer(proxyHandler)
160− defer frontend.Close()
170+ t.Cleanup(frontend.Close)
161
171
162 f, _ := url.Parse(frontend.URL)
172 f, _ := url.Parse(frontend.URL)
163 encodedPath := "/a%2Fb/?c=1"
173 encodedPath := "/a%2Fb/?c=1"
164 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
174 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
165 _, err := http.DefaultClient.Do(getReq)
175 _, err := http.DefaultClient.Do(getReq)
166 if err != nil {
176 if err != nil {
167− t.Fatalf("err %s", err)
177+ t.Fatal(err)
168 }
178 }
169 if seen != encodedPath {
179 if seen != encodedPath {
170 t.Errorf("got bad request %q expected %q", seen, encodedPath)
180 t.Errorf("got bad request %q expected %q", seen, encodedPath)
@@ -173,13 +183,13 @@ func TestEncodedSlashes(t *testing.T) {
173
183
174func TestRobotsTxt(t *testing.T) {
184func TestRobotsTxt(t *testing.T) {
175 opts := baseTestOptions()
185 opts := baseTestOptions()
176− opts.ClientID = "asdlkjx"
177− opts.ClientSecret = "alkgks"
178− opts.Cookie.Secret = rawCookieSecret
179− validation.Validate(opts)
186+ err := validation.Validate(opts)
187+ assert.NoError(t, err)
180
188
181 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
189 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
182− assert.NoError(t, err)
190+ if err != nil {
191+ t.Fatal(err)
192+ }
183 rw := httptest.NewRecorder()
193 rw := httptest.NewRecorder()
184 req, _ := http.NewRequest("GET", "/robots.txt", nil)
194 req, _ := http.NewRequest("GET", "/robots.txt", nil)
185 proxy.ServeHTTP(rw, req)
195 proxy.ServeHTTP(rw, req)
@@ -189,9 +199,6 @@ func TestRobotsTxt(t *testing.T) {
189
199
190func TestIsValidRedirect(t *testing.T) {
200func TestIsValidRedirect(t *testing.T) {
191 opts := baseTestOptions()
201 opts := baseTestOptions()
192− opts.ClientID = "skdlfj"
193− opts.ClientSecret = "fgkdsgj"
194− opts.Cookie.Secret = base64CookieSecret
195 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
202 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
196 opts.WhitelistDomains = []string{
203 opts.WhitelistDomains = []string{
197 "foo.bar",
204 "foo.bar",
@@ -201,10 +208,13 @@ func TestIsValidRedirect(t *testing.T) {
201 "anyport.bar:*",
208 "anyport.bar:*",
202 ".sub.anyport.bar:*",
209 ".sub.anyport.bar:*",
203 }
210 }
204− validation.Validate(opts)
211+ err := validation.Validate(opts)
212+ assert.NoError(t, err)
205
213
206 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
214 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
207− assert.NoError(t, err)
215+ if err != nil {
216+ t.Fatal(err)
217+ }
208
218
209 testCases := []struct {
219 testCases := []struct {
210 Desc, Redirect string
220 Desc, Redirect string
@@ -439,11 +449,7 @@ func TestIsValidRedirect(t *testing.T) {
439}
449}
440
450
441func TestOpenRedirects(t *testing.T) {
451func TestOpenRedirects(t *testing.T) {
442− opts := options.NewOptions()
452+ opts := baseTestOptions()
443− opts.ClientID = "skdlfj"
444− opts.ClientSecret = "fgkdsgj"
445− opts.Cookie.Secret = rawCookieSecret
446− opts.EmailDomains = []string{"*"}
447 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
453 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
448 opts.WhitelistDomains = []string{
454 opts.WhitelistDomains = []string{
449 "foo.bar",
455 "foo.bar",
@@ -458,13 +464,19 @@ func TestOpenRedirects(t *testing.T) {
458 assert.NoError(t, err)
464 assert.NoError(t, err)
459
465
460 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
466 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
461− assert.NoError(t, err)
467+ if err != nil {
468+ t.Fatal(err)
469+ }
462
470
463 file, err := os.Open("./test/openredirects.txt")
471 file, err := os.Open("./test/openredirects.txt")
464 if err != nil {
472 if err != nil {
465 t.Fatal(err)
473 t.Fatal(err)
466 }
474 }
475+ defer func(t *testing.T) {
467− defer file.Close()
476+ if err := file.Close(); err != nil {
477+ t.Fatal(err)
478+ }
479+ }(t)
468
480
469 scanner := bufio.NewScanner(file)
481 scanner := bufio.NewScanner(file)
470 for scanner.Scan() {
482 for scanner.Scan() {
@@ -544,22 +556,21 @@ func TestBasicAuthPassword(t *testing.T) {
544 }
556 }
545 }
557 }
546 w.WriteHeader(200)
558 w.WriteHeader(200)
547− w.Write([]byte(payload))
559+ _, err := w.Write([]byte(payload))
560+ if err != nil {
561+ t.Fatal(err)
562+ }
548 }))
563 }))
549 opts := baseTestOptions()
564 opts := baseTestOptions()
550 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
565 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
551− // The CookieSecret must be 32 bytes in order to create the AES
552− // cipher.
553− opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
554− opts.ClientID = "dlgkj"
555− opts.ClientSecret = "alkgret"
556 opts.Cookie.Secure = false
566 opts.Cookie.Secure = false
557 opts.PassBasicAuth = true
567 opts.PassBasicAuth = true
558 opts.SetBasicAuth = true
568 opts.SetBasicAuth = true
559 opts.PassUserHeaders = true
569 opts.PassUserHeaders = true
560 opts.PreferEmailToUser = true
570 opts.PreferEmailToUser = true
561 opts.BasicAuthPassword = "This is a secure password"
571 opts.BasicAuthPassword = "This is a secure password"
562− validation.Validate(opts)
572+ err := validation.Validate(opts)
573+ assert.NoError(t, err)
563
574
564 providerURL, _ := url.Parse(providerServer.URL)
575 providerURL, _ := url.Parse(providerServer.URL)
565 const emailAddress = "john.doe@example.com"
576 const emailAddress = "john.doe@example.com"
@@ -568,7 +579,9 @@ func TestBasicAuthPassword(t *testing.T) {
568 proxy, err := NewOAuthProxy(opts, func(email string) bool {
579 proxy, err := NewOAuthProxy(opts, func(email string) bool {
569 return email == emailAddress
580 return email == emailAddress
570 })
581 })
571− assert.NoError(t, err)
582+ if err != nil {
583+ t.Fatal(err)
584+ }
572
585
573 rw := httptest.NewRecorder()
586 rw := httptest.NewRecorder()
574 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
587 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
@@ -618,8 +631,8 @@ func TestBasicAuthWithEmail(t *testing.T) {
618 opts.PassUserHeaders = false
631 opts.PassUserHeaders = false
619 opts.PreferEmailToUser = false
632 opts.PreferEmailToUser = false
620 opts.BasicAuthPassword = "This is a secure password"
633 opts.BasicAuthPassword = "This is a secure password"
621− opts.Cookie.Secret = rawCookieSecret
622− validation.Validate(opts)
634+ err := validation.Validate(opts)
635+ assert.NoError(t, err)
623
636
624 const emailAddress = "john.doe@example.com"
637 const emailAddress = "john.doe@example.com"
625 const userName = "9fcab5c9b889a557"
638 const userName = "9fcab5c9b889a557"
@@ -641,7 +654,9 @@ func TestBasicAuthWithEmail(t *testing.T) {
641 proxy, err := NewOAuthProxy(opts, func(email string) bool {
654 proxy, err := NewOAuthProxy(opts, func(email string) bool {
642 return email == emailAddress
655 return email == emailAddress
643 })
656 })
644− assert.NoError(t, err)
657+ if err != nil {
658+ t.Fatal(err)
659+ }
645 proxy.addHeadersForProxying(rw, req, session)
660 proxy.addHeadersForProxying(rw, req, session)
646 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
661 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
647 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
662 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
@@ -655,7 +670,9 @@ func TestBasicAuthWithEmail(t *testing.T) {
655 proxy, err := NewOAuthProxy(opts, func(email string) bool {
670 proxy, err := NewOAuthProxy(opts, func(email string) bool {
656 return email == emailAddress
671 return email == emailAddress
657 })
672 })
658− assert.NoError(t, err)
673+ if err != nil {
674+ t.Fatal(err)
675+ }
659 proxy.addHeadersForProxying(rw, req, session)
676 proxy.addHeadersForProxying(rw, req, session)
660 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
677 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
661 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
678 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
@@ -664,11 +681,8 @@ func TestBasicAuthWithEmail(t *testing.T) {
664
681
665func TestPassUserHeadersWithEmail(t *testing.T) {
682func TestPassUserHeadersWithEmail(t *testing.T) {
666 opts := baseTestOptions()
683 opts := baseTestOptions()
667− opts.PassBasicAuth = false
668− opts.PassUserHeaders = true
669− opts.PreferEmailToUser = false
670− opts.Cookie.Secret = base64CookieSecret
671− validation.Validate(opts)
684+ err := validation.Validate(opts)
685+ assert.NoError(t, err)
672
686
673 const emailAddress = "john.doe@example.com"
687 const emailAddress = "john.doe@example.com"
674 const userName = "9fcab5c9b889a557"
688 const userName = "9fcab5c9b889a557"
@@ -686,7 +700,9 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
686 proxy, err := NewOAuthProxy(opts, func(email string) bool {
700 proxy, err := NewOAuthProxy(opts, func(email string) bool {
687 return email == emailAddress
701 return email == emailAddress
688 })
702 })
689− assert.NoError(t, err)
703+ if err != nil {
704+ t.Fatal(err)
705+ }
690 proxy.addHeadersForProxying(rw, req, session)
706 proxy.addHeadersForProxying(rw, req, session)
691 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
707 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
692 }
708 }
@@ -699,7 +715,9 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
699 proxy, err := NewOAuthProxy(opts, func(email string) bool {
715 proxy, err := NewOAuthProxy(opts, func(email string) bool {
700 return email == emailAddress
716 return email == emailAddress
701 })
717 })
702− assert.NoError(t, err)
718+ if err != nil {
719+ t.Fatal(err)
720+ }
703 proxy.addHeadersForProxying(rw, req, session)
721 proxy.addHeadersForProxying(rw, req, session)
704 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
722 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
705 }
723 }
@@ -716,10 +734,10 @@ type PassAccessTokenTestOptions struct {
716 ProxyUpstream string
734 ProxyUpstream string
717}
735}
718
736
719−func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
737+func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
720− t := &PassAccessTokenTest{}
738+ patt := &PassAccessTokenTest{}
721
739
722− t.providerServer = httptest.NewServer(
740+ patt.providerServer = httptest.NewServer(
723 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
741 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
724 var payload string
742 var payload string
725 switch r.URL.Path {
743 switch r.URL.Path {
@@ -732,35 +750,35 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
732 }
750 }
733 }
751 }
734 w.WriteHeader(200)
752 w.WriteHeader(200)
735− w.Write([]byte(payload))
753+ _, err := w.Write([]byte(payload))
754+ if err != nil {
755+ panic(err)
756+ }
736 }))
757 }))
737
758
738− t.opts = baseTestOptions()
759+ patt.opts = baseTestOptions()
739− t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
760+ patt.opts.Upstreams = append(patt.opts.Upstreams, patt.providerServer.URL)
740 if opts.ProxyUpstream != "" {
761 if opts.ProxyUpstream != "" {
741− t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
762+ patt.opts.Upstreams = append(patt.opts.Upstreams, opts.ProxyUpstream)
742− }
763+ }
743− // The CookieSecret must be 32 bytes in order to create the AES
744− // cipher.
745− t.opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
746− t.opts.ClientID = "slgkj"
747− t.opts.ClientSecret = "gfjgojl"
748− t.opts.Cookie.Secure = false
764+ patt.opts.Cookie.Secure = false
749− t.opts.PassAccessToken = opts.PassAccessToken
765+ patt.opts.PassAccessToken = opts.PassAccessToken
750− validation.Validate(t.opts)
766+ err := validation.Validate(patt.opts)
751−
767+ if err != nil {
768+ return nil, err
769+ }
770+
752− providerURL, _ := url.Parse(t.providerServer.URL)
771+ providerURL, _ := url.Parse(patt.providerServer.URL)
753 const emailAddress = "michael.bland@gsa.gov"
772 const emailAddress = "michael.bland@gsa.gov"
754
773
755− t.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
774+ patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
756− var err error
757− t.proxy, err = NewOAuthProxy(t.opts, func(email string) bool {
775+ patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
758 return email == emailAddress
776 return email == emailAddress
759 })
777 })
760 if err != nil {
778 if err != nil {
761− panic(err)
779+ return nil, err
762 }
780 }
763− return t
781+ return patt, nil
764}
782}
765
783
766func (patTest *PassAccessTokenTest) Close() {
784func (patTest *PassAccessTokenTest) Close() {
@@ -817,17 +835,20 @@ func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoin
817}
835}
818
836
819func TestForwardAccessTokenUpstream(t *testing.T) {
837func TestForwardAccessTokenUpstream(t *testing.T) {
820− patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
838+ patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
821 PassAccessToken: true,
839 PassAccessToken: true,
822 })
840 })
841+ if err != nil {
842+ t.Fatal(err)
843+ }
823− defer patTest.Close()
844+ t.Cleanup(patTest.Close)
824
845
825 // A successful validation will redirect and set the auth cookie.
846 // A successful validation will redirect and set the auth cookie.
826 code, cookie := patTest.getCallbackEndpoint()
847 code, cookie := patTest.getCallbackEndpoint()
827 if code != 302 {
848 if code != 302 {
828 t.Fatalf("expected 302; got %d", code)
849 t.Fatalf("expected 302; got %d", code)
829 }
850 }
830− assert.NotEqual(t, nil, cookie)
851+ assert.NotNil(t, cookie)
831
852
832 // Now we make a regular request; the access_token from the cookie is
853 // Now we make a regular request; the access_token from the cookie is
833 // forwarded as the "X-Forwarded-Access-Token" header. The token is
854 // forwarded as the "X-Forwarded-Access-Token" header. The token is
@@ -840,12 +861,14 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
840}
861}
841
862
842func TestStaticProxyUpstream(t *testing.T) {
863func TestStaticProxyUpstream(t *testing.T) {
843− patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
864+ patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
844 PassAccessToken: true,
865 PassAccessToken: true,
845 ProxyUpstream: "static://200/static-proxy",
866 ProxyUpstream: "static://200/static-proxy",
846 })
867 })
847−
868+ if err != nil {
869+ t.Fatal(err)
870+ }
848− defer patTest.Close()
871+ t.Cleanup(patTest.Close)
849
872
850 // A successful validation will redirect and set the auth cookie.
873 // A successful validation will redirect and set the auth cookie.
851 code, cookie := patTest.getCallbackEndpoint()
874 code, cookie := patTest.getCallbackEndpoint()
@@ -864,10 +887,13 @@ func TestStaticProxyUpstream(t *testing.T) {
864}
887}
865
888
866func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
889func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
867− patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
890+ patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
868 PassAccessToken: false,
891 PassAccessToken: false,
869 })
892 })
893+ if err != nil {
894+ t.Fatal(err)
895+ }
870− defer patTest.Close()
896+ t.Cleanup(patTest.Close)
871
897
872 // A successful validation will redirect and set the auth cookie.
898 // A successful validation will redirect and set the auth cookie.
873 code, cookie := patTest.getCallbackEndpoint()
899 code, cookie := patTest.getCallbackEndpoint()
@@ -895,27 +921,26 @@ type SignInPageTest struct {
895const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
921const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
896const signInSkipProvider = `>Found<`
922const signInSkipProvider = `>Found<`
897
923
898−func NewSignInPageTest(skipProvider bool) *SignInPageTest {
924+func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
899 var sipTest SignInPageTest
925 var sipTest SignInPageTest
900
926
901 sipTest.opts = baseTestOptions()
927 sipTest.opts = baseTestOptions()
902− sipTest.opts.Cookie.Secret = rawCookieSecret
903− sipTest.opts.ClientID = "lkdgj"
904− sipTest.opts.ClientSecret = "sgiufgoi"
905 sipTest.opts.SkipProviderButton = skipProvider
928 sipTest.opts.SkipProviderButton = skipProvider
906− validation.Validate(sipTest.opts)
929+ err := validation.Validate(sipTest.opts)
930+ if err != nil {
931+ return nil, err
932+ }
907
933
908− var err error
909 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
934 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
910 return true
935 return true
911 })
936 })
912 if err != nil {
937 if err != nil {
913− panic(err)
938+ return nil, err
914 }
939 }
915 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
940 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
916 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
941 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
917
942
918− return &sipTest
943+ return &sipTest, nil
919}
944}
920
945
921func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
946func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
@@ -926,7 +951,10 @@ func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
926}
951}
927
952
928func TestSignInPageIncludesTargetRedirect(t *testing.T) {
953func TestSignInPageIncludesTargetRedirect(t *testing.T) {
929− sipTest := NewSignInPageTest(false)
954+ sipTest, err := NewSignInPageTest(false)
955+ if err != nil {
956+ t.Fatal(err)
957+ }
930 const endpoint = "/some/random/endpoint"
958 const endpoint = "/some/random/endpoint"
931
959
932 code, body := sipTest.GetEndpoint(endpoint)
960 code, body := sipTest.GetEndpoint(endpoint)
@@ -944,7 +972,10 @@ func TestSignInPageIncludesTargetRedirect(t *testing.T) {
944}
972}
945
973
946func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
974func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
947− sipTest := NewSignInPageTest(false)
975+ sipTest, err := NewSignInPageTest(false)
976+ if err != nil {
977+ t.Fatal(err)
978+ }
948 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
979 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
949 assert.Equal(t, 200, code)
980 assert.Equal(t, 200, code)
950
981
@@ -959,8 +990,12 @@ func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
959}
990}
960
991
961func TestSignInPageSkipProvider(t *testing.T) {
992func TestSignInPageSkipProvider(t *testing.T) {
962− sipTest := NewSignInPageTest(true)
993+ sipTest, err := NewSignInPageTest(true)
994+ if err != nil {
995+ t.Fatal(err)
996+ }
997+
963− const endpoint = "/some/random/endpoint"
998+ endpoint := "/some/random/endpoint"
964
999
965 code, body := sipTest.GetEndpoint(endpoint)
1000 code, body := sipTest.GetEndpoint(endpoint)
966 assert.Equal(t, 302, code)
1001 assert.Equal(t, 302, code)
@@ -973,8 +1008,12 @@ func TestSignInPageSkipProvider(t *testing.T) {
973}
1008}
974
1009
975func TestSignInPageSkipProviderDirect(t *testing.T) {
1010func TestSignInPageSkipProviderDirect(t *testing.T) {
976− sipTest := NewSignInPageTest(true)
1011+ sipTest, err := NewSignInPageTest(true)
1012+ if err != nil {
1013+ t.Fatal(err)
1014+ }
1015+
977− const endpoint = "/sign_in"
1016+ endpoint := "/sign_in"
978
1017
979 code, body := sipTest.GetEndpoint(endpoint)
1018 code, body := sipTest.GetEndpoint(endpoint)
980 assert.Equal(t, 302, code)
1019 assert.Equal(t, 302, code)
@@ -1000,27 +1039,26 @@ type ProcessCookieTestOpts struct {
1000
1039
1001type OptionsModifier func(*options.Options)
1040type OptionsModifier func(*options.Options)
1002
1041
1003−func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
1042+func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1004 var pcTest ProcessCookieTest
1043 var pcTest ProcessCookieTest
1005
1044
1006 pcTest.opts = baseTestOptions()
1045 pcTest.opts = baseTestOptions()
1007 for _, modifier := range modifiers {
1046 for _, modifier := range modifiers {
1008 modifier(pcTest.opts)
1047 modifier(pcTest.opts)
1009 }
1048 }
1010− pcTest.opts.ClientID = "asdfljk"
1011− pcTest.opts.ClientSecret = "lkjfdsig"
1012− pcTest.opts.Cookie.Secret = "0123456789abcdef0123456789abcdef"
1013 // First, set the CookieRefresh option so proxy.AesCipher is created,
1049 // First, set the CookieRefresh option so proxy.AesCipher is created,
1014 // needed to encrypt the access_token.
1050 // needed to encrypt the access_token.
1015 pcTest.opts.Cookie.Refresh = time.Hour
1051 pcTest.opts.Cookie.Refresh = time.Hour
1016− validation.Validate(pcTest.opts)
1052+ err := validation.Validate(pcTest.opts)
1053+ if err != nil {
1054+ return nil, err
1055+ }
1017
1056
1018− var err error
1019 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1057 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1020 return pcTest.validateUser
1058 return pcTest.validateUser
1021 })
1059 })
1022 if err != nil {
1060 if err != nil {
1023− panic(err)
1061+ return nil, err
1024 }
1062 }
1025 pcTest.proxy.provider = &TestProvider{
1063 pcTest.proxy.provider = &TestProvider{
1026 ValidToken: opts.providerValidateCookieResponse,
1064 ValidToken: opts.providerValidateCookieResponse,
@@ -1032,16 +1070,16 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
1032 pcTest.rw = httptest.NewRecorder()
1070 pcTest.rw = httptest.NewRecorder()
1033 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1071 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1034 pcTest.validateUser = true
1072 pcTest.validateUser = true
1035− return &pcTest
1073+ return &pcTest, nil
1036}
1074}
1037
1075
1038−func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
1076+func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
1039 return NewProcessCookieTest(ProcessCookieTestOpts{
1077 return NewProcessCookieTest(ProcessCookieTestOpts{
1040 providerValidateCookieResponse: true,
1078 providerValidateCookieResponse: true,
1041 })
1079 })
1042}
1080}
1043
1081
1044−func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
1082+func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1045 return NewProcessCookieTest(ProcessCookieTestOpts{
1083 return NewProcessCookieTest(ProcessCookieTestOpts{
1046 providerValidateCookieResponse: true,
1084 providerValidateCookieResponse: true,
1047 }, modifiers...)
1085 }, modifiers...)
@@ -1063,37 +1101,51 @@ func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error)
1063}
1101}
1064
1102
1065func TestLoadCookiedSession(t *testing.T) {
1103func TestLoadCookiedSession(t *testing.T) {
1066− pcTest := NewProcessCookieTestWithDefaults()
1104+ pcTest, err := NewProcessCookieTestWithDefaults()
1105+ if err != nil {
1106+ t.Fatal(err)
1107+ }
1067
1108
1068 created := time.Now()
1109 created := time.Now()
1069 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1110 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1070− pcTest.SaveSession(startSession)
1111+ err = pcTest.SaveSession(startSession)
1112+ assert.NoError(t, err)
1071
1113
1072 session, err := pcTest.LoadCookiedSession()
1114 session, err := pcTest.LoadCookiedSession()
1073− assert.Equal(t, nil, err)
1115+ if err != nil {
1116+ t.Fatal(err)
1117+ }
1074 assert.Equal(t, startSession.Email, session.Email)
1118 assert.Equal(t, startSession.Email, session.Email)
1075 assert.Equal(t, "", session.User)
1119 assert.Equal(t, "", session.User)
1076 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1120 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1077}
1121}
1078
1122
1079func TestProcessCookieNoCookieError(t *testing.T) {
1123func TestProcessCookieNoCookieError(t *testing.T) {
1080− pcTest := NewProcessCookieTestWithDefaults()
1124+ pcTest, err := NewProcessCookieTestWithDefaults()
1125+ if err != nil {
1126+ t.Fatal(err)
1127+ }
1081
1128
1082 session, err := pcTest.LoadCookiedSession()
1129 session, err := pcTest.LoadCookiedSession()
1083− assert.Equal(t, "cookie \"_oauth2_proxy\" not present", err.Error())
1130+ assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1084 if session != nil {
1131 if session != nil {
1085 t.Errorf("expected nil session. got %#v", session)
1132 t.Errorf("expected nil session. got %#v", session)
1086 }
1133 }
1087}
1134}
1088
1135
1089func TestProcessCookieRefreshNotSet(t *testing.T) {
1136func TestProcessCookieRefreshNotSet(t *testing.T) {
1090− pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1137+ pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1091 opts.Cookie.Expire = time.Duration(23) * time.Hour
1138 opts.Cookie.Expire = time.Duration(23) * time.Hour
1092 })
1139 })
1140+ if err != nil {
1141+ t.Fatal(err)
1142+ }
1143+
1093 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1144 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1094
1145
1095 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1146 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1096− pcTest.SaveSession(startSession)
1147+ err = pcTest.SaveSession(startSession)
1148+ assert.NoError(t, err)
1097
1149
1098 session, err := pcTest.LoadCookiedSession()
1150 session, err := pcTest.LoadCookiedSession()
1099 assert.Equal(t, nil, err)
1151 assert.Equal(t, nil, err)
@@ -1104,12 +1156,17 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
1104}
1156}
1105
1157
1106func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1158func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1107− pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1159+ pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1108 opts.Cookie.Expire = time.Duration(24) * time.Hour
1160 opts.Cookie.Expire = time.Duration(24) * time.Hour
1109 })
1161 })
1162+ if err != nil {
1163+ t.Fatal(err)
1164+ }
1165+
1110 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1166 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1111 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1167 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1112− pcTest.SaveSession(startSession)
1168+ err = pcTest.SaveSession(startSession)
1169+ assert.NoError(t, err)
1113
1170
1114 session, err := pcTest.LoadCookiedSession()
1171 session, err := pcTest.LoadCookiedSession()
1115 assert.NotEqual(t, nil, err)
1172 assert.NotEqual(t, nil, err)
@@ -1119,12 +1176,17 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1119}
1176}
1120
1177
1121func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1178func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1122− pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1179+ pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1123 opts.Cookie.Expire = time.Duration(24) * time.Hour
1180 opts.Cookie.Expire = time.Duration(24) * time.Hour
1124 })
1181 })
1182+ if err != nil {
1183+ t.Fatal(err)
1184+ }
1185+
1125 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1186 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1126 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1187 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1127− pcTest.SaveSession(startSession)
1188+ err = pcTest.SaveSession(startSession)
1189+ assert.NoError(t, err)
1128
1190
1129 pcTest.proxy.CookieRefresh = time.Hour
1191 pcTest.proxy.CookieRefresh = time.Hour
1130 session, err := pcTest.LoadCookiedSession()
1192 session, err := pcTest.LoadCookiedSession()
@@ -1134,18 +1196,26 @@ func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1134 }
1196 }
1135}
1197}
1136
1198
1137−func NewUserInfoEndpointTest() *ProcessCookieTest {
1199+func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1138− pcTest := NewProcessCookieTestWithDefaults()
1200+ pcTest, err := NewProcessCookieTestWithDefaults()
1201+ if err != nil {
1202+ return nil, err
1203+ }
1139 pcTest.req, _ = http.NewRequest("GET",
1204 pcTest.req, _ = http.NewRequest("GET",
1140 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1205 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1141− return pcTest
1206+ return pcTest, nil
1142}
1207}
1143
1208
1144func TestUserInfoEndpointAccepted(t *testing.T) {
1209func TestUserInfoEndpointAccepted(t *testing.T) {
1145− test := NewUserInfoEndpointTest()
1210+ test, err := NewUserInfoEndpointTest()
1211+ if err != nil {
1212+ t.Fatal(err)
1213+ }
1214+
1146 startSession := &sessions.SessionState{
1215 startSession := &sessions.SessionState{
1147 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1216 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1148− test.SaveSession(startSession)
1217+ err = test.SaveSession(startSession)
1218+ assert.NoError(t, err)
1149
1219
1150 test.proxy.ServeHTTP(test.rw, test.req)
1220 test.proxy.ServeHTTP(test.rw, test.req)
1151 assert.Equal(t, http.StatusOK, test.rw.Code)
1221 assert.Equal(t, http.StatusOK, test.rw.Code)
@@ -1154,25 +1224,36 @@ func TestUserInfoEndpointAccepted(t *testing.T) {
1154}
1224}
1155
1225
1156func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1226func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1157− test := NewUserInfoEndpointTest()
1227+ test, err := NewUserInfoEndpointTest()
1228+ if err != nil {
1229+ t.Fatal(err)
1230+ }
1158
1231
1159 test.proxy.ServeHTTP(test.rw, test.req)
1232 test.proxy.ServeHTTP(test.rw, test.req)
1160 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1233 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1161}
1234}
1162
1235
1163−func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
1236+func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1164− pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1237+ pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1238+ if err != nil {
1239+ return nil, err
1240+ }
1165 pcTest.req, _ = http.NewRequest("GET",
1241 pcTest.req, _ = http.NewRequest("GET",
1166 pcTest.opts.ProxyPrefix+"/auth", nil)
1242 pcTest.opts.ProxyPrefix+"/auth", nil)
1167− return pcTest
1243+ return pcTest, nil
1168}
1244}
1169
1245
1170func TestAuthOnlyEndpointAccepted(t *testing.T) {
1246func TestAuthOnlyEndpointAccepted(t *testing.T) {
1171− test := NewAuthOnlyEndpointTest()
1247+ test, err := NewAuthOnlyEndpointTest()
1248+ if err != nil {
1249+ t.Fatal(err)
1250+ }
1251+
1172 created := time.Now()
1252 created := time.Now()
1173 startSession := &sessions.SessionState{
1253 startSession := &sessions.SessionState{
1174 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1254 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1175− test.SaveSession(startSession)
1255+ err = test.SaveSession(startSession)
1256+ assert.NoError(t, err)
1176
1257
1177 test.proxy.ServeHTTP(test.rw, test.req)
1258 test.proxy.ServeHTTP(test.rw, test.req)
1178 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1259 assert.Equal(t, http.StatusAccepted, test.rw.Code)
@@ -1181,7 +1262,10 @@ func TestAuthOnlyEndpointAccepted(t *testing.T) {
1181}
1262}
1182
1263
1183func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1264func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1184− test := NewAuthOnlyEndpointTest()
1265+ test, err := NewAuthOnlyEndpointTest()
1266+ if err != nil {
1267+ t.Fatal(err)
1268+ }
1185
1269
1186 test.proxy.ServeHTTP(test.rw, test.req)
1270 test.proxy.ServeHTTP(test.rw, test.req)
1187 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1271 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
@@ -1190,13 +1274,18 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1190}
1274}
1191
1275
1192func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1276func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1193− test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1277+ test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1194 opts.Cookie.Expire = time.Duration(24) * time.Hour
1278 opts.Cookie.Expire = time.Duration(24) * time.Hour
1195 })
1279 })
1280+ if err != nil {
1281+ t.Fatal(err)
1282+ }
1283+
1196 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1284 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1197 startSession := &sessions.SessionState{
1285 startSession := &sessions.SessionState{
1198 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1286 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1199− test.SaveSession(startSession)
1287+ err = test.SaveSession(startSession)
1288+ assert.NoError(t, err)
1200
1289
1201 test.proxy.ServeHTTP(test.rw, test.req)
1290 test.proxy.ServeHTTP(test.rw, test.req)
1202 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1291 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
@@ -1205,11 +1294,16 @@ func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1205}
1294}
1206
1295
1207func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1296func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1208− test := NewAuthOnlyEndpointTest()
1297+ test, err := NewAuthOnlyEndpointTest()
1298+ if err != nil {
1299+ t.Fatal(err)
1300+ }
1301+
1209 created := time.Now()
1302 created := time.Now()
1210 startSession := &sessions.SessionState{
1303 startSession := &sessions.SessionState{
1211 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1304 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1212− test.SaveSession(startSession)
1305+ err = test.SaveSession(startSession)
1306+ assert.NoError(t, err)
1213 test.validateUser = false
1307 test.validateUser = false
1214
1308
1215 test.proxy.ServeHTTP(test.rw, test.req)
1309 test.proxy.ServeHTTP(test.rw, test.req)
@@ -1224,15 +1318,13 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1224 pcTest.opts = baseTestOptions()
1318 pcTest.opts = baseTestOptions()
1225 pcTest.opts.SetXAuthRequest = true
1319 pcTest.opts.SetXAuthRequest = true
1226 err := validation.Validate(pcTest.opts)
1320 err := validation.Validate(pcTest.opts)
1227− if err != nil {
1321+ assert.NoError(t, err)
1228− panic(err)
1229− }
1230
1322
1231 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1323 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1232 return pcTest.validateUser
1324 return pcTest.validateUser
1233 })
1325 })
1234 if err != nil {
1326 if err != nil {
1235− panic(err)
1327+ t.Fatal(err)
1236 }
1328 }
1237 pcTest.proxy.provider = &TestProvider{
1329 pcTest.proxy.provider = &TestProvider{
1238 ValidToken: true,
1330 ValidToken: true,
@@ -1247,7 +1339,8 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1247 created := time.Now()
1339 created := time.Now()
1248 startSession := &sessions.SessionState{
1340 startSession := &sessions.SessionState{
1249 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1341 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1250− pcTest.SaveSession(startSession)
1342+ err = pcTest.SaveSession(startSession)
1343+ assert.NoError(t, err)
1251
1344
1252 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1345 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1253 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1346 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
@@ -1261,14 +1354,14 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1261 pcTest.opts = baseTestOptions()
1354 pcTest.opts = baseTestOptions()
1262 pcTest.opts.SetXAuthRequest = true
1355 pcTest.opts.SetXAuthRequest = true
1263 pcTest.opts.SetBasicAuth = true
1356 pcTest.opts.SetBasicAuth = true
1264− validation.Validate(pcTest.opts)
1357+ err := validation.Validate(pcTest.opts)
1358+ assert.NoError(t, err)
1265
1359
1266− var err error
1267 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1360 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1268 return pcTest.validateUser
1361 return pcTest.validateUser
1269 })
1362 })
1270 if err != nil {
1363 if err != nil {
1271− panic(err)
1364+ t.Fatal(err)
1272 }
1365 }
1273 pcTest.proxy.provider = &TestProvider{
1366 pcTest.proxy.provider = &TestProvider{
1274 ValidToken: true,
1367 ValidToken: true,
@@ -1283,7 +1376,8 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1283 created := time.Now()
1376 created := time.Now()
1284 startSession := &sessions.SessionState{
1377 startSession := &sessions.SessionState{
1285 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1378 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1286− pcTest.SaveSession(startSession)
1379+ err = pcTest.SaveSession(startSession)
1380+ assert.NoError(t, err)
1287
1381
1288 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1382 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1289 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1383 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
@@ -1299,14 +1393,14 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1299 pcTest.opts = baseTestOptions()
1393 pcTest.opts = baseTestOptions()
1300 pcTest.opts.SetXAuthRequest = true
1394 pcTest.opts.SetXAuthRequest = true
1301 pcTest.opts.SetBasicAuth = false
1395 pcTest.opts.SetBasicAuth = false
1302− validation.Validate(pcTest.opts)
1396+ err := validation.Validate(pcTest.opts)
1397+ assert.NoError(t, err)
1303
1398
1304− var err error
1305 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1399 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1306 return pcTest.validateUser
1400 return pcTest.validateUser
1307 })
1401 })
1308 if err != nil {
1402 if err != nil {
1309− panic(err)
1403+ t.Fatal(err)
1310 }
1404 }
1311 pcTest.proxy.provider = &TestProvider{
1405 pcTest.proxy.provider = &TestProvider{
1312 ValidToken: true,
1406 ValidToken: true,
@@ -1321,7 +1415,8 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1321 created := time.Now()
1415 created := time.Now()
1322 startSession := &sessions.SessionState{
1416 startSession := &sessions.SessionState{
1323 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1417 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1324− pcTest.SaveSession(startSession)
1418+ err = pcTest.SaveSession(startSession)
1419+ assert.NoError(t, err)
1325
1420
1326 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1421 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1327 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1422 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
@@ -1333,20 +1428,26 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1333func TestAuthSkippedForPreflightRequests(t *testing.T) {
1428func TestAuthSkippedForPreflightRequests(t *testing.T) {
1334 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1429 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1335 w.WriteHeader(200)
1430 w.WriteHeader(200)
1336− w.Write([]byte("response"))
1431+ _, err := w.Write([]byte("response"))
1432+ if err != nil {
1433+ t.Fatal(err)
1434+ }
1337 }))
1435 }))
1338− defer upstream.Close()
1436+ t.Cleanup(upstream.Close)
1339
1437
1340 opts := baseTestOptions()
1438 opts := baseTestOptions()
1341 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1439 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1342 opts.SkipAuthPreflight = true
1440 opts.SkipAuthPreflight = true
1343− validation.Validate(opts)
1441+ err := validation.Validate(opts)
1442+ assert.NoError(t, err)
1344
1443
1345 upstreamURL, _ := url.Parse(upstream.URL)
1444 upstreamURL, _ := url.Parse(upstream.URL)
1346 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1445 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1347
1446
1348 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1447 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1349− assert.NoError(t, err)
1448+ if err != nil {
1449+ t.Fatal(err)
1450+ }
1350 rw := httptest.NewRecorder()
1451 rw := httptest.NewRecorder()
1351 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1452 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1352 proxy.ServeHTTP(rw, req)
1453 proxy.ServeHTTP(rw, req)
@@ -1361,16 +1462,25 @@ type SignatureAuthenticator struct {
1361
1462
1362func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1463func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1363 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1464 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1465+
1466+ var msg string
1467+ switch result {
1364− if result == hmacauth.ResultNoSignature {
1468+ case hmacauth.ResultNoSignature:
1365− w.Write([]byte("no signature received"))
1469+ msg = "no signature received"
1366− } else if result == hmacauth.ResultMatch {
1470+ case hmacauth.ResultMatch:
1367− w.Write([]byte("signatures match"))
1471+ msg = "signatures match"
1368− } else if result == hmacauth.ResultMismatch {
1472+ case hmacauth.ResultMismatch:
1473+ msg = fmt.Sprintf(
1369− w.Write([]byte("signatures do not match:" +
1474+ "signatures do not match:\n received: %s\n computed: %s",
1370− "\n received: " + headerSig +
1475+ headerSig,
1371− "\n computed: " + computedSig))
1476+ computedSig)
1372− } else {
1477+ default:
1373− panic("Unknown result value: " + result.String())
1478+ panic("unknown result value: " + result.String())
1479+ }
1480+
1481+ _, err := w.Write([]byte(msg))
1482+ if err != nil {
1483+ panic(err)
1374 }
1484 }
1375}
1485}
1376
1486
@@ -1384,24 +1494,30 @@ type SignatureTest struct {
1384 authenticator *SignatureAuthenticator
1494 authenticator *SignatureAuthenticator
1385}
1495}
1386
1496
1387−func NewSignatureTest() *SignatureTest {
1497+func NewSignatureTest() (*SignatureTest, error) {
1388 opts := baseTestOptions()
1498 opts := baseTestOptions()
1389− opts.Cookie.Secret = rawCookieSecret
1390− opts.ClientID = "client ID"
1391− opts.ClientSecret = "client secret"
1392 opts.EmailDomains = []string{"acm.org"}
1499 opts.EmailDomains = []string{"acm.org"}
1393
1500
1394 authenticator := &SignatureAuthenticator{}
1501 authenticator := &SignatureAuthenticator{}
1395 upstream := httptest.NewServer(
1502 upstream := httptest.NewServer(
1396 http.HandlerFunc(authenticator.Authenticate))
1503 http.HandlerFunc(authenticator.Authenticate))
1397− upstreamURL, _ := url.Parse(upstream.URL)
1504+ upstreamURL, err := url.Parse(upstream.URL)
1505+ if err != nil {
1506+ return nil, err
1507+ }
1398 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1508 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1399
1509
1400 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1510 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1401− w.Write([]byte(`{"access_token": "my_auth_token"}`))
1511+ _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1512+ if err != nil {
1513+ panic(err)
1514+ }
1402 }
1515 }
1403 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1516 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1404− providerURL, _ := url.Parse(provider.URL)
1517+ providerURL, err := url.Parse(provider.URL)
1518+ if err != nil {
1519+ return nil, err
1520+ }
1405 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1521 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1406
1522
1407 return &SignatureTest{
1523 return &SignatureTest{
@@ -1412,7 +1528,7 @@ func NewSignatureTest() *SignatureTest {
1412 make(http.Header),
1528 make(http.Header),
1413 httptest.NewRecorder(),
1529 httptest.NewRecorder(),
1414 authenticator,
1530 authenticator,
1415− }
1531+ }, nil
1416}
1532}
1417
1533
1418func (st *SignatureTest) Close() {
1534func (st *SignatureTest) Close() {
@@ -1436,14 +1552,14 @@ func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1436 return 0, io.EOF
1552 return 0, io.EOF
1437}
1553}
1438
1554
1439−func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1555+func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1440 err := validation.Validate(st.opts)
1556 err := validation.Validate(st.opts)
1441 if err != nil {
1557 if err != nil {
1442− panic(err)
1558+ return err
1443 }
1559 }
1444 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1560 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1445 if err != nil {
1561 if err != nil {
1446− panic(err)
1562+ return err
1447 }
1563 }
1448
1564
1449 var bodyBuf io.ReadCloser
1565 var bodyBuf io.ReadCloser
@@ -1457,7 +1573,7 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1457 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1573 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1458 err = proxy.SaveSession(st.rw, req, state)
1574 err = proxy.SaveSession(st.rw, req, state)
1459 if err != nil {
1575 if err != nil {
1460− panic(err)
1576+ return err
1461 }
1577 }
1462 for _, c := range st.rw.Result().Cookies() {
1578 for _, c := range st.rw.Result().Cookies() {
1463 req.AddCookie(c)
1579 req.AddCookie(c)
@@ -1466,33 +1582,52 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1466 st.authenticator.auth = hmacauth.NewHmacAuth(
1582 st.authenticator.auth = hmacauth.NewHmacAuth(
1467 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1583 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1468 proxy.ServeHTTP(st.rw, req)
1584 proxy.ServeHTTP(st.rw, req)
1469−}
1470−
1471−func TestNoRequestSignature(t *testing.T) {
1472− st := NewSignatureTest()
1473− defer st.Close()
1474− st.MakeRequestWithExpectedKey("GET", "", "")
1475− assert.Equal(t, 200, st.rw.Code)
1476− assert.Equal(t, st.rw.Body.String(), "no signature received")
1477−}
1478
1585
1479−func TestRequestSignatureGetRequest(t *testing.T) {
1586+ return nil
1480− st := NewSignatureTest()
1481− defer st.Close()
1482− st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
1483− st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
1484− assert.Equal(t, 200, st.rw.Code)
1485− assert.Equal(t, st.rw.Body.String(), "signatures match")
1486}
1587}
1487
1588
1488−func TestRequestSignaturePostRequest(t *testing.T) {
1589+func TestRequestSignature(t *testing.T) {
1590+ testCases := map[string]struct {
1591+ method string
1592+ body string
1593+ key string
1594+ resp string
1595+ }{
1596+ "No request signature": {
1597+ method: "GET",
1598+ body: "",
1599+ key: "",
1600+ resp: "no signature received",
1601+ },
1602+ "Get request": {
1603+ method: "GET",
1604+ body: "",
1605+ key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1606+ resp: "signatures match",
1607+ },
1608+ "Post request": {
1609+ method: "POST",
1610+ body: `{ "hello": "world!" }`,
1611+ key: "d90df39e2d19282840252612dd7c81421a372f61",
1612+ resp: "signatures match",
1613+ },
1614+ }
1615+ for name, tc := range testCases {
1616+ t.Run(name, func(t *testing.T) {
1489− st := NewSignatureTest()
1617+ st, err := NewSignatureTest()
1618+ if err != nil {
1619+ t.Fatal(err)
1620+ }
1490− defer st.Close()
1621+ t.Cleanup(st.Close)
1622+ if tc.key != "" {
1491− st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
1623+ st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1492− payload := `{ "hello": "world!" }`
1624+ }
1493− st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
1625+ err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1626+ assert.NoError(t, err)
1494− assert.Equal(t, 200, st.rw.Code)
1627+ assert.Equal(t, 200, st.rw.Code)
1495− assert.Equal(t, st.rw.Body.String(), "signatures match")
1628+ assert.Equal(t, tc.resp, st.rw.Body.String())
1629+ })
1630+ }
1496}
1631}
1497
1632
1498func TestGetRedirect(t *testing.T) {
1633func TestGetRedirect(t *testing.T) {
@@ -1501,7 +1636,9 @@ func TestGetRedirect(t *testing.T) {
1501 assert.NoError(t, err)
1636 assert.NoError(t, err)
1502 require.NotEmpty(t, opts.ProxyPrefix)
1637 require.NotEmpty(t, opts.ProxyPrefix)
1503 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1638 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1504− assert.NoError(t, err)
1639+ if err != nil {
1640+ t.Fatal(err)
1641+ }
1505
1642
1506 tests := []struct {
1643 tests := []struct {
1507 name string
1644 name string
@@ -1535,22 +1672,21 @@ type ajaxRequestTest struct {
1535 proxy *OAuthProxy
1672 proxy *OAuthProxy
1536}
1673}
1537
1674
1538−func newAjaxRequestTest() *ajaxRequestTest {
1675+func newAjaxRequestTest() (*ajaxRequestTest, error) {
1539 test := &ajaxRequestTest{}
1676 test := &ajaxRequestTest{}
1540 test.opts = baseTestOptions()
1677 test.opts = baseTestOptions()
1541− test.opts.Cookie.Secret = base64CookieSecret
1542− test.opts.ClientID = "gkljfdl"
1543− test.opts.ClientSecret = "sdflkjs"
1544− validation.Validate(test.opts)
1678+ err := validation.Validate(test.opts)
1679+ if err != nil {
1680+ return nil, err
1681+ }
1545
1682
1546− var err error
1547 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1683 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1548 return true
1684 return true
1549 })
1685 })
1550 if err != nil {
1686 if err != nil {
1551− panic(err)
1687+ return nil, err
1552 }
1688 }
1553− return test
1689+ return test, nil
1554}
1690}
1555
1691
1556func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1692func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
@@ -1565,7 +1701,10 @@ func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (i
1565}
1701}
1566
1702
1567func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1703func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1568− test := newAjaxRequestTest()
1704+ test, err := newAjaxRequestTest()
1705+ if err != nil {
1706+ t.Fatal(err)
1707+ }
1569 endpoint := "/test"
1708 endpoint := "/test"
1570
1709
1571 code, rh, err := test.getEndpoint(endpoint, header)
1710 code, rh, err := test.getEndpoint(endpoint, header)
@@ -1589,7 +1728,10 @@ func TestAjaxUnauthorizedRequest2(t *testing.T) {
1589}
1728}
1590
1729
1591func TestAjaxForbiddendRequest(t *testing.T) {
1730func TestAjaxForbiddendRequest(t *testing.T) {
1592− test := newAjaxRequestTest()
1731+ test, err := newAjaxRequestTest()
1732+ if err != nil {
1733+ t.Fatal(err)
1734+ }
1593 endpoint := "/test"
1735 endpoint := "/test"
1594 header := make(http.Header)
1736 header := make(http.Header)
1595 code, rh, err := test.getEndpoint(endpoint, header)
1737 code, rh, err := test.getEndpoint(endpoint, header)
@@ -1604,8 +1746,14 @@ func TestClearSplitCookie(t *testing.T) {
1604 opts.Cookie.Secret = base64CookieSecret
1746 opts.Cookie.Secret = base64CookieSecret
1605 opts.Cookie.Name = "oauth2"
1747 opts.Cookie.Name = "oauth2"
1606 opts.Cookie.Domains = []string{"abc"}
1748 opts.Cookie.Domains = []string{"abc"}
1749+ err := validation.Validate(opts)
1750+ assert.NoError(t, err)
1751+
1607− store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1752+ store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1608− assert.Equal(t, nil, err)
1753+ if err != nil {
1754+ t.Fatal(err)
1755+ }
1756+
1609 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1757 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1610 var rw = httptest.NewRecorder()
1758 var rw = httptest.NewRecorder()
1611 req := httptest.NewRequest("get", "/", nil)
1759 req := httptest.NewRequest("get", "/", nil)
@@ -1623,7 +1771,8 @@ func TestClearSplitCookie(t *testing.T) {
1623 Value: "oauth2_1",
1771 Value: "oauth2_1",
1624 })
1772 })
1625
1773
1626− p.ClearSessionCookie(rw, req)
1774+ err = p.ClearSessionCookie(rw, req)
1775+ assert.NoError(t, err)
1627 header := rw.Header()
1776 header := rw.Header()
1628
1777
1629 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1778 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
@@ -1633,8 +1782,11 @@ func TestClearSingleCookie(t *testing.T) {
1633 opts := baseTestOptions()
1782 opts := baseTestOptions()
1634 opts.Cookie.Name = "oauth2"
1783 opts.Cookie.Name = "oauth2"
1635 opts.Cookie.Domains = []string{"abc"}
1784 opts.Cookie.Domains = []string{"abc"}
1636− store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1785+ store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1637− assert.Equal(t, nil, err)
1786+ if err != nil {
1787+ t.Fatal(err)
1788+ }
1789+
1638 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1790 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1639 var rw = httptest.NewRecorder()
1791 var rw = httptest.NewRecorder()
1640 req := httptest.NewRequest("get", "/", nil)
1792 req := httptest.NewRequest("get", "/", nil)
@@ -1648,7 +1800,8 @@ func TestClearSingleCookie(t *testing.T) {
1648 Value: "oauth2",
1800 Value: "oauth2",
1649 })
1801 })
1650
1802
1651− p.ClearSessionCookie(rw, req)
1803+ err = p.ClearSessionCookie(rw, req)
1804+ assert.NoError(t, err)
1652 header := rw.Header()
1805 header := rw.Header()
1653
1806
1654 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1807 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
@@ -1686,13 +1839,16 @@ func TestGetJwtSession(t *testing.T) {
1686 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1839 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1687 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1840 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1688
1841
1689− test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1842+ test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1690 opts.PassAuthorization = true
1843 opts.PassAuthorization = true
1691 opts.SetAuthorization = true
1844 opts.SetAuthorization = true
1692 opts.SetXAuthRequest = true
1845 opts.SetXAuthRequest = true
1693 opts.SkipJwtBearerTokens = true
1846 opts.SkipJwtBearerTokens = true
1694 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1847 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1695 })
1848 })
1849+ if err != nil {
1850+ t.Fatal(err)
1851+ }
1696 tp, _ := test.proxy.provider.(*TestProvider)
1852 tp, _ := test.proxy.provider.(*TestProvider)
1697 tp.GroupValidator = func(s string) bool {
1853 tp.GroupValidator = func(s string) bool {
1698 return true
1854 return true
@@ -1705,7 +1861,8 @@ func TestGetJwtSession(t *testing.T) {
1705
1861
1706 // Bearer
1862 // Bearer
1707 expires := time.Unix(1912151821, 0)
1863 expires := time.Unix(1912151821, 0)
1708− session, _ := test.proxy.GetJwtSession(test.req)
1864+ session, err := test.proxy.GetJwtSession(test.req)
1865+ assert.NoError(t, err)
1709 assert.Equal(t, session.User, "1234567890")
1866 assert.Equal(t, session.User, "1234567890")
1710 assert.Equal(t, session.Email, "john@example.com")
1867 assert.Equal(t, session.Email, "john@example.com")
1711 assert.Equal(t, session.ExpiresOn, &expires)
1868 assert.Equal(t, session.ExpiresOn, &expires)
@@ -1739,22 +1896,26 @@ func TestFindJwtBearerToken(t *testing.T) {
1739 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1896 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1740 }
1897 }
1741
1898
1742− token, _ = p.findBearerToken(getReq)
1899+ token, err := p.findBearerToken(getReq)
1900+ assert.NoError(t, err)
1743 assert.Equal(t, validToken, token)
1901 assert.Equal(t, validToken, token)
1744
1902
1745 // Basic - no password
1903 // Basic - no password
1746 getReq.SetBasicAuth(token, "")
1904 getReq.SetBasicAuth(token, "")
1747− token, _ = p.findBearerToken(getReq)
1905+ token, err = p.findBearerToken(getReq)
1906+ assert.NoError(t, err)
1748 assert.Equal(t, validToken, token)
1907 assert.Equal(t, validToken, token)
1749
1908
1750 // Basic - sentinel password
1909 // Basic - sentinel password
1751 getReq.SetBasicAuth(token, "x-oauth-basic")
1910 getReq.SetBasicAuth(token, "x-oauth-basic")
1752− token, _ = p.findBearerToken(getReq)
1911+ token, err = p.findBearerToken(getReq)
1912+ assert.NoError(t, err)
1753 assert.Equal(t, validToken, token)
1913 assert.Equal(t, validToken, token)
1754
1914
1755 // Basic - any username, password matching jwt pattern
1915 // Basic - any username, password matching jwt pattern
1756 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1916 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1757− token, _ = p.findBearerToken(getReq)
1917+ token, err = p.findBearerToken(getReq)
1918+ assert.NoError(t, err)
1758 assert.Equal(t, validToken, token)
1919 assert.Equal(t, validToken, token)
1759
1920
1760 failures := []string{
1921 failures := []string{
@@ -1785,8 +1946,6 @@ func TestFindJwtBearerToken(t *testing.T) {
1785 _, err := p.findBearerToken(getReq)
1946 _, err := p.findBearerToken(getReq)
1786 assert.Error(t, err)
1947 assert.Error(t, err)
1787 }
1948 }
1788−
1789− fmt.Printf("%s", token)
1790}
1949}
1791
1950
1792func Test_prepareNoCache(t *testing.T) {
1951func Test_prepareNoCache(t *testing.T) {
@@ -1807,18 +1966,22 @@ func Test_prepareNoCache(t *testing.T) {
1807
1966
1808func Test_noCacheHeaders(t *testing.T) {
1967func Test_noCacheHeaders(t *testing.T) {
1809 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1968 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1810− w.Write([]byte("upstream"))
1969+ _, err := w.Write([]byte("upstream"))
1970+ if err != nil {
1971+ t.Error(err)
1972+ }
1811 }))
1973 }))
1812 t.Cleanup(upstream.Close)
1974 t.Cleanup(upstream.Close)
1813
1975
1814 opts := baseTestOptions()
1976 opts := baseTestOptions()
1815 opts.Upstreams = []string{upstream.URL}
1977 opts.Upstreams = []string{upstream.URL}
1816 opts.SkipAuthRegex = []string{".*"}
1978 opts.SkipAuthRegex = []string{".*"}
1817− _ = validation.Validate(opts)
1979+ err := validation.Validate(opts)
1818− proxy, err := NewOAuthProxy(opts, func(email string) bool {
1819− return true
1820− })
1821 assert.NoError(t, err)
1980 assert.NoError(t, err)
1981+ proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
1982+ if err != nil {
1983+ t.Fatal(err)
1984+ }
1822
1985
1823 t.Run("not exist in response from upstream", func(t *testing.T) {
1986 t.Run("not exist in response from upstream", func(t *testing.T) {
1824 rec := httptest.NewRecorder()
1987 rec := httptest.NewRecorder()
@@ -1887,8 +2050,8 @@ func Test_noCacheHeaders(t *testing.T) {
1887func baseTestOptions() *options.Options {
2050func baseTestOptions() *options.Options {
1888 opts := options.NewOptions()
2051 opts := options.NewOptions()
1889 opts.Cookie.Secret = rawCookieSecret
2052 opts.Cookie.Secret = rawCookieSecret
1890− opts.ClientID = "cliend-id"
2053+ opts.ClientID = clientID
1891− opts.ClientSecret = "client-secret"
2054+ opts.ClientSecret = clientSecret
1892 opts.EmailDomains = []string{"*"}
2055 opts.EmailDomains = []string{"*"}
1893 return opts
2056 return opts
1894}
2057}
pkg/apis/sessions/legacy_v5_tester.gonew file+87
@@ -0,0 +1,87 @@
1+package sessions
2+
3+import (
4+ "fmt"
5+ "testing"
6+ "time"
7+
8+ "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
9+ "github.com/stretchr/testify/assert"
10+)
11+
12+// LegacyV5TestCase provides V5 JSON based test cases for legacy fallback code
13+type LegacyV5TestCase struct {
14+ Input string
15+ Error bool
16+ Output *SessionState
17+}
18+
19+// CreateLegacyV5TestCases makes various V5 JSON sessions as test cases
20+//
21+// Used for `apis/sessions/session_state_test.go` & `sessions/redis/redis_store_test.go`
22+//
23+// TODO: Remove when this is deprecated (likely V7)
24+func CreateLegacyV5TestCases(t *testing.T) (map[string]LegacyV5TestCase, encryption.Cipher, encryption.Cipher) {
25+ const secret = "0123456789abcdefghijklmnopqrstuv"
26+
27+ created := time.Now()
28+ createdJSON, err := created.MarshalJSON()
29+ assert.NoError(t, err)
30+ createdString := string(createdJSON)
31+ e := time.Now().Add(time.Duration(1) * time.Hour)
32+ eJSON, err := e.MarshalJSON()
33+ assert.NoError(t, err)
34+ eString := string(eJSON)
35+
36+ cfbCipher, err := encryption.NewCFBCipher([]byte(secret))
37+ assert.NoError(t, err)
38+ legacyCipher := encryption.NewBase64Cipher(cfbCipher)
39+
40+ testCases := map[string]LegacyV5TestCase{
41+ "User & email unencrypted": {
42+ Input: `{"Email":"user@domain.com","User":"just-user"}`,
43+ Error: true,
44+ },
45+ "Only email unencrypted": {
46+ Input: `{"Email":"user@domain.com"}`,
47+ Error: true,
48+ },
49+ "Just user unencrypted": {
50+ Input: `{"User":"just-user"}`,
51+ Error: true,
52+ },
53+ "User and Email unencrypted while rest is encrypted": {
54+ Input: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
55+ Error: true,
56+ },
57+ "Full session with cipher": {
58+ Input: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
59+ Output: &SessionState{
60+ Email: "user@domain.com",
61+ User: "just-user",
62+ AccessToken: "token1234",
63+ IDToken: "rawtoken1234",
64+ CreatedAt: &created,
65+ ExpiresOn: &e,
66+ RefreshToken: "refresh4321",
67+ },
68+ },
69+ "Minimal session encrypted with cipher": {
70+ Input: `{"Email":"EGTllJcOFC16b7LBYzLekaHAC5SMMSPdyUrg8hd25g==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw=="}`,
71+ Output: &SessionState{
72+ Email: "user@domain.com",
73+ User: "just-user",
74+ },
75+ },
76+ "Unencrypted User, Email and AccessToken": {
77+ Input: `{"Email":"user@domain.com","User":"just-user","AccessToken":"X"}`,
78+ Error: true,
79+ },
80+ "Unencrypted User, Email and IDToken": {
81+ Input: `{"Email":"user@domain.com","User":"just-user","IDToken":"XXXX"}`,
82+ Error: true,
83+ },
84+ }
85+
86+ return testCases, cfbCipher, legacyCipher
87+}
pkg/apis/sessions/session_state.go+156−67
@@ -1,25 +1,30 @@
1package sessions
1package sessions
2
2
3import (
3import (
4+ "bytes"
4 "encoding/json"
5 "encoding/json"
5 "errors"
6 "errors"
6 "fmt"
7 "fmt"
8+ "io"
9+ "io/ioutil"
7 "time"
10 "time"
8 "unicode/utf8"
11 "unicode/utf8"
9
12
10 "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
13 "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
14+ "github.com/pierrec/lz4"
15+ "github.com/vmihailenco/msgpack/v4"
11)
16)
12
17
13// SessionState is used to store information about the currently authenticated user session
18// SessionState is used to store information about the currently authenticated user session
14type SessionState struct {
19type SessionState struct {
15− AccessToken string `json:",omitempty"`
20+ AccessToken string `json:",omitempty" msgpack:"at,omitempty"`
16− IDToken string `json:",omitempty"`
21+ IDToken string `json:",omitempty" msgpack:"it,omitempty"`
17− CreatedAt *time.Time `json:",omitempty"`
22+ CreatedAt *time.Time `json:",omitempty" msgpack:"ca,omitempty"`
18− ExpiresOn *time.Time `json:",omitempty"`
23+ ExpiresOn *time.Time `json:",omitempty" msgpack:"eo,omitempty"`
19− RefreshToken string `json:",omitempty"`
24+ RefreshToken string `json:",omitempty" msgpack:"rt,omitempty"`
20− Email string `json:",omitempty"`
25+ Email string `json:",omitempty" msgpack:"e,omitempty"`
21− User string `json:",omitempty"`
26+ User string `json:",omitempty" msgpack:"u,omitempty"`
22− PreferredUsername string `json:",omitempty"`
27+ PreferredUsername string `json:",omitempty" msgpack:"pu,omitempty"`
23}
28}
24
29
25// IsExpired checks whether the session has expired
30// IsExpired checks whether the session has expired
@@ -59,78 +64,79 @@ func (s *SessionState) String() string {
59 return o + "}"
64 return o + "}"
60}
65}
61
66
62−// EncodeSessionState returns string representation of the current session
67+// EncodeSessionState returns an encrypted, lz4 compressed, MessagePack encoded session
63−func (s *SessionState) EncodeSessionState(c encryption.Cipher) (string, error) {
68+func (s *SessionState) EncodeSessionState(c encryption.Cipher, compress bool) ([]byte, error) {
64− var ss SessionState
69+ packed, err := msgpack.Marshal(s)
65− if c == nil {
70+ if err != nil {
66− // Store only Email and User when cipher is unavailable
71+ return nil, fmt.Errorf("error marshalling session state to msgpack: %w", err)
67− ss.Email = s.Email
72+ }
68− ss.User = s.User
73+
69− ss.PreferredUsername = s.PreferredUsername
74+ if !compress {
70− } else {
75+ return c.Encrypt(packed)
71− ss = *s
76+ }
72− for _, s := range []*string{
77+
73− &ss.Email,
78+ compressed, err := lz4Compress(packed)
74− &ss.User,
79+ if err != nil {
75− &ss.PreferredUsername,
80+ return nil, err
76− &ss.AccessToken,
81+ }
77− &ss.IDToken,
82+ return c.Encrypt(compressed)
78− &ss.RefreshToken,
83+}
79− } {
84+
80− err := into(s, c.Encrypt)
85+// DecodeSessionState decodes a LZ4 compressed MessagePack into a Session State
86+func DecodeSessionState(data []byte, c encryption.Cipher, compressed bool) (*SessionState, error) {
87+ decrypted, err := c.Decrypt(data)
88+ if err != nil {
89+ return nil, fmt.Errorf("error decrypting the session state: %w", err)
90+ }
91+
92+ packed := decrypted
93+ if compressed {
94+ packed, err = lz4Decompress(decrypted)
81− if err != nil {
95+ if err != nil {
82− return "", err
96+ return nil, err
83− }
84 }
97 }
85 }
98 }
86
99
87− b, err := json.Marshal(ss)
100+ var ss SessionState
88− return string(b), err
101+ err = msgpack.Unmarshal(packed, &ss)
102+ if err != nil {
103+ return nil, fmt.Errorf("error unmarshalling data to session state: %w", err)
104+ }
105+
106+ err = ss.validate()
107+ if err != nil {
108+ return nil, err
109+ }
110+
111+ return &ss, nil
89}
112}
90
113
91−// DecodeSessionState decodes the session cookie string into a SessionState
114+// LegacyV5DecodeSessionState decodes a legacy JSON session cookie string into a SessionState
92−func DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) {
115+func LegacyV5DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) {
93 var ss SessionState
116 var ss SessionState
94 err := json.Unmarshal([]byte(v), &ss)
117 err := json.Unmarshal([]byte(v), &ss)
95 if err != nil {
118 if err != nil {
96 return nil, fmt.Errorf("error unmarshalling session: %w", err)
119 return nil, fmt.Errorf("error unmarshalling session: %w", err)
97 }
120 }
98
121
99− if c == nil {
100− // Load only Email and User when cipher is unavailable
101− ss = SessionState{
102− Email: ss.Email,
103− User: ss.User,
104− PreferredUsername: ss.PreferredUsername,
105− }
106− } else {
107− // Backward compatibility with using unencrypted Email or User
108− // Decryption errors will leave original string
109− err = into(&ss.Email, c.Decrypt)
110− if err == nil {
111− if !utf8.ValidString(ss.Email) {
112− return nil, errors.New("invalid value for decrypted email")
113− }
114− }
115− err = into(&ss.User, c.Decrypt)
116− if err == nil {
117− if !utf8.ValidString(ss.User) {
118− return nil, errors.New("invalid value for decrypted user")
119− }
120− }
121−
122− for _, s := range []*string{
122+ for _, s := range []*string{
123+ &ss.User,
124+ &ss.Email,
123− &ss.PreferredUsername,
125+ &ss.PreferredUsername,
124− &ss.AccessToken,
126+ &ss.AccessToken,
125− &ss.IDToken,
127+ &ss.IDToken,
126− &ss.RefreshToken,
128+ &ss.RefreshToken,
127− } {
129+ } {
128− err := into(s, c.Decrypt)
130+ err := into(s, c.Decrypt)
129− if err != nil {
131+ if err != nil {
130− return nil, err
132+ return nil, err
131− }
132 }
133 }
133 }
134 }
135+ err = ss.validate()
136+ if err != nil {
137+ return nil, err
138+ }
139+
134 return &ss, nil
140 return &ss, nil
135}
141}
136
142
@@ -150,3 +156,86 @@ func into(s *string, f codecFunc) error {
150 *s = string(d)
156 *s = string(d)
151 return nil
157 return nil
152}
158}
159+
160+// lz4Compress compresses with LZ4
161+//
162+// The Compress:Decompress ratio is 1:Many. LZ4 gives fastest decompress speeds
163+// at the expense of greater compression compared to other compression
164+// algorithms.
165+func lz4Compress(payload []byte) ([]byte, error) {
166+ buf := new(bytes.Buffer)
167+ zw := lz4.NewWriter(nil)
168+ zw.Header = lz4.Header{
169+ BlockMaxSize: 65536,
170+ CompressionLevel: 0,
171+ }
172+ zw.Reset(buf)
173+
174+ reader := bytes.NewReader(payload)
175+ _, err := io.Copy(zw, reader)
176+ if err != nil {
177+ return nil, fmt.Errorf("error copying lz4 stream to buffer: %w", err)
178+ }
179+ err = zw.Close()
180+ if err != nil {
181+ return nil, fmt.Errorf("error closing lz4 writer: %w", err)
182+ }
183+
184+ compressed, err := ioutil.ReadAll(buf)
185+ if err != nil {
186+ return nil, fmt.Errorf("error reading lz4 buffer: %w", err)
187+ }
188+
189+ return compressed, nil
190+}
191+
192+// lz4Decompress decompresses with LZ4
193+func lz4Decompress(compressed []byte) ([]byte, error) {
194+ reader := bytes.NewReader(compressed)
195+ buf := new(bytes.Buffer)
196+ zr := lz4.NewReader(nil)
197+ zr.Reset(reader)
198+ _, err := io.Copy(buf, zr)
199+ if err != nil {
200+ return nil, fmt.Errorf("error copying lz4 stream to buffer: %w", err)
201+ }
202+
203+ payload, err := ioutil.ReadAll(buf)
204+ if err != nil {
205+ return nil, fmt.Errorf("error reading lz4 buffer: %w", err)
206+ }
207+
208+ return payload, nil
209+}
210+
211+// validate ensures the decoded session is non-empty and contains valid data
212+//
213+// Non-empty check is needed due to ensure the non-authenticated AES-CFB
214+// decryption doesn't result in garbage data that collides with a valid
215+// MessagePack header bytes (which MessagePack will unmarshal to an empty
216+// default SessionState). <1% chance, but observed with random test data.
217+//
218+// UTF-8 check ensures the strings are valid and not raw bytes overloaded
219+// into Latin-1 encoding. The occurs when legacy unencrypted fields are
220+// decrypted with AES-CFB which results in random bytes.
221+func (s *SessionState) validate() error {
222+ for _, field := range []string{
223+ s.User,
224+ s.Email,
225+ s.PreferredUsername,
226+ s.AccessToken,
227+ s.IDToken,
228+ s.RefreshToken,
229+ } {
230+ if !utf8.ValidString(field) {
231+ return errors.New("invalid non-UTF8 field in session")
232+ }
233+ }
234+
235+ empty := new(SessionState)
236+ if *s == *empty {
237+ return errors.New("invalid empty session unmarshalled")
238+ }
239+
240+ return nil
241+}
pkg/apis/sessions/session_state_test.go+162−270
@@ -12,132 +12,11 @@ import (
12 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/assert"
13)
13)
14
14
15−const secret = "0123456789abcdefghijklmnopqrstuv"
16−const altSecret = "0000000000abcdefghijklmnopqrstuv"
17−
18func timePtr(t time.Time) *time.Time {
15func timePtr(t time.Time) *time.Time {
19 return &t
16 return &t
20}
17}
21
18
22−func newTestCipher(secret []byte) (encryption.Cipher, error) {
19+func TestIsExpired(t *testing.T) {
23− return encryption.NewBase64Cipher(encryption.NewCFBCipher, secret)
24−}
25−
26−func TestSessionStateSerialization(t *testing.T) {
27− c, err := newTestCipher([]byte(secret))
28− assert.Equal(t, nil, err)
29− c2, err := newTestCipher([]byte(altSecret))
30− assert.Equal(t, nil, err)
31− s := &SessionState{
32− Email: "user@domain.com",
33− PreferredUsername: "user",
34− AccessToken: "token1234",
35− IDToken: "rawtoken1234",
36− CreatedAt: timePtr(time.Now()),
37− ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
38− RefreshToken: "refresh4321",
39− }
40− encoded, err := s.EncodeSessionState(c)
41− assert.Equal(t, nil, err)
42−
43− ss, err := DecodeSessionState(encoded, c)
44− t.Logf("%#v", ss)
45− assert.Equal(t, nil, err)
46− assert.Equal(t, "", ss.User)
47− assert.Equal(t, s.Email, ss.Email)
48− assert.Equal(t, s.PreferredUsername, ss.PreferredUsername)
49− assert.Equal(t, s.AccessToken, ss.AccessToken)
50− assert.Equal(t, s.IDToken, ss.IDToken)
51− assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
52− assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
53− assert.Equal(t, s.RefreshToken, ss.RefreshToken)
54−
55− // ensure a different cipher can't decode properly (ie: it gets gibberish)
56− ss, err = DecodeSessionState(encoded, c2)
57− t.Logf("%#v", ss)
58− assert.NotEqual(t, nil, err)
59−}
60−
61−func TestSessionStateSerializationWithUser(t *testing.T) {
62− c, err := newTestCipher([]byte(secret))
63− assert.Equal(t, nil, err)
64− c2, err := newTestCipher([]byte(altSecret))
65− assert.Equal(t, nil, err)
66− s := &SessionState{
67− User: "just-user",
68− PreferredUsername: "ju",
69− Email: "user@domain.com",
70− AccessToken: "token1234",
71− CreatedAt: timePtr(time.Now()),
72− ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
73− RefreshToken: "refresh4321",
74− }
75− encoded, err := s.EncodeSessionState(c)
76− assert.Equal(t, nil, err)
77−
78− ss, err := DecodeSessionState(encoded, c)
79− t.Logf("%#v", ss)
80− assert.Equal(t, nil, err)
81− assert.Equal(t, s.User, ss.User)
82− assert.Equal(t, s.Email, ss.Email)
83− assert.Equal(t, s.PreferredUsername, ss.PreferredUsername)
84− assert.Equal(t, s.AccessToken, ss.AccessToken)
85− assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
86− assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
87− assert.Equal(t, s.RefreshToken, ss.RefreshToken)
88−
89− // ensure a different cipher can't decode properly (ie: it gets gibberish)
90− ss, err = DecodeSessionState(encoded, c2)
91− t.Logf("%#v", ss)
92− assert.NotEqual(t, nil, err)
93−}
94−
95−func TestSessionStateSerializationNoCipher(t *testing.T) {
96− s := &SessionState{
97− Email: "user@domain.com",
98− PreferredUsername: "user",
99− AccessToken: "token1234",
100− CreatedAt: timePtr(time.Now()),
101− ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
102− RefreshToken: "refresh4321",
103− }
104− encoded, err := s.EncodeSessionState(nil)
105− assert.Equal(t, nil, err)
106−
107− // only email should have been serialized
108− ss, err := DecodeSessionState(encoded, nil)
109− assert.Equal(t, nil, err)
110− assert.Equal(t, "", ss.User)
111− assert.Equal(t, s.Email, ss.Email)
112− assert.Equal(t, s.PreferredUsername, ss.PreferredUsername)
113− assert.Equal(t, "", ss.AccessToken)
114− assert.Equal(t, "", ss.RefreshToken)
115−}
116−
117−func TestSessionStateSerializationNoCipherWithUser(t *testing.T) {
118− s := &SessionState{
119− User: "just-user",
120− Email: "user@domain.com",
121− PreferredUsername: "user",
122− AccessToken: "token1234",
123− CreatedAt: timePtr(time.Now()),
124− ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
125− RefreshToken: "refresh4321",
126− }
127− encoded, err := s.EncodeSessionState(nil)
128− assert.Equal(t, nil, err)
129−
130− // only email should have been serialized
131− ss, err := DecodeSessionState(encoded, nil)
132− assert.Equal(t, nil, err)
133− assert.Equal(t, s.User, ss.User)
134− assert.Equal(t, s.Email, ss.Email)
135− assert.Equal(t, s.PreferredUsername, ss.PreferredUsername)
136− assert.Equal(t, "", ss.AccessToken)
137− assert.Equal(t, "", ss.RefreshToken)
138−}
139−
140−func TestExpired(t *testing.T) {
141 s := &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))}
20 s := &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))}
142 assert.Equal(t, true, s.IsExpired())
21 assert.Equal(t, true, s.IsExpired())
143
22
@@ -148,172 +27,160 @@ func TestExpired(t *testing.T) {
148 assert.Equal(t, false, s.IsExpired())
27 assert.Equal(t, false, s.IsExpired())
149}
28}
150
29
151−type testCase struct {
152− SessionState
153− Encoded string
154− Cipher encryption.Cipher
155− Error bool
156−}
157−
158−// TestEncodeSessionState tests EncodeSessionState with the test vector
159−//
160−// Currently only tests without cipher here because we have no way to mock
161−// the random generator used in EncodeSessionState.
162−func TestEncodeSessionState(t *testing.T) {
30+func TestAge(t *testing.T) {
163− c := time.Now()
31+ ss := &SessionState{}
164− e := time.Now().Add(time.Duration(1) * time.Hour)
165
32
166− testCases := []testCase{
33+ // Created at unset so should be 0
167− {
34+ assert.Equal(t, time.Duration(0), ss.Age())
168− SessionState: SessionState{
169− Email: "user@domain.com",
170− User: "just-user",
171− },
172− Encoded: `{"Email":"user@domain.com","User":"just-user"}`,
173− },
174− {
175− SessionState: SessionState{
176− Email: "user@domain.com",
177− User: "just-user",
178− AccessToken: "token1234",
179− IDToken: "rawtoken1234",
180− CreatedAt: &c,
181− ExpiresOn: &e,
182− RefreshToken: "refresh4321",
183− },
184− Encoded: `{"Email":"user@domain.com","User":"just-user"}`,
185− },
186− }
187
35
188− for i, tc := range testCases {
36+ // Set CreatedAt to 1 hour ago
189− encoded, err := tc.EncodeSessionState(tc.Cipher)
37+ ss.CreatedAt = timePtr(time.Now().Add(-1 * time.Hour))
190− t.Logf("i:%d Encoded:%#vSessionState:%#v Error:%#v", i, encoded, tc.SessionState, err)
38+ assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
191− if tc.Error {
192− assert.Error(t, err)
193− assert.Empty(t, encoded)
194− continue
195− }
196− assert.NoError(t, err)
197− assert.JSONEq(t, tc.Encoded, encoded)
198− }
199}
39}
200
40
201−// TestDecodeSessionState testssessions.DecodeSessionState with the test vector
41+// TestEncodeAndDecodeSessionState encodes & decodes various session states
202−func TestDecodeSessionState(t *testing.T) {
42+// and confirms the operation is 1:1
43+func TestEncodeAndDecodeSessionState(t *testing.T) {
203 created := time.Now()
44 created := time.Now()
204− createdJSON, _ := created.MarshalJSON()
205− createdString := string(createdJSON)
206− e := time.Now().Add(time.Duration(1) * time.Hour)
45+ expires := time.Now().Add(time.Duration(1) * time.Hour)
207− eJSON, _ := e.MarshalJSON()
46+
208− eString := string(eJSON)
47+ // Tokens in the test table are purposefully redundant
209−
48+ // Otherwise compressing small payloads could result in a compressed value
210− c, err := newTestCipher([]byte(secret))
49+ // that is larger (compression dictionary + limited like strings to compress)
211− assert.NoError(t, err)
50+ // which breaks the len(compressed) < len(uncompressed) assertion.
212−
51+ testCases := map[string]SessionState{
213− testCases := []testCase{
52+ "Full session": {
214− {
53+ Email: "username@example.com",
215− SessionState: SessionState{
54+ User: "username",
216− Email: "user@domain.com",
55+ PreferredUsername: "preferred.username",
217− User: "just-user",
56+ AccessToken: "AccessToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
218− },
57+ IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
219− Encoded: `{"Email":"user@domain.com","User":"just-user"}`,
58+ CreatedAt: &created,
220− },
59+ ExpiresOn: &expires,
221− {
60+ RefreshToken: "RefreshToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
222− SessionState: SessionState{
223− Email: "user@domain.com",
224− User: "",
225− },
226− Encoded: `{"Email":"user@domain.com"}`,
227 },
61 },
228− {
62+ "No ExpiresOn": {
229− SessionState: SessionState{
63+ Email: "username@example.com",
230− User: "just-user",
64+ User: "username",
231− },
65+ PreferredUsername: "preferred.username",
232− Encoded: `{"User":"just-user"}`,
66+ AccessToken: "AccessToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
67+ IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
68+ CreatedAt: &created,
69+ RefreshToken: "RefreshToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
233 },
70 },
234− {
71+ "No PreferredUsername": {
235− SessionState: SessionState{
72+ Email: "username@example.com",
236− Email: "user@domain.com",
73+ User: "username",
237− User: "just-user",
74+ AccessToken: "AccessToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
238− },
75+ IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
239− Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
76+ CreatedAt: &created,
77+ ExpiresOn: &expires,
78+ RefreshToken: "RefreshToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
240 },
79 },
241− {
80+ "Minimal session": {
242− SessionState: SessionState{
81+ User: "username",
243− Email: "user@domain.com",
82+ IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
244− User: "just-user",
245− AccessToken: "token1234",
246− IDToken: "rawtoken1234",
247− CreatedAt: &created,
83+ CreatedAt: &created,
248− ExpiresOn: &e,
84+ RefreshToken: "RefreshToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
249− RefreshToken: "refresh4321",
250− },
251− Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
252− Cipher: c,
253 },
85 },
254− {
86+ "Bearer authorization header created session": {
255− SessionState: SessionState{
87+ Email: "username",
256− Email: "user@domain.com",
88+ User: "username",
257− User: "just-user",
89+ AccessToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
258− },
90+ IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
259− Encoded: `{"Email":"EGTllJcOFC16b7LBYzLekaHAC5SMMSPdyUrg8hd25g==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw=="}`,
91+ ExpiresOn: &expires,
260− Cipher: c,
261− },
262− {
263− Encoded: `{"Email":"user@domain.com","User":"just-user","AccessToken":"X"}`,
264− Cipher: c,
265− Error: true,
266− },
267− {
268− Encoded: `{"Email":"user@domain.com","User":"just-user","IDToken":"XXXX"}`,
269− Cipher: c,
270− Error: true,
271− },
272− {
273− SessionState: SessionState{
274− Email: "user@domain.com",
275− User: "YmFzZTY0LWVuY29kZWQtdXNlcgo=", // Base64 encoding of base64-encoded-user
276− },
277− Error: true,
278− Cipher: c,
279 },
92 },
280 }
93 }
281
94
282− for i, tc := range testCases {
95+ for _, secretSize := range []int{16, 24, 32} {
283− ss, err := DecodeSessionState(tc.Encoded, tc.Cipher)
96+ t.Run(fmt.Sprintf("%d byte secret", secretSize), func(t *testing.T) {
284− t.Logf("i:%d Encoded:%#vSessionState:%#v Error:%#v", i, tc.Encoded, ss, err)
97+ secret := make([]byte, secretSize)
285− if tc.Error {
98+ _, err := io.ReadFull(rand.Reader, secret)
286− assert.Error(t, err)
99+ assert.NoError(t, err)
287− assert.Nil(t, ss)
100+
288− continue
101+ cfb, err := encryption.NewCFBCipher([]byte(secret))
289− }
102+ assert.NoError(t, err)
290− assert.NoError(t, err)
103+ gcm, err := encryption.NewGCMCipher([]byte(secret))
291− if assert.NotNil(t, ss) {
104+ assert.NoError(t, err)
292− assert.Equal(t, tc.User, ss.User)
105+
293− assert.Equal(t, tc.Email, ss.Email)
106+ ciphers := map[string]encryption.Cipher{
294− assert.Equal(t, tc.AccessToken, ss.AccessToken)
107+ "CFB cipher": cfb,
295− assert.Equal(t, tc.RefreshToken, ss.RefreshToken)
108+ "GCM cipher": gcm,
296− assert.Equal(t, tc.IDToken, ss.IDToken)
297− if tc.ExpiresOn != nil {
298− assert.NotEqual(t, nil, ss.ExpiresOn)
299− assert.Equal(t, tc.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
300 }
109 }
110+
111+ for cipherName, c := range ciphers {
112+ t.Run(cipherName, func(t *testing.T) {
113+ for testName, ss := range testCases {
114+ t.Run(testName, func(t *testing.T) {
115+ encoded, err := ss.EncodeSessionState(c, false)
116+ assert.NoError(t, err)
117+ encodedCompressed, err := ss.EncodeSessionState(c, true)
118+ assert.NoError(t, err)
119+ // Make sure compressed version is smaller than if not compressed
120+ assert.Greater(t, len(encoded), len(encodedCompressed))
121+
122+ decoded, err := DecodeSessionState(encoded, c, false)
123+ assert.NoError(t, err)
124+ decodedCompressed, err := DecodeSessionState(encodedCompressed, c, true)
125+ assert.NoError(t, err)
126+
127+ compareSessionStates(t, decoded, decodedCompressed)
128+ compareSessionStates(t, decoded, &ss)
129+ })
130+ }
131+ })
132+ }
133+
134+ t.Run("Mixed cipher types cause errors", func(t *testing.T) {
135+ for testName, ss := range testCases {
136+ t.Run(testName, func(t *testing.T) {
137+ cfbEncoded, err := ss.EncodeSessionState(cfb, false)
138+ assert.NoError(t, err)
139+ _, err = DecodeSessionState(cfbEncoded, gcm, false)
140+ assert.Error(t, err)
141+
142+ gcmEncoded, err := ss.EncodeSessionState(gcm, false)
143+ assert.NoError(t, err)
144+ _, err = DecodeSessionState(gcmEncoded, cfb, false)
145+ assert.Error(t, err)
146+ })
147+ }
148+ })
301− }
149+ })
302 }
150 }
303}
151}
304
152
305−func TestSessionStateAge(t *testing.T) {
153+// TestLegacyV5DecodeSessionState confirms V5 JSON sessions decode
306− ss := &SessionState{}
154+//
307−
155+// TODO: Remove when this is deprecated (likely V7)
308− // Created at unset so should be 0
156+func TestLegacyV5DecodeSessionState(t *testing.T) {
309− assert.Equal(t, time.Duration(0), ss.Age())
157+ testCases, cipher, legacyCipher := CreateLegacyV5TestCases(t)
158+
159+ for testName, tc := range testCases {
160+ t.Run(testName, func(t *testing.T) {
161+ // Legacy sessions fail in DecodeSessionState which results in
162+ // the fallback to LegacyV5DecodeSessionState
163+ _, err := DecodeSessionState([]byte(tc.Input), cipher, false)
164+ assert.Error(t, err)
165+ _, err = DecodeSessionState([]byte(tc.Input), cipher, true)
166+ assert.Error(t, err)
310
167
311− // Set CreatedAt to 1 hour ago
168+ ss, err := LegacyV5DecodeSessionState(tc.Input, legacyCipher)
312− ss.CreatedAt = timePtr(time.Now().Add(-1 * time.Hour))
169+ if tc.Error {
313− assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
170+ assert.Error(t, err)
171+ assert.Nil(t, ss)
172+ return
173+ }
174+ assert.NoError(t, err)
175+ compareSessionStates(t, tc.Output, ss)
176+ })
177+ }
314}
178}
315
179
180+// Test_into tests the into helper function used in LegacyV5DecodeSessionState
181+//
182+// TODO: Remove when this is deprecated (likely V7)
316−func TestIntoEncryptAndIntoDecrypt(t *testing.T) {
183+func Test_into(t *testing.T) {
317 const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
184 const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
318
185
319 // Test all 3 valid AES sizes
186 // Test all 3 valid AES sizes
@@ -323,8 +190,9 @@ func TestIntoEncryptAndIntoDecrypt(t *testing.T) {
323 _, err := io.ReadFull(rand.Reader, secret)
190 _, err := io.ReadFull(rand.Reader, secret)
324 assert.Equal(t, nil, err)
191 assert.Equal(t, nil, err)
325
192
326− c, err := newTestCipher(secret)
193+ cfb, err := encryption.NewCFBCipher(secret)
327 assert.NoError(t, err)
194 assert.NoError(t, err)
195+ c := encryption.NewBase64Cipher(cfb)
328
196
329 // Check no errors with empty or nil strings
197 // Check no errors with empty or nil strings
330 empty := ""
198 empty := ""
@@ -353,3 +221,27 @@ func TestIntoEncryptAndIntoDecrypt(t *testing.T) {
353 })
221 })
354 }
222 }
355}
223}
224+
225+func compareSessionStates(t *testing.T, expected *SessionState, actual *SessionState) {
226+ if expected.CreatedAt != nil {
227+ assert.NotNil(t, actual.CreatedAt)
228+ assert.Equal(t, true, expected.CreatedAt.Equal(*actual.CreatedAt))
229+ } else {
230+ assert.Nil(t, actual.CreatedAt)
231+ }
232+ if expected.ExpiresOn != nil {
233+ assert.NotNil(t, actual.ExpiresOn)
234+ assert.Equal(t, true, expected.ExpiresOn.Equal(*actual.ExpiresOn))
235+ } else {
236+ assert.Nil(t, actual.ExpiresOn)
237+ }
238+
239+ // Compare sessions without *time.Time fields
240+ exp := *expected
241+ exp.CreatedAt = nil
242+ exp.ExpiresOn = nil
243+ act := *actual
244+ act.CreatedAt = nil
245+ act.ExpiresOn = nil
246+ assert.Equal(t, exp, act)
247+}
pkg/encryption/cipher.go+2−6
@@ -21,12 +21,8 @@ type base64Cipher struct {
21
21
22// NewBase64Cipher returns a new AES Cipher for encrypting cookie values
22// NewBase64Cipher returns a new AES Cipher for encrypting cookie values
23// and wrapping them in Base64 -- Supports Legacy encryption scheme
23// and wrapping them in Base64 -- Supports Legacy encryption scheme
24−func NewBase64Cipher(initCipher func([]byte) (Cipher, error), secret []byte) (Cipher, error) {
24+func NewBase64Cipher(c Cipher) Cipher {
25− c, err := initCipher(secret)
26− if err != nil {
27− return nil, err
28− }
29− return &base64Cipher{Cipher: c}, nil
25+ return &base64Cipher{Cipher: c}
30}
26}
31
27
32// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it
28// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it
pkg/encryption/cipher_test.go+7−6
@@ -13,8 +13,9 @@ import (
13func TestEncodeAndDecodeAccessToken(t *testing.T) {
13func TestEncodeAndDecodeAccessToken(t *testing.T) {
14 const secret = "0123456789abcdefghijklmnopqrstuv"
14 const secret = "0123456789abcdefghijklmnopqrstuv"
15 const token = "my access token"
15 const token = "my access token"
16− c, err := NewBase64Cipher(NewCFBCipher, []byte(secret))
16+ cfb, err := NewCFBCipher([]byte(secret))
17− assert.Equal(t, nil, err)
17+ assert.NoError(t, err)
18+ c := NewBase64Cipher(cfb)
18
19
19 encoded, err := c.Encrypt([]byte(token))
20 encoded, err := c.Encrypt([]byte(token))
20 assert.Equal(t, nil, err)
21 assert.Equal(t, nil, err)
@@ -32,8 +33,9 @@ func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
32
33
33 secret, err := base64.URLEncoding.DecodeString(secretBase64)
34 secret, err := base64.URLEncoding.DecodeString(secretBase64)
34 assert.Equal(t, nil, err)
35 assert.Equal(t, nil, err)
35− c, err := NewBase64Cipher(NewCFBCipher, []byte(secret))
36+ cfb, err := NewCFBCipher([]byte(secret))
36− assert.Equal(t, nil, err)
37+ assert.NoError(t, err)
38+ c := NewBase64Cipher(cfb)
37
39
38 encoded, err := c.Encrypt([]byte(token))
40 encoded, err := c.Encrypt([]byte(token))
39 assert.Equal(t, nil, err)
41 assert.Equal(t, nil, err)
@@ -64,8 +66,7 @@ func TestEncryptAndDecrypt(t *testing.T) {
64 cstd, err := initCipher(secret)
66 cstd, err := initCipher(secret)
65 assert.Equal(t, nil, err)
67 assert.Equal(t, nil, err)
66
68
67− cb64, err := NewBase64Cipher(initCipher, secret)
69+ cb64 := NewBase64Cipher(cstd)
68− assert.Equal(t, nil, err)
69
70
70 ciphers := map[string]Cipher{
71 ciphers := map[string]Cipher{
71 "Standard": cstd,
72 "Standard": cstd,
pkg/sessions/cookie/session_store.go+21−12
@@ -60,7 +60,7 @@ func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
60 return nil, errors.New("cookie signature not valid")
60 return nil, errors.New("cookie signature not valid")
61 }
61 }
62
62
63− session, err := sessionFromCookie(string(val), s.CookieCipher)
63+ session, err := sessionFromCookie(val, s.CookieCipher)
64 if err != nil {
64 if err != nil {
65 return nil, err
65 return nil, err
66 }
66 }
@@ -85,17 +85,26 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
85}
85}
86
86
87// cookieForSession serializes a session state for storage in a cookie
87// cookieForSession serializes a session state for storage in a cookie
88−func cookieForSession(s *sessions.SessionState, c encryption.Cipher) (string, error) {
88+func cookieForSession(s *sessions.SessionState, c encryption.Cipher) ([]byte, error) {
89− return s.EncodeSessionState(c)
89+ return s.EncodeSessionState(c, true)
90}
90}
91
91
92// sessionFromCookie deserializes a session from a cookie value
92// sessionFromCookie deserializes a session from a cookie value
93−func sessionFromCookie(v string, c encryption.Cipher) (s *sessions.SessionState, err error) {
93+func sessionFromCookie(v []byte, c encryption.Cipher) (s *sessions.SessionState, err error) {
94− return sessions.DecodeSessionState(v, c)
94+ ss, err := sessions.DecodeSessionState(v, c, true)
95+ // If anything fails (Decrypt, LZ4, MessagePack), try legacy JSON decode
96+ // LZ4 will likely fail for wrong header after AES-CFB spits out garbage
97+ // data from trying to decrypt JSON it things is ciphertext
98+ if err != nil {
99+ // Legacy used Base64 + AES CFB
100+ legacyCipher := encryption.NewBase64Cipher(c)
101+ return sessions.LegacyV5DecodeSessionState(string(v), legacyCipher)
102+ }
103+ return ss, nil
95}
104}
96
105
97// setSessionCookie adds the user's session cookie to the response
106// setSessionCookie adds the user's session cookie to the response
98−func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val string, created time.Time) {
107+func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val []byte, created time.Time) {
99 for _, c := range s.makeSessionCookie(req, val, created) {
108 for _, c := range s.makeSessionCookie(req, val, created) {
100 http.SetCookie(rw, c)
109 http.SetCookie(rw, c)
101 }
110 }
@@ -103,12 +112,12 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques
103
112
104// makeSessionCookie creates an http.Cookie containing the authenticated user's
113// makeSessionCookie creates an http.Cookie containing the authenticated user's
105// authentication details
114// authentication details
106−func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie {
115+func (s *SessionStore) makeSessionCookie(req *http.Request, value []byte, now time.Time) []*http.Cookie {
107− if value != "" {
116+ strValue := string(value)
117+ if strValue != "" {
108− value = encryption.SignedValue(s.Cookie.Secret, s.Cookie.Name, []byte(value), now)
118+ strValue = encryption.SignedValue(s.Cookie.Secret, s.Cookie.Name, value, now)
109 }
119 }
110− c := s.makeCookie(req, s.Cookie.Name, value, s.Cookie.Expire, now)
120+ c := s.makeCookie(req, s.Cookie.Name, strValue, s.Cookie.Expire, now)
111−
112 if len(c.String()) > maxCookieLength {
121 if len(c.String()) > maxCookieLength {
113 return splitCookie(c)
122 return splitCookie(c)
114 }
123 }
@@ -129,7 +138,7 @@ func (s *SessionStore) makeCookie(req *http.Request, name string, value string,
129// NewCookieSessionStore initialises a new instance of the SessionStore from
138// NewCookieSessionStore initialises a new instance of the SessionStore from
130// the configuration given
139// the configuration given
131func NewCookieSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) {
140func NewCookieSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) {
132− cipher, err := encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(cookieOpts.Secret))
141+ cipher, err := encryption.NewCFBCipher(encryption.SecretBytes(cookieOpts.Secret))
133 if err != nil {
142 if err != nil {
134 return nil, fmt.Errorf("error initialising cipher: %v", err)
143 return nil, fmt.Errorf("error initialising cipher: %v", err)
135 }
144 }
pkg/sessions/redis/redis_store.go+75−56
@@ -40,7 +40,7 @@ type SessionStore struct {
40// NewRedisSessionStore initialises a new instance of the SessionStore from
40// NewRedisSessionStore initialises a new instance of the SessionStore from
41// the configuration given
41// the configuration given
42func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) {
42func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) {
43− cipher, err := encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(cookieOpts.Secret))
43+ cfbCipher, err := encryption.NewCFBCipher(encryption.SecretBytes(cookieOpts.Secret))
44 if err != nil {
44 if err != nil {
45 return nil, fmt.Errorf("error initialising cipher: %v", err)
45 return nil, fmt.Errorf("error initialising cipher: %v", err)
46 }
46 }
@@ -52,7 +52,7 @@ func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cook
52
52
53 rs := &SessionStore{
53 rs := &SessionStore{
54 Client: client,
54 Client: client,
55− CookieCipher: cipher,
55+ CookieCipher: cfbCipher,
56 Cookie: cookieOpts,
56 Cookie: cookieOpts,
57 }
57 }
58 return rs, nil
58 return rs, nil
@@ -146,12 +146,8 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
146 // Old sessions that we are refreshing would have a request cookie
146 // Old sessions that we are refreshing would have a request cookie
147 // New sessions don't, so we ignore the error. storeValue will check requestCookie
147 // New sessions don't, so we ignore the error. storeValue will check requestCookie
148 requestCookie, _ := req.Cookie(store.Cookie.Name)
148 requestCookie, _ := req.Cookie(store.Cookie.Name)
149− value, err := s.EncodeSessionState(store.CookieCipher)
150− if err != nil {
151− return err
152− }
153 ctx := req.Context()
149 ctx := req.Context()
154− ticketString, err := store.storeValue(ctx, value, store.Cookie.Expire, requestCookie)
150+ ticketString, err := store.saveSession(ctx, s, store.Cookie.Expire, requestCookie)
155 if err != nil {
151 if err != nil {
156 return err
152 return err
157 }
153 }
@@ -180,40 +176,13 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro
180 return nil, fmt.Errorf("cookie signature not valid")
176 return nil, fmt.Errorf("cookie signature not valid")
181 }
177 }
182 ctx := req.Context()
178 ctx := req.Context()
183− session, err := store.loadSessionFromString(ctx, string(val))
179+ session, err := store.loadSessionFromTicket(ctx, string(val))
184 if err != nil {
180 if err != nil {
185 return nil, fmt.Errorf("error loading session: %s", err)
181 return nil, fmt.Errorf("error loading session: %s", err)
186 }
182 }
187 return session, nil
183 return session, nil
188}
184}
189
185
190−// loadSessionFromString loads the session based on the ticket value
191−func (store *SessionStore) loadSessionFromString(ctx context.Context, value string) (*sessions.SessionState, error) {
192− ticket, err := decodeTicket(store.Cookie.Name, value)
193− if err != nil {
194− return nil, err
195− }
196−
197− resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.Cookie.Name))
198− if err != nil {
199− return nil, err
200− }
201−
202− block, err := aes.NewCipher(ticket.Secret)
203− if err != nil {
204− return nil, err
205− }
206− // Use secret as the IV too, because each entry has it's own key
207− stream := cipher.NewCFBDecrypter(block, ticket.Secret)
208− stream.XORKeyStream(resultBytes, resultBytes)
209−
210− session, err := sessions.DecodeSessionState(string(resultBytes), store.CookieCipher)
211− if err != nil {
212− return nil, err
213− }
214− return session, nil
215−}
216−
217// Clear clears any saved session information for a given ticket cookie
186// Clear clears any saved session information for a given ticket cookie
218// from redis, and then clears the session
187// from redis, and then clears the session
219func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
188func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
@@ -253,36 +222,25 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
253 return nil
222 return nil
254}
223}
255
224
256−// makeCookie makes a cookie, signing the value if present
225+// saveSession encodes a session with a GCM cipher & saves the data into Redis
257−func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
258− if value != "" {
259− value = encryption.SignedValue(store.Cookie.Secret, store.Cookie.Name, []byte(value), now)
260− }
261− return cookies.MakeCookieFromOptions(
262− req,
263− store.Cookie.Name,
264− value,
265− store.Cookie,
266− expires,
267− now,
268− )
269−}
270−
271−func (store *SessionStore) storeValue(ctx context.Context, value string, expiration time.Duration, requestCookie *http.Cookie) (string, error) {
226+func (store *SessionStore) saveSession(ctx context.Context, s *sessions.SessionState, expiration time.Duration, requestCookie *http.Cookie) (string, error) {
272 ticket, err := store.getTicket(requestCookie)
227 ticket, err := store.getTicket(requestCookie)
273 if err != nil {
228 if err != nil {
274 return "", fmt.Errorf("error getting ticket: %v", err)
229 return "", fmt.Errorf("error getting ticket: %v", err)
275 }
230 }
276
231
277− ciphertext := make([]byte, len(value))
278− block, err := aes.NewCipher(ticket.Secret)
232+ c, err := encryption.NewGCMCipher(ticket.Secret)
279 if err != nil {
233 if err != nil {
280 return "", fmt.Errorf("error initiating cipher block %s", err)
234 return "", fmt.Errorf("error initiating cipher block %s", err)
281 }
235 }
282
236
283− // Use secret as the Initialization Vector too, because each entry has it's own key
237+ // Use AES-GCM since it provides authenticated encryption
284− stream := cipher.NewCFBEncrypter(block, ticket.Secret)
238+ // AES-CFB used in cookies has the cookie signing SHA to get around the lack of
285− stream.XORKeyStream(ciphertext, []byte(value))
239+ // authentication in AES-CFB
240+ ciphertext, err := s.EncodeSessionState(c, false)
241+ if err != nil {
242+ return "", err
243+ }
286
244
287 handle := ticket.asHandle(store.Cookie.Name)
245 handle := ticket.asHandle(store.Cookie.Name)
288 err = store.Client.Set(ctx, handle, ciphertext, expiration)
246 err = store.Client.Set(ctx, handle, ciphertext, expiration)
@@ -292,6 +250,67 @@ func (store *SessionStore) storeValue(ctx context.Context, value string, expirat
292 return ticket.encodeTicket(store.Cookie.Name), nil
250 return ticket.encodeTicket(store.Cookie.Name), nil
293}
251}
294
252
253+// loadSessionFromTicket loads the session based on the ticket value
254+func (store *SessionStore) loadSessionFromTicket(ctx context.Context, value string) (*sessions.SessionState, error) {
255+ ticket, err := decodeTicket(store.Cookie.Name, value)
256+ if err != nil {
257+ return nil, err
258+ }
259+
260+ resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.Cookie.Name))
261+ if err != nil {
262+ return nil, err
263+ }
264+
265+ c, err := encryption.NewGCMCipher(ticket.Secret)
266+ if err != nil {
267+ return nil, err
268+ }
269+
270+ session, err := sessions.DecodeSessionState(resultBytes, c, false)
271+ if err != nil {
272+ // The GCM cipher will error due to a legacy JSON payload not passing
273+ // the authentication check part of AES GCM encryption.
274+ // In that case, we can attempt to fallback to try a legacy load
275+ legacyCipher := encryption.NewBase64Cipher(store.CookieCipher)
276+ return legacyV5DecodeSession(resultBytes, ticket, legacyCipher)
277+ }
278+ return session, nil
279+}
280+
281+// legacyV5DecodeSession loads the session based on the ticket value
282+// This fallback uses V5 style encryption of Base64 + AES CFB
283+func legacyV5DecodeSession(resultBytes []byte, ticket *TicketData, c encryption.Cipher) (*sessions.SessionState, error) {
284+ block, err := aes.NewCipher(ticket.Secret)
285+ if err != nil {
286+ return nil, err
287+ }
288+ // Use secret as the IV too, because each entry has it's own key
289+ stream := cipher.NewCFBDecrypter(block, ticket.Secret)
290+ stream.XORKeyStream(resultBytes, resultBytes)
291+
292+ session, err := sessions.LegacyV5DecodeSessionState(string(resultBytes), c)
293+ if err != nil {
294+ return nil, err
295+ }
296+ return session, nil
297+}
298+
299+// makeCookie makes a cookie, signing the value if present
300+func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
301+ if value != "" {
302+ value = encryption.SignedValue(store.Cookie.Secret, store.Cookie.Name, []byte(value), now)
303+ }
304+ return cookies.MakeCookieFromOptions(
305+ req,
306+ store.Cookie.Name,
307+ value,
308+ store.Cookie,
309+ expires,
310+ now,
311+ )
312+}
313+
295// getTicket retrieves an existing ticket from the cookie if present,
314// getTicket retrieves an existing ticket from the cookie if present,
296// or creates a new ticket
315// or creates a new ticket
297func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, error) {
316func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, error) {
pkg/sessions/redis/session_store_test.go → pkg/sessions/redis/redis_store_test.gorenamed+64
@@ -1,6 +1,11 @@
1package redis
1package redis
2
2
3import (
3import (
4+ "crypto/aes"
5+ "crypto/cipher"
6+ "crypto/rand"
7+ "fmt"
8+ "io"
4 "log"
9 "log"
5 "os"
10 "os"
6 "testing"
11 "testing"
@@ -17,6 +22,65 @@ import (
17 . "github.com/onsi/gomega"
22 . "github.com/onsi/gomega"
18)
23)
19
24
25+// TestLegacyV5DecodeSession tests the fallback to LegacyV5DecodeSession
26+// when a V5 encoded session is in Redis
27+//
28+// TODO: Remove when this is deprecated (likely V7)
29+func Test_legacyV5DecodeSession(t *testing.T) {
30+ testCases, _, legacyCipher := sessionsapi.CreateLegacyV5TestCases(t)
31+
32+ for testName, tc := range testCases {
33+ t.Run(testName, func(t *testing.T) {
34+ g := NewWithT(t)
35+
36+ secret := make([]byte, aes.BlockSize)
37+ _, err := io.ReadFull(rand.Reader, secret)
38+ g.Expect(err).ToNot(HaveOccurred())
39+ ticket := &TicketData{
40+ TicketID: "",
41+ Secret: secret,
42+ }
43+
44+ encrypted, err := legacyStoreValue(tc.Input, ticket.Secret)
45+ g.Expect(err).ToNot(HaveOccurred())
46+
47+ ss, err := legacyV5DecodeSession(encrypted, ticket, legacyCipher)
48+ if tc.Error {
49+ g.Expect(err).To(HaveOccurred())
50+ g.Expect(ss).To(BeNil())
51+ return
52+ }
53+ g.Expect(err).ToNot(HaveOccurred())
54+
55+ // Compare sessions without *time.Time fields
56+ exp := *tc.Output
57+ exp.CreatedAt = nil
58+ exp.ExpiresOn = nil
59+ act := *ss
60+ act.CreatedAt = nil
61+ act.ExpiresOn = nil
62+ g.Expect(exp).To(Equal(act))
63+ })
64+ }
65+}
66+
67+// legacyStoreValue implements the legacy V5 Redis store AES-CFB value encryption
68+//
69+// TODO: Remove when this is deprecated (likely V7)
70+func legacyStoreValue(value string, ticketSecret []byte) ([]byte, error) {
71+ ciphertext := make([]byte, len(value))
72+ block, err := aes.NewCipher(ticketSecret)
73+ if err != nil {
74+ return nil, fmt.Errorf("error initiating cipher block: %v", err)
75+ }
76+
77+ // Use secret as the Initialization Vector too, because each entry has it's own key
78+ stream := cipher.NewCFBEncrypter(block, ticketSecret)
79+ stream.XORKeyStream(ciphertext, []byte(value))
80+
81+ return ciphertext, nil
82+}
83+
20func TestSessionStore(t *testing.T) {
84func TestSessionStore(t *testing.T) {
21 logger.SetOutput(GinkgoWriter)
85 logger.SetOutput(GinkgoWriter)
22
86