auth
2014-11-11
Parent:41d8f972720c
auth/http_test.go
Remove extraneous TODOs. One TODO shouldn't actually be done. Another is already done. Remove both.
1 package auth
3 import (
4 "bytes"
5 "html/template"
6 "io/ioutil"
7 "net/http"
8 "net/http/httptest"
9 "net/url"
10 "testing"
11 "time"
13 "code.secondbit.org/uuid"
14 )
16 const (
17 scopeSet = 1 << iota
18 stateSet
19 uriSet
20 )
22 func stripParam(param string, u *url.URL) {
23 q := u.Query()
24 q.Del(param)
25 u.RawQuery = q.Encode()
26 }
28 func TestGetGrantCodeSuccess(t *testing.T) {
29 t.Parallel()
30 store := NewMemstore()
31 testContext := Context{
32 template: template.Must(template.New(getGrantTemplateName).Parse("Get auth grant")),
33 clients: store,
34 grants: store,
35 profiles: store,
36 tokens: store,
37 }
38 client := Client{
39 ID: uuid.NewID(),
40 Secret: "super secret!",
41 OwnerID: uuid.NewID(),
42 Name: "My test client",
43 Logo: "https://secondbit.org/logo.png",
44 Website: "https://secondbit.org",
45 Type: "public",
46 }
47 uri, err := url.Parse("https://test.secondbit.org/redirect")
48 if err != nil {
49 t.Fatal("Can't parse URL:", err)
50 }
51 endpoint := Endpoint{
52 ID: uuid.NewID(),
53 ClientID: client.ID,
54 URI: *uri,
55 Added: time.Now(),
56 }
57 err = testContext.SaveClient(client)
58 if err != nil {
59 t.Fatal("Can't store client:", err)
60 }
61 err = testContext.AddEndpoint(client.ID, endpoint)
62 if err != nil {
63 t.Fatal("Can't store endpoint:", err)
64 }
65 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
66 if err != nil {
67 t.Fatal("Can't build request:", err)
68 }
69 for i := 0; i < 1<<3; i++ {
70 w := httptest.NewRecorder()
71 params := url.Values{}
72 // see OAuth 2.0 spec, section 4.1.1
73 params.Set("response_type", "code")
74 params.Set("client_id", client.ID.String())
75 if i&uriSet != 0 {
76 params.Set("redirect_uri", endpoint.URI.String())
77 }
78 if i&scopeSet != 0 {
79 params.Set("scope", "testscope")
80 }
81 if i&stateSet != 0 {
82 params.Set("state", "my super secure state string")
83 }
84 req.URL.RawQuery = params.Encode()
85 req.Method = "GET"
86 req.Body = nil
87 req.Header.Del("Content-Type")
88 GetGrantHandler(w, req, testContext)
89 if w.Code != http.StatusOK {
90 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String())
91 }
92 if w.Body.String() != "Get auth grant" {
93 t.Errorf("Expected body to be `%s`, got `%s` for %s", "Get auth grant", w.Body.String(), req.URL.String())
94 }
95 req.Method = "POST"
96 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
97 w = httptest.NewRecorder()
98 data := url.Values{}
99 data.Set("grant", "approved")
100 body := bytes.NewBufferString(data.Encode())
101 req.Body = ioutil.NopCloser(body)
102 GetGrantHandler(w, req, testContext)
103 if w.Code != http.StatusFound {
104 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusFound, w.Code, req.URL.String())
105 }
106 redirectedTo := w.Header().Get("Location")
107 red, err := url.Parse(redirectedTo)
108 if err != nil {
109 t.Fatalf(`Being redirected to a non-URL "%s" threw error "%s" for "%s"\n`, redirectedTo, err, req.URL.String())
110 }
111 t.Log("Redirected to", redirectedTo)
112 if red.Query().Get("code") == "" {
113 t.Fatalf(`Expected code param in redirect URL to be set, but it wasn't for %s`, req.URL.String())
114 }
115 if _, err := testContext.GetGrant(red.Query().Get("code")); err != nil {
116 t.Fatalf(`Unexpected error "%s: retrieving the grant "%s" supplied in the redirect URL for %s`, err, red.Query().Get("code"), req.URL.String())
117 }
118 err = testContext.DeleteGrant(red.Query().Get("code"))
119 if err != nil {
120 t.Log(`Unexpected error "%s" deleting grant "%s" for %s`, err, red.Query().Get("code"), req.URL.String())
121 }
122 stripParam("code", red)
123 if red.Query().Get("state") != params.Get("state") {
124 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s" for %s`, params.Get("state"), red.Query().Get("state"), req.URL.String())
125 }
126 stripParam("state", red)
127 if red.String() != endpoint.URI.String() {
128 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
129 }
130 }
131 }
133 func TestGetGrantCodeInvalidClient(t *testing.T) {
134 t.Parallel()
135 store := NewMemstore()
136 testContext := Context{
137 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
138 clients: store,
139 grants: store,
140 profiles: store,
141 tokens: store,
142 }
143 client := Client{
144 ID: uuid.NewID(),
145 Secret: "super secret!",
146 OwnerID: uuid.NewID(),
147 Name: "My test client",
148 Type: "public",
149 }
150 err := testContext.SaveClient(client)
151 if err != nil {
152 t.Fatal("Can't store client:", err)
153 }
154 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
155 if err != nil {
156 t.Fatal("Can't build request:", err)
157 }
158 w := httptest.NewRecorder()
159 params := url.Values{}
160 params.Set("response_type", "code")
161 params.Set("redirect_uri", "https://test.secondbit.org/")
162 req.URL.RawQuery = params.Encode()
163 GetGrantHandler(w, req, testContext)
164 if w.Code != http.StatusBadRequest {
165 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
166 }
167 if w.Body.String() != "Client ID must be specified in the request." {
168 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "Client ID must be specified in the request.", w.Body.String())
169 }
170 w = httptest.NewRecorder()
171 params.Set("client_id", "Not an ID")
172 req.URL.RawQuery = params.Encode()
173 GetGrantHandler(w, req, testContext)
174 if w.Code != http.StatusBadRequest {
175 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
176 }
177 if w.Body.String() != "client_id is not a valid Client ID." {
178 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "client_id is not a valid Client ID.", w.Body.String())
179 }
180 w = httptest.NewRecorder()
181 params.Set("client_id", uuid.NewID().String())
182 req.URL.RawQuery = params.Encode()
183 GetGrantHandler(w, req, testContext)
184 if w.Code != http.StatusBadRequest {
185 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
186 }
187 if w.Body.String() != "The specified Client couldn’t be found." {
188 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The specified Client couldn’t be found.", w.Body.String())
189 }
190 }
192 func TestGetGrantCodeInvalidURI(t *testing.T) {
193 t.Parallel()
194 store := NewMemstore()
195 testContext := Context{
196 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
197 clients: store,
198 grants: store,
199 profiles: store,
200 tokens: store,
201 }
202 client := Client{
203 ID: uuid.NewID(),
204 Secret: "super secret!",
205 OwnerID: uuid.NewID(),
206 Name: "My test client",
207 Type: "public",
208 }
209 uri, err := url.Parse("https://test.secondbit.org/redirect")
210 if err != nil {
211 t.Fatal("Can't parse URL:", err)
212 }
213 err = testContext.SaveClient(client)
214 if err != nil {
215 t.Fatal("Can't store client:", err)
216 }
217 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
218 if err != nil {
219 t.Fatal("Can't build request:", err)
220 }
221 w := httptest.NewRecorder()
222 params := url.Values{}
223 params.Set("response_type", "code")
224 params.Set("client_id", client.ID.String())
225 req.URL.RawQuery = params.Encode()
226 GetGrantHandler(w, req, testContext)
227 if w.Code != http.StatusBadRequest {
228 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
229 }
230 if w.Body.String() != "The redirect_uri specified is not valid." {
231 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
232 }
233 endpoint := Endpoint{
234 ID: uuid.NewID(),
235 ClientID: client.ID,
236 URI: *uri,
237 Added: time.Now(),
238 }
239 err = testContext.AddEndpoint(client.ID, endpoint)
240 if err != nil {
241 t.Fatal("Can't store endpoint:", err)
242 }
243 w = httptest.NewRecorder()
244 params.Set("redirect_uri", "https://test.secondbit.org/wrong")
245 req.URL.RawQuery = params.Encode()
246 GetGrantHandler(w, req, testContext)
247 if w.Code != http.StatusBadRequest {
248 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
249 }
250 if w.Body.String() != "The redirect_uri specified is not valid." {
251 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
252 }
253 endpoint2 := Endpoint{
254 ID: uuid.NewID(),
255 ClientID: client.ID,
256 URI: *uri,
257 Added: time.Now(),
258 }
259 err = testContext.AddEndpoint(client.ID, endpoint2)
260 if err != nil {
261 t.Fatal("Can't store endpoint:", err)
262 }
263 w = httptest.NewRecorder()
264 params.Set("redirect_uri", "")
265 req.URL.RawQuery = params.Encode()
266 GetGrantHandler(w, req, testContext)
267 if w.Code != http.StatusBadRequest {
268 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
269 }
270 if w.Body.String() != "The redirect_uri specified is not valid." {
271 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
272 }
273 w = httptest.NewRecorder()
274 params.Set("redirect_uri", "://not a URL")
275 req.URL.RawQuery = params.Encode()
276 GetGrantHandler(w, req, testContext)
277 if w.Code != http.StatusBadRequest {
278 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
279 }
280 if w.Body.String() != "The redirect_uri specified is not valid." {
281 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
282 }
283 }
285 func TestGetGrantCodeInvalidResponseType(t *testing.T) {
286 t.Parallel()
287 store := NewMemstore()
288 testContext := Context{
289 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
290 clients: store,
291 grants: store,
292 profiles: store,
293 tokens: store,
294 }
295 client := Client{
296 ID: uuid.NewID(),
297 Secret: "super secret!",
298 OwnerID: uuid.NewID(),
299 Name: "My test client",
300 Logo: "https://secondbit.org/logo.png",
301 Website: "https://secondbit.org",
302 Type: "public",
303 }
304 uri, err := url.Parse("https://test.secondbit.org/redirect")
305 if err != nil {
306 t.Fatal("Can't parse URL:", err)
307 }
308 endpoint := Endpoint{
309 ID: uuid.NewID(),
310 ClientID: client.ID,
311 URI: *uri,
312 Added: time.Now(),
313 }
314 err = testContext.SaveClient(client)
315 if err != nil {
316 t.Fatal("Can't store client:", err)
317 }
318 err = testContext.AddEndpoint(client.ID, endpoint)
319 if err != nil {
320 t.Fatal("Can't store endpoint:", err)
321 }
322 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
323 if err != nil {
324 t.Fatal("Can't build request:", err)
325 }
326 params := url.Values{}
327 params.Set("response_type", "totally not code")
328 params.Set("client_id", client.ID.String())
329 params.Set("redirect_uri", endpoint.URI.String())
330 params.Set("scope", "testscope")
331 params.Set("state", "my super secure state string")
332 req.URL.RawQuery = params.Encode()
333 w := httptest.NewRecorder()
334 GetGrantHandler(w, req, testContext)
335 if w.Code != http.StatusFound {
336 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
337 }
338 redirectedTo := w.Header().Get("Location")
339 red, err := url.Parse(redirectedTo)
340 if err != nil {
341 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
342 }
343 if red.Query().Get("error") != "invalid_request" {
344 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
345 }
346 stripParam("error", red)
347 if red.Query().Get("state") != params.Get("state") {
348 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
349 }
350 stripParam("state", red)
351 if red.String() != endpoint.URI.String() {
352 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
353 }
354 stripParam("response_type", req.URL)
355 w = httptest.NewRecorder()
356 GetGrantHandler(w, req, testContext)
357 if w.Code != http.StatusFound {
358 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
359 }
360 redirectedTo = w.Header().Get("Location")
361 red, err = url.Parse(redirectedTo)
362 if err != nil {
363 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
364 }
365 if red.Query().Get("error") != "invalid_request" {
366 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
367 }
368 stripParam("error", red)
369 if red.Query().Get("state") != params.Get("state") {
370 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
371 }
372 stripParam("state", red)
373 if red.String() != endpoint.URI.String() {
374 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
375 }
376 }
378 func TestGetGrantCodeDenied(t *testing.T) {
379 t.Parallel()
380 store := NewMemstore()
381 testContext := Context{
382 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
383 clients: store,
384 grants: store,
385 profiles: store,
386 tokens: store,
387 }
388 client := Client{
389 ID: uuid.NewID(),
390 Secret: "super secret!",
391 OwnerID: uuid.NewID(),
392 Name: "My test client",
393 Logo: "https://secondbit.org/logo.png",
394 Website: "https://secondbit.org",
395 Type: "public",
396 }
397 uri, err := url.Parse("https://test.secondbit.org/redirect")
398 if err != nil {
399 t.Fatal("Can't parse URL:", err)
400 }
401 endpoint := Endpoint{
402 ID: uuid.NewID(),
403 ClientID: client.ID,
404 URI: *uri,
405 Added: time.Now(),
406 }
407 err = testContext.SaveClient(client)
408 if err != nil {
409 t.Fatal("Can't store client:", err)
410 }
411 err = testContext.AddEndpoint(client.ID, endpoint)
412 if err != nil {
413 t.Fatal("Can't store endpoint:", err)
414 }
415 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
416 if err != nil {
417 t.Fatal("Can't build request:", err)
418 }
419 params := url.Values{}
420 params.Set("response_type", "code")
421 params.Set("client_id", client.ID.String())
422 params.Set("redirect_uri", endpoint.URI.String())
423 params.Set("scope", "testscope")
424 params.Set("state", "my super secure state string")
425 data := url.Values{}
426 data.Set("grant", "denied")
427 req.URL.RawQuery = params.Encode()
428 req.Body = ioutil.NopCloser(bytes.NewBufferString(data.Encode()))
429 req.Method = "POST"
430 w := httptest.NewRecorder()
431 GetGrantHandler(w, req, testContext)
432 if w.Code != http.StatusFound {
433 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
434 }
435 redirectedTo := w.Header().Get("Location")
436 red, err := url.Parse(redirectedTo)
437 if err != nil {
438 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
439 }
440 if red.Query().Get("error") != "access_denied" {
441 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "access_denied", red.Query().Get("error"))
442 }
443 stripParam("error", red)
444 if red.Query().Get("state") != params.Get("state") {
445 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
446 }
447 stripParam("state", red)
448 if red.String() != endpoint.URI.String() {
449 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
450 }
451 }