@@ -0,0 +1,417 @@
1+package header
2+
3+import (
4+ "encoding/base64"
5+ "errors"
6+ "net/http"
7+
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
9+ sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
10+ . "github.com/onsi/ginkgo"
11+ . "github.com/onsi/ginkgo/extensions/table"
12+ . "github.com/onsi/gomega"
13+)
14+
15+var _ = Describe("Injector Suite", func() {
16+ Context("NewInjector", func() {
17+ type newInjectorTableInput struct {
18+ headers []options.Header
19+ initialHeaders http.Header
20+ session *sessionsapi.SessionState
21+ expectedHeaders http.Header
22+ expectedErr error
23+ }
24+
25+ DescribeTable("creates an injector",
26+ func(in newInjectorTableInput) {
27+ injector, err := NewInjector(in.headers)
28+ if in.expectedErr != nil {
29+ Expect(err).To(MatchError(in.expectedErr))
30+ Expect(injector).To(BeNil())
31+ return
32+ }
33+
34+ Expect(err).ToNot(HaveOccurred())
35+ Expect(injector).ToNot(BeNil())
36+
37+ headers := in.initialHeaders.Clone()
38+ injector.Inject(headers, in.session)
39+ Expect(headers).To(Equal(in.expectedHeaders))
40+ },
41+ Entry("with no configured headers", newInjectorTableInput{
42+ headers: []options.Header{},
43+ initialHeaders: http.Header{
44+ "foo": []string{"bar", "baz"},
45+ },
46+ session: &sessionsapi.SessionState{},
47+ expectedHeaders: http.Header{
48+ "foo": []string{"bar", "baz"},
49+ },
50+ expectedErr: nil,
51+ }),
52+ Entry("with a static valued header from base64", newInjectorTableInput{
53+ headers: []options.Header{
54+ {
55+ Name: "Secret",
56+ Values: []options.HeaderValue{
57+ {
58+ SecretSource: &options.SecretSource{
59+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("super-secret"))),
60+ },
61+ },
62+ },
63+ },
64+ },
65+ initialHeaders: http.Header{
66+ "foo": []string{"bar", "baz"},
67+ },
68+ session: &sessionsapi.SessionState{},
69+ expectedHeaders: http.Header{
70+ "foo": []string{"bar", "baz"},
71+ "Secret": []string{"super-secret"},
72+ },
73+ expectedErr: nil,
74+ }),
75+ Entry("with a static valued header from env", newInjectorTableInput{
76+ headers: []options.Header{
77+ {
78+ Name: "Secret",
79+ Values: []options.HeaderValue{
80+ {
81+ SecretSource: &options.SecretSource{
82+ FromEnv: "SECRET_ENV",
83+ },
84+ },
85+ },
86+ },
87+ },
88+ initialHeaders: http.Header{
89+ "foo": []string{"bar", "baz"},
90+ },
91+ session: &sessionsapi.SessionState{},
92+ expectedHeaders: http.Header{
93+ "foo": []string{"bar", "baz"},
94+ "Secret": []string{"super-secret-env"},
95+ },
96+ expectedErr: nil,
97+ }),
98+ Entry("with a claim valued header", newInjectorTableInput{
99+ headers: []options.Header{
100+ {
101+ Name: "Claim",
102+ Values: []options.HeaderValue{
103+ {
104+ ClaimSource: &options.ClaimSource{
105+ Claim: "id_token",
106+ },
107+ },
108+ },
109+ },
110+ },
111+ initialHeaders: http.Header{
112+ "foo": []string{"bar", "baz"},
113+ },
114+ session: &sessionsapi.SessionState{
115+ IDToken: "IDToken-1234",
116+ },
117+ expectedHeaders: http.Header{
118+ "foo": []string{"bar", "baz"},
119+ "Claim": []string{"IDToken-1234"},
120+ },
121+ expectedErr: nil,
122+ }),
123+ Entry("with a claim valued header and a nil session", newInjectorTableInput{
124+ headers: []options.Header{
125+ {
126+ Name: "Claim",
127+ Values: []options.HeaderValue{
128+ {
129+ ClaimSource: &options.ClaimSource{
130+ Claim: "id_token",
131+ },
132+ },
133+ },
134+ },
135+ },
136+ initialHeaders: http.Header{
137+ "foo": []string{"bar", "baz"},
138+ },
139+ session: nil,
140+ expectedHeaders: http.Header{
141+ "foo": []string{"bar", "baz"},
142+ },
143+ expectedErr: nil,
144+ }),
145+ Entry("with a prefixed claim valued header", newInjectorTableInput{
146+ headers: []options.Header{
147+ {
148+ Name: "Claim",
149+ Values: []options.HeaderValue{
150+ {
151+ ClaimSource: &options.ClaimSource{
152+ Claim: "id_token",
153+ Prefix: "Bearer ",
154+ },
155+ },
156+ },
157+ },
158+ },
159+ initialHeaders: http.Header{
160+ "foo": []string{"bar", "baz"},
161+ },
162+ session: &sessionsapi.SessionState{
163+ IDToken: "IDToken-1234",
164+ },
165+ expectedHeaders: http.Header{
166+ "foo": []string{"bar", "baz"},
167+ "Claim": []string{"Bearer IDToken-1234"},
168+ },
169+ expectedErr: nil,
170+ }),
171+ Entry("with a prefixed claim valued header missing the claim", newInjectorTableInput{
172+ headers: []options.Header{
173+ {
174+ Name: "Claim",
175+ Values: []options.HeaderValue{
176+ {
177+ ClaimSource: &options.ClaimSource{
178+ Claim: "idToken",
179+ Prefix: "Bearer ",
180+ },
181+ },
182+ },
183+ },
184+ },
185+ initialHeaders: http.Header{
186+ "foo": []string{"bar", "baz"},
187+ },
188+ session: &sessionsapi.SessionState{},
189+ expectedHeaders: http.Header{
190+ "foo": []string{"bar", "baz"},
191+ },
192+ expectedErr: nil,
193+ }),
194+ Entry("with a basicAuthPassword and claim valued header", newInjectorTableInput{
195+ headers: []options.Header{
196+ {
197+ Name: "X-Auth-Request-Authorization",
198+ Values: []options.HeaderValue{
199+ {
200+ ClaimSource: &options.ClaimSource{
201+ Claim: "user",
202+ BasicAuthPassword: &options.SecretSource{
203+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
204+ },
205+ },
206+ },
207+ },
208+ },
209+ },
210+ initialHeaders: http.Header{
211+ "foo": []string{"bar", "baz"},
212+ },
213+ session: &sessionsapi.SessionState{
214+ User: "user-123",
215+ },
216+ expectedHeaders: http.Header{
217+ "foo": []string{"bar", "baz"},
218+ "X-Auth-Request-Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte("user-123:basic-password"))},
219+ },
220+ expectedErr: nil,
221+ }),
222+ Entry("with a basicAuthPassword and claim valued header missing the claim", newInjectorTableInput{
223+ headers: []options.Header{
224+ {
225+ Name: "X-Auth-Request-Authorization",
226+ Values: []options.HeaderValue{
227+ {
228+ ClaimSource: &options.ClaimSource{
229+ Claim: "user",
230+ BasicAuthPassword: &options.SecretSource{
231+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
232+ },
233+ },
234+ },
235+ },
236+ },
237+ },
238+ initialHeaders: http.Header{
239+ "foo": []string{"bar", "baz"},
240+ },
241+ session: &sessionsapi.SessionState{},
242+ expectedHeaders: http.Header{
243+ "foo": []string{"bar", "baz"},
244+ },
245+ expectedErr: nil,
246+ }),
247+ Entry("with a header that already exists", newInjectorTableInput{
248+ headers: []options.Header{
249+ {
250+ Name: "X-Auth-Request-User",
251+ Values: []options.HeaderValue{
252+ {
253+ ClaimSource: &options.ClaimSource{
254+ Claim: "user",
255+ },
256+ },
257+ },
258+ },
259+ },
260+ initialHeaders: http.Header{
261+ "X-Auth-Request-User": []string{"user"},
262+ },
263+ session: &sessionsapi.SessionState{
264+ User: "user-123",
265+ },
266+ expectedHeaders: http.Header{
267+ "X-Auth-Request-User": []string{"user", "user-123"},
268+ },
269+ expectedErr: nil,
270+ }),
271+ Entry("with a claim and secret valued header value", newInjectorTableInput{
272+ headers: []options.Header{
273+ {
274+ Name: "Claim",
275+ Values: []options.HeaderValue{
276+ {
277+ ClaimSource: &options.ClaimSource{
278+ Claim: "id_token",
279+ },
280+ SecretSource: &options.SecretSource{
281+ FromEnv: "SECRET_ENV",
282+ },
283+ },
284+ },
285+ },
286+ },
287+ initialHeaders: http.Header{
288+ "foo": []string{"bar", "baz"},
289+ },
290+ session: &sessionsapi.SessionState{
291+ IDToken: "IDToken-1234",
292+ },
293+ expectedHeaders: nil,
294+ expectedErr: errors.New("error building injector for header \"Claim\": header \"Claim\" value has multiple entries: only one entry per value is allowed"),
295+ }),
296+ Entry("with an invalid static valued header", newInjectorTableInput{
297+ headers: []options.Header{
298+ {
299+ Name: "Secret",
300+ Values: []options.HeaderValue{
301+ {
302+ SecretSource: &options.SecretSource{
303+ FromEnv: "SECRET_ENV",
304+ FromFile: "secret-file",
305+ },
306+ },
307+ },
308+ },
309+ },
310+ initialHeaders: http.Header{
311+ "foo": []string{"bar", "baz"},
312+ },
313+ session: &sessionsapi.SessionState{},
314+ expectedHeaders: nil,
315+ expectedErr: errors.New("error building injector for header \"Secret\": error getting secret value: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"),
316+ }),
317+ Entry("with an invalid basicAuthPassword claim valued header", newInjectorTableInput{
318+ headers: []options.Header{
319+ {
320+ Name: "X-Auth-Request-Authorization",
321+ Values: []options.HeaderValue{
322+ {
323+ ClaimSource: &options.ClaimSource{
324+ Claim: "user",
325+ BasicAuthPassword: &options.SecretSource{
326+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
327+ FromEnv: "SECRET_ENV",
328+ },
329+ },
330+ },
331+ },
332+ },
333+ },
334+ initialHeaders: http.Header{
335+ "foo": []string{"bar", "baz"},
336+ },
337+ session: &sessionsapi.SessionState{
338+ User: "user-123",
339+ },
340+ expectedHeaders: nil,
341+ expectedErr: errors.New("error building injector for header \"X-Auth-Request-Authorization\": error loading basicAuthPassword: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"),
342+ }),
343+ Entry("with a mix of configured headers", newInjectorTableInput{
344+ headers: []options.Header{
345+ {
346+ Name: "X-Auth-Request-Authorization",
347+ Values: []options.HeaderValue{
348+ {
349+ ClaimSource: &options.ClaimSource{
350+ Claim: "user",
351+ BasicAuthPassword: &options.SecretSource{
352+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
353+ },
354+ },
355+ },
356+ },
357+ },
358+ {
359+ Name: "X-Auth-Request-User",
360+ Values: []options.HeaderValue{
361+ {
362+ ClaimSource: &options.ClaimSource{
363+ Claim: "user",
364+ },
365+ },
366+ },
367+ },
368+ {
369+ Name: "X-Auth-Request-Email",
370+ Values: []options.HeaderValue{
371+ {
372+ ClaimSource: &options.ClaimSource{
373+ Claim: "email",
374+ },
375+ },
376+ },
377+ },
378+ {
379+ Name: "X-Auth-Request-Version-Info",
380+ Values: []options.HeaderValue{
381+ {
382+ SecretSource: &options.SecretSource{
383+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("major=1"))),
384+ },
385+ },
386+ {
387+ SecretSource: &options.SecretSource{
388+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("minor=2"))),
389+ },
390+ },
391+ {
392+ SecretSource: &options.SecretSource{
393+ Value: []byte(base64.StdEncoding.EncodeToString([]byte("patch=3"))),
394+ },
395+ },
396+ },
397+ },
398+ },
399+ initialHeaders: http.Header{
400+ "foo": []string{"bar", "baz"},
401+ },
402+ session: &sessionsapi.SessionState{
403+ User: "user-123",
404+ Email: "user@example.com",
405+ },
406+ expectedHeaders: http.Header{
407+ "foo": []string{"bar", "baz"},
408+ "X-Auth-Request-Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte("user-123:basic-password"))},
409+ "X-Auth-Request-User": []string{"user-123"},
410+ "X-Auth-Request-Email": []string{"user@example.com"},
411+ "X-Auth-Request-Version-Info": []string{"major=1", "minor=2", "patch=3"},
412+ },
413+ expectedErr: nil,
414+ }),
415+ )
416+ })
417+})