1 package security
2
3 import (
4 "bytes"
5 "context"
6 "fmt"
7 "mime/multipart"
8 "net/http"
9 "net/url"
10 "strings"
11 "testing"
12
13 "github.com/go-openapi/errors"
14 "github.com/go-openapi/runtime"
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17 )
18
19 const (
20 owners = "owners_auth"
21 validToken = "token123"
22 invalidToken = "token124"
23 principal = "admin"
24 authPath = "/blah"
25 invalidParam = "access_toke"
26 )
27
28 type authExpectation uint8
29
30 const (
31 expectIsAuthorized authExpectation = iota
32 expectInvalidAuthorization
33 expectNoAuthorization
34 )
35
36 func TestBearerAuth(t *testing.T) {
37 bearerAuth := ScopedTokenAuthentication(func(token string, _ []string) (interface{}, error) {
38 if token == validToken {
39 return principal, nil
40 }
41 return nil, errors.Unauthenticated("bearer")
42 })
43 ba := BearerAuth(owners, bearerAuth)
44 ctx := context.Background()
45
46 t.Run("with valid bearer auth", func(t *testing.T) {
47 t.Run("token in query param",
48 testAuthenticateBearerInQuery(ctx, ba, "", validToken, expectIsAuthorized),
49 )
50 t.Run("token in header",
51 testAuthenticateBearerInHeader(ctx, ba, "", validToken, expectIsAuthorized),
52 )
53 t.Run("token in urlencoded form",
54 testAuthenticateBearerInForm(ctx, ba, "", validToken, expectIsAuthorized),
55 )
56 t.Run("token in multipart form",
57 testAuthenticateBearerInMultipartForm(ctx, ba, "", validToken, expectIsAuthorized),
58 )
59 })
60
61 t.Run("with invalid token", func(t *testing.T) {
62 t.Run("token in query param",
63 testAuthenticateBearerInQuery(ctx, ba, "", invalidToken, expectInvalidAuthorization),
64 )
65 t.Run("token in header",
66 testAuthenticateBearerInHeader(ctx, ba, "", invalidToken, expectInvalidAuthorization),
67 )
68 t.Run("token in urlencoded form",
69 testAuthenticateBearerInForm(ctx, ba, "", invalidToken, expectInvalidAuthorization),
70 )
71 t.Run("token in multipart form",
72 testAuthenticateBearerInMultipartForm(ctx, ba, "", invalidToken, expectInvalidAuthorization),
73 )
74 })
75
76 t.Run("with missing auth", func(t *testing.T) {
77 t.Run("token in query param",
78 testAuthenticateBearerInQuery(ctx, ba, invalidParam, validToken, expectNoAuthorization),
79 )
80 t.Run("token in header",
81 testAuthenticateBearerInHeader(ctx, ba, "Beare", validToken, expectNoAuthorization),
82 )
83 t.Run("token in urlencoded form",
84 testAuthenticateBearerInForm(ctx, ba, invalidParam, validToken, expectNoAuthorization),
85 )
86 t.Run("token in multipart form",
87 testAuthenticateBearerInMultipartForm(ctx, ba, invalidParam, validToken, expectNoAuthorization),
88 )
89 })
90 }
91
92 func TestBearerAuthCtx(t *testing.T) {
93 bearerAuthCtx := ScopedTokenAuthenticationCtx(func(ctx context.Context, token string, _ []string) (context.Context, interface{}, error) {
94 if token == validToken {
95 return context.WithValue(ctx, extra, extraWisdom), principal, nil
96 }
97 return context.WithValue(ctx, reason, expReason), nil, errors.Unauthenticated("bearer")
98 })
99 ba := BearerAuthCtx(owners, bearerAuthCtx)
100 ctx := context.WithValue(context.Background(), original, wisdom)
101
102 assertContextOK := func(requestContext context.Context, t *testing.T) {
103
104 assert.Equal(t, wisdom, requestContext.Value(original))
105 assert.Equal(t, extraWisdom, requestContext.Value(extra))
106 assert.Nil(t, requestContext.Value(reason))
107 }
108
109 assertContextKO := func(requestContext context.Context, t *testing.T) {
110
111 assert.Equal(t, wisdom, requestContext.Value(original))
112 assert.Nil(t, requestContext.Value(extra))
113 assert.Equal(t, expReason, requestContext.Value(reason))
114 }
115
116 assertContextNone := func(requestContext context.Context, t *testing.T) {
117
118 assert.Equal(t, wisdom, requestContext.Value(original))
119 assert.Nil(t, requestContext.Value(extra))
120 assert.Nil(t, requestContext.Value(reason))
121 }
122
123 t.Run("with valid bearer auth", func(t *testing.T) {
124 t.Run("token in query param",
125 testAuthenticateBearerInQuery(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
126 )
127 t.Run("token in header",
128 testAuthenticateBearerInHeader(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
129 )
130 t.Run("token in urlencoded form",
131 testAuthenticateBearerInForm(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
132 )
133 t.Run("token in multipart form",
134 testAuthenticateBearerInMultipartForm(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
135 )
136 })
137
138 t.Run("with invalid token", func(t *testing.T) {
139 t.Run("token in query param",
140 testAuthenticateBearerInQuery(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
141 )
142 t.Run("token in header",
143 testAuthenticateBearerInHeader(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
144 )
145 t.Run("token in urlencoded form",
146 testAuthenticateBearerInForm(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
147 )
148 t.Run("token in multipart form",
149 testAuthenticateBearerInMultipartForm(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
150 )
151 })
152
153 t.Run("with missing auth", func(t *testing.T) {
154 t.Run("token in query param",
155 testAuthenticateBearerInQuery(ctx, ba, invalidParam, validToken, expectNoAuthorization, assertContextNone),
156 )
157 t.Run("token in header",
158 testAuthenticateBearerInHeader(ctx, ba, "Beare", validToken, expectNoAuthorization, assertContextNone),
159 )
160 t.Run("token in urlencoded form",
161 testAuthenticateBearerInForm(ctx, ba, invalidParam, validToken, expectNoAuthorization, assertContextNone),
162 )
163 t.Run("token in multipart form",
164 testAuthenticateBearerInMultipartForm(ctx, ba, invalidParam, validToken, expectNoAuthorization, assertContextNone),
165 )
166 })
167 }
168
169 func testIsAuthorized(_ context.Context, req *http.Request, authorizer runtime.Authenticator, expectAuthorized authExpectation, extraAsserters ...func(context.Context, *testing.T)) func(*testing.T) {
170 return func(t *testing.T) {
171 hasToken, usr, err := authorizer.Authenticate(&ScopedAuthRequest{Request: req})
172 switch expectAuthorized {
173
174 case expectIsAuthorized:
175 require.NoError(t, err)
176 assert.True(t, hasToken)
177 assert.Equal(t, principal, usr)
178 assert.Equal(t, owners, OAuth2SchemeName(req))
179
180 case expectInvalidAuthorization:
181 require.Error(t, err)
182 require.ErrorContains(t, err, "unauthenticated")
183 assert.True(t, hasToken)
184 assert.Nil(t, usr)
185 assert.Equal(t, owners, OAuth2SchemeName(req))
186
187 case expectNoAuthorization:
188 require.NoError(t, err)
189 assert.False(t, hasToken)
190 assert.Nil(t, usr)
191 assert.Empty(t, OAuth2SchemeName(req))
192 }
193
194 for _, contextAsserter := range extraAsserters {
195 contextAsserter(req.Context(), t)
196 }
197 }
198 }
199
200 func shouldAuthorizeOrNot(expectAuthorized authExpectation) string {
201 if expectAuthorized == expectIsAuthorized {
202 return "should authorize"
203 }
204
205 return "should not authorize"
206 }
207
208 func testAuthenticateBearerInQuery(
209
210
211
212 ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
213 extraAsserters ...func(context.Context, *testing.T),
214 ) func(*testing.T) {
215 if parameter == "" {
216 parameter = accessTokenParam
217 }
218
219 return func(t *testing.T) {
220 req, err := http.NewRequestWithContext(
221 ctx, http.MethodGet,
222 fmt.Sprintf("%s?%s=%s", authPath, parameter, token),
223 nil,
224 )
225 require.NoError(t, err)
226
227 t.Run(
228 shouldAuthorizeOrNot(expectAuthorized),
229 testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
230 )
231 }
232 }
233
234 func testAuthenticateBearerInHeader(
235
236 ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
237 extraAsserters ...func(context.Context, *testing.T),
238 ) func(*testing.T) {
239 if parameter == "" {
240 parameter = "Bearer"
241 }
242
243 return func(t *testing.T) {
244 req, err := http.NewRequestWithContext(ctx, http.MethodGet, authPath, nil)
245 require.NoError(t, err)
246 req.Header.Set(runtime.HeaderAuthorization, fmt.Sprintf("%s %s", parameter, token))
247
248 t.Run(
249 shouldAuthorizeOrNot(expectAuthorized),
250 testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
251 )
252 }
253 }
254
255 func testAuthenticateBearerInForm(
256
257 ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
258 extraAsserters ...func(context.Context, *testing.T),
259 ) func(*testing.T) {
260 if parameter == "" {
261 parameter = accessTokenParam
262 }
263
264 return func(t *testing.T) {
265 body := url.Values(map[string][]string{})
266 body.Set(parameter, token)
267 req, err := http.NewRequestWithContext(ctx, http.MethodPost, authPath, strings.NewReader(body.Encode()))
268 require.NoError(t, err)
269 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
270
271 t.Run(
272 shouldAuthorizeOrNot(expectAuthorized),
273 testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
274 )
275 }
276 }
277 func testAuthenticateBearerInMultipartForm(
278
279 ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
280 extraAsserters ...func(context.Context, *testing.T),
281 ) func(*testing.T) {
282 if parameter == "" {
283 parameter = accessTokenParam
284 }
285
286 return func(t *testing.T) {
287 body := bytes.NewBuffer(nil)
288 writer := multipart.NewWriter(body)
289 require.NoError(t, writer.WriteField(parameter, token))
290 require.NoError(t, writer.Close())
291 req, err := http.NewRequestWithContext(ctx, http.MethodPost, authPath, body)
292 require.NoError(t, err)
293 req.Header.Set("Content-Type", writer.FormDataContentType())
294
295 t.Run(
296 shouldAuthorizeOrNot(expectAuthorized),
297 testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
298 )
299 }
300 }
301
View as plain text