1
21
22 package pkce
23
24 import (
25 "context"
26 "crypto/sha256"
27 "encoding/base64"
28 "fmt"
29 "testing"
30
31 "github.com/stretchr/testify/assert"
32 "github.com/stretchr/testify/require"
33
34 "github.com/ory/fosite"
35 "github.com/ory/fosite/handler/oauth2"
36 "github.com/ory/fosite/storage"
37 )
38
39 type mockCodeStrategy struct {
40 signature string
41 }
42
43 func (m *mockCodeStrategy) AuthorizeCodeSignature(token string) string {
44 return m.signature
45 }
46
47 func (m *mockCodeStrategy) GenerateAuthorizeCode(ctx context.Context, requester fosite.Requester) (token string, signature string, err error) {
48 return "", "", nil
49 }
50
51 func (m *mockCodeStrategy) ValidateAuthorizeCode(ctx context.Context, requester fosite.Requester, token string) (err error) {
52 return nil
53 }
54
55 func TestPKCEHandleAuthorizeEndpointRequest(t *testing.T) {
56 h := &Handler{
57 Storage: storage.NewMemoryStore(),
58 AuthorizeCodeStrategy: new(oauth2.HMACSHAStrategy),
59 }
60 w := fosite.NewAuthorizeResponse()
61 r := fosite.NewAuthorizeRequest()
62 c := &fosite.DefaultClient{}
63 r.Client = c
64
65 w.AddParameter("code", "foo")
66
67 r.Form.Add("code_challenge", "challenge")
68 r.Form.Add("code_challenge_method", "plain")
69
70 r.ResponseTypes = fosite.Arguments{}
71 require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
72
73 r.ResponseTypes = fosite.Arguments{"code"}
74 require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
75
76 r.ResponseTypes = fosite.Arguments{"code", "id_token"}
77 require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
78
79 c.Public = true
80 h.EnablePlainChallengeMethod = true
81 require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
82
83 c.Public = false
84 h.EnablePlainChallengeMethod = true
85 require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
86
87 h.EnablePlainChallengeMethod = false
88 require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
89
90 r.Form.Set("code_challenge_method", "S256")
91 r.Form.Set("code_challenge", "")
92 h.Force = true
93 require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
94
95 r.Form.Set("code_challenge", "challenge")
96 require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
97 }
98
99 func TestPKCEHandlerValidate(t *testing.T) {
100 s := storage.NewMemoryStore()
101 ms := &mockCodeStrategy{}
102 h := &Handler{
103 Storage: s, AuthorizeCodeStrategy: ms,
104 }
105 pc := &fosite.DefaultClient{Public: true}
106
107 s256verifier := "KGCt4m8AmjUvIR5ArTByrmehjtbxn1A49YpTZhsH8N7fhDr7LQayn9xx6mck"
108 hash := sha256.New()
109 hash.Write([]byte(s256verifier))
110 s256challenge := base64.RawURLEncoding.EncodeToString(hash.Sum([]byte{}))
111
112 for k, tc := range []struct {
113 d string
114 grant string
115 force bool
116 enablePlain bool
117 challenge string
118 method string
119 verifier string
120 code string
121 expectErr error
122 client *fosite.DefaultClient
123 }{
124 {
125 d: "fails because not auth code flow",
126 grant: "not_authorization_code",
127 expectErr: fosite.ErrUnknownRequest,
128 },
129 {
130 d: "passes with private client",
131 grant: "authorization_code",
132 challenge: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
133 verifier: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
134 method: "plain",
135 client: &fosite.DefaultClient{Public: false},
136 enablePlain: true,
137 force: true,
138 code: "valid-code-1",
139 },
140 {
141 d: "fails because invalid code",
142 grant: "authorization_code",
143 expectErr: fosite.ErrInvalidGrant,
144 client: pc,
145 code: "invalid-code-2",
146 },
147 {
148 d: "passes because auth code flow but pkce is not forced and no challenge given",
149 grant: "authorization_code",
150 client: pc,
151 code: "valid-code-3",
152 },
153 {
154 d: "fails because auth code flow and pkce challenge given but plain is disabled",
155 grant: "authorization_code",
156 challenge: "foo",
157 client: pc,
158 expectErr: fosite.ErrInvalidRequest,
159 code: "valid-code-4",
160 },
161 {
162 d: "passes",
163 grant: "authorization_code",
164 challenge: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
165 verifier: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
166 client: pc,
167 enablePlain: true,
168 force: true,
169 code: "valid-code-5",
170 },
171 {
172 d: "passes",
173 grant: "authorization_code",
174 challenge: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
175 verifier: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
176 method: "plain",
177 client: pc,
178 enablePlain: true,
179 force: true,
180 code: "valid-code-6",
181 },
182 {
183 d: "fails because challenge and verifier do not match",
184 grant: "authorization_code",
185 challenge: "not-foo",
186 verifier: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
187 method: "plain",
188 client: pc,
189 enablePlain: true,
190 code: "valid-code-7",
191 expectErr: fosite.ErrInvalidGrant,
192 },
193 {
194 d: "fails because challenge and verifier do not match",
195 grant: "authorization_code",
196 challenge: "not-foonot-foonot-foonot-foonot-foonot-foonot-foonot-foo",
197 verifier: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
198 client: pc,
199 enablePlain: true,
200 code: "valid-code-8",
201 expectErr: fosite.ErrInvalidGrant,
202 },
203 {
204 d: "fails because verifier is too short",
205 grant: "authorization_code",
206 challenge: "foo",
207 verifier: "foo",
208 method: "S256",
209 client: pc,
210 force: true,
211 code: "valid-code-9a",
212 expectErr: fosite.ErrInvalidGrant,
213 },
214 {
215 d: "fails because verifier is too long",
216 grant: "authorization_code",
217 challenge: "foo",
218 verifier: "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
219 method: "S256",
220 client: pc,
221 force: true,
222 code: "valid-code-10",
223 expectErr: fosite.ErrInvalidGrant,
224 },
225 {
226 d: "fails because verifier is malformed",
227 grant: "authorization_code",
228 challenge: "foo",
229 verifier: `(!"/$%Z&$T()/)OUZI>$"&=/T(PUOI>"%/)TUOI&/(O/()RGTE>=/(%"/()="$/)(=()=/R/()=))`,
230 method: "S256",
231 client: pc,
232 force: true,
233 code: "valid-code-11",
234 expectErr: fosite.ErrInvalidGrant,
235 },
236 {
237 d: "fails because challenge and verifier do not match",
238 grant: "authorization_code",
239 challenge: "Zm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9v",
240 verifier: "Zm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9v",
241 method: "S256",
242 client: pc,
243 force: true,
244 code: "valid-code-12",
245 expectErr: fosite.ErrInvalidGrant,
246 },
247 {
248 d: "passes because challenge and verifier match",
249 grant: "authorization_code",
250 challenge: s256challenge,
251 verifier: s256verifier,
252 method: "S256",
253 client: pc,
254 force: true,
255 code: "valid-code-13",
256 },
257 } {
258 t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
259 h.EnablePlainChallengeMethod = tc.enablePlain
260 h.Force = tc.force
261 ms.signature = tc.code
262 ar := fosite.NewAuthorizeRequest()
263 ar.Form.Add("code_challenge", tc.challenge)
264 ar.Form.Add("code_challenge_method", tc.method)
265 require.NoError(t, s.CreatePKCERequestSession(nil, fmt.Sprintf("valid-code-%d", k), ar))
266
267 r := fosite.NewAccessRequest(nil)
268 r.Client = tc.client
269 r.GrantTypes = fosite.Arguments{tc.grant}
270 r.Form.Add("code_verifier", tc.verifier)
271 if tc.expectErr == nil {
272 require.NoError(t, h.HandleTokenEndpointRequest(context.Background(), r))
273 } else {
274 err := h.HandleTokenEndpointRequest(context.Background(), r)
275 require.EqualError(t, err, tc.expectErr.Error(), "%+v", err)
276 }
277 })
278 }
279 }
280
281 func TestPKCEHandleTokenEndpointRequest(t *testing.T) {
282 for k, tc := range []struct {
283 d string
284 force bool
285 forcePublic bool
286 enablePlain bool
287 challenge string
288 method string
289 expectErr bool
290 client *fosite.DefaultClient
291 }{
292 {
293 d: "should pass because pkce is not enforced",
294 },
295 {
296 d: "should fail because plain is not enabled and method is empty which defaults to plain",
297 expectErr: true,
298 force: true,
299 },
300 {
301 d: "should fail because force is enabled and no challenge was given",
302 force: true,
303 enablePlain: true,
304 expectErr: true,
305 method: "S256",
306 },
307 {
308 d: "should fail because forcePublic is enabled, the client is public, and no challenge was given",
309 forcePublic: true,
310 client: &fosite.DefaultClient{Public: true},
311 expectErr: true,
312 method: "S256",
313 },
314 {
315 d: "should fail because although force is enabled and a challenge was given, plain is disabled",
316 force: true,
317 expectErr: true,
318 method: "plain",
319 challenge: "challenge",
320 },
321 {
322 d: "should fail because although force is enabled and a challenge was given, plain is disabled and method is empty",
323 force: true,
324 expectErr: true,
325 challenge: "challenge",
326 },
327 {
328 d: "should fail because invalid challenge method",
329 force: true,
330 expectErr: true,
331 method: "invalid",
332 challenge: "challenge",
333 },
334 {
335 d: "should pass because force is enabled with challenge given and method is S256",
336 force: true,
337 method: "S256",
338 challenge: "challenge",
339 },
340 {
341 d: "should pass because forcePublic is enabled with challenge given and method is S256",
342 forcePublic: true,
343 client: &fosite.DefaultClient{Public: true},
344 method: "S256",
345 challenge: "challenge",
346 },
347 } {
348 t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
349 h := &Handler{
350 Force: tc.force,
351 ForceForPublicClients: tc.forcePublic,
352 EnablePlainChallengeMethod: tc.enablePlain,
353 }
354
355 if tc.expectErr {
356 assert.Error(t, h.validate(tc.challenge, tc.method, tc.client))
357 } else {
358 assert.NoError(t, h.validate(tc.challenge, tc.method, tc.client))
359 }
360 })
361 }
362 }
363
View as plain text