auth
auth/http_test.go
Fix bug with response_type redirect, add tests. Test that we redirect with an error when an invalid response_type is supplied. Fix a bug that would not add any of our parameters to the redirect URL.
| paddy@52 | 1 package auth |
| paddy@52 | 2 |
| paddy@52 | 3 import ( |
| paddy@52 | 4 "html/template" |
| paddy@52 | 5 "net/http" |
| paddy@52 | 6 "net/http/httptest" |
| paddy@53 | 7 "net/url" |
| paddy@52 | 8 "testing" |
| paddy@56 | 9 "time" |
| paddy@56 | 10 |
| paddy@56 | 11 "code.secondbit.org/uuid" |
| paddy@52 | 12 ) |
| paddy@52 | 13 |
| paddy@53 | 14 const ( |
| paddy@53 | 15 scopeSet = 1 << iota |
| paddy@53 | 16 stateSet |
| paddy@53 | 17 uriSet |
| paddy@53 | 18 ) |
| paddy@53 | 19 |
| paddy@65 | 20 func stripParam(param string, u *url.URL) { |
| paddy@65 | 21 q := u.Query() |
| paddy@65 | 22 q.Del(param) |
| paddy@65 | 23 u.RawQuery = q.Encode() |
| paddy@65 | 24 } |
| paddy@65 | 25 |
| paddy@52 | 26 func TestGetGrantCodeSuccess(t *testing.T) { |
| paddy@52 | 27 t.Parallel() |
| paddy@52 | 28 store := NewMemstore() |
| paddy@52 | 29 testContext := Context{ |
| paddy@52 | 30 template: template.Must(template.New(getGrantTemplateName).Parse("Get auth grant")), |
| paddy@52 | 31 clients: store, |
| paddy@52 | 32 grants: store, |
| paddy@52 | 33 profiles: store, |
| paddy@52 | 34 tokens: store, |
| paddy@52 | 35 } |
| paddy@56 | 36 client := Client{ |
| paddy@56 | 37 ID: uuid.NewID(), |
| paddy@56 | 38 Secret: "super secret!", |
| paddy@56 | 39 OwnerID: uuid.NewID(), |
| paddy@56 | 40 Name: "My test client", |
| paddy@56 | 41 Logo: "https://secondbit.org/logo.png", |
| paddy@56 | 42 Website: "https://secondbit.org", |
| paddy@56 | 43 Type: "public", |
| paddy@56 | 44 } |
| paddy@56 | 45 uri, err := url.Parse("https://test.secondbit.org/redirect") |
| paddy@56 | 46 if err != nil { |
| paddy@56 | 47 t.Fatal("Can't parse URL:", err) |
| paddy@56 | 48 } |
| paddy@56 | 49 endpoint := Endpoint{ |
| paddy@56 | 50 ID: uuid.NewID(), |
| paddy@56 | 51 ClientID: client.ID, |
| paddy@56 | 52 URI: *uri, |
| paddy@56 | 53 Added: time.Now(), |
| paddy@56 | 54 } |
| paddy@56 | 55 err = testContext.SaveClient(client) |
| paddy@56 | 56 if err != nil { |
| paddy@56 | 57 t.Fatal("Can't store client:", err) |
| paddy@56 | 58 } |
| paddy@56 | 59 err = testContext.AddEndpoint(client.ID, endpoint) |
| paddy@56 | 60 if err != nil { |
| paddy@56 | 61 t.Fatal("Can't store endpoint:", err) |
| paddy@56 | 62 } |
| paddy@52 | 63 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@52 | 64 if err != nil { |
| paddy@52 | 65 t.Fatal("Can't build request:", err) |
| paddy@52 | 66 } |
| paddy@58 | 67 for i := 0; i < 1<<3; i++ { |
| paddy@53 | 68 w := httptest.NewRecorder() |
| paddy@53 | 69 params := url.Values{} |
| paddy@53 | 70 // see OAuth 2.0 spec, section 4.1.1 |
| paddy@53 | 71 params.Set("response_type", "code") |
| paddy@56 | 72 params.Set("client_id", client.ID.String()) |
| paddy@53 | 73 if i&uriSet != 0 { |
| paddy@58 | 74 params.Set("redirect_uri", endpoint.URI.String()) |
| paddy@53 | 75 } |
| paddy@53 | 76 if i&scopeSet != 0 { |
| paddy@53 | 77 params.Set("scope", "testscope") |
| paddy@53 | 78 } |
| paddy@53 | 79 if i&stateSet != 0 { |
| paddy@53 | 80 params.Set("state", "my super secure state string") |
| paddy@53 | 81 } |
| paddy@53 | 82 req.URL.RawQuery = params.Encode() |
| paddy@53 | 83 GetGrantHandler(w, req, testContext) |
| paddy@53 | 84 if w.Code != http.StatusOK { |
| paddy@53 | 85 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String()) |
| paddy@53 | 86 } |
| paddy@53 | 87 if w.Body.String() != "Get auth grant" { |
| paddy@53 | 88 t.Errorf("Expected body to be `%s`, got `%s` for %s", "Get auth grant", w.Body.String(), req.URL.String()) |
| paddy@53 | 89 } |
| paddy@52 | 90 } |
| paddy@52 | 91 } |
| paddy@56 | 92 |
| paddy@62 | 93 func TestGetGrantCodeInvalidClient(t *testing.T) { |
| paddy@62 | 94 t.Parallel() |
| paddy@62 | 95 store := NewMemstore() |
| paddy@62 | 96 testContext := Context{ |
| paddy@62 | 97 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")), |
| paddy@62 | 98 clients: store, |
| paddy@62 | 99 grants: store, |
| paddy@62 | 100 profiles: store, |
| paddy@62 | 101 tokens: store, |
| paddy@62 | 102 } |
| paddy@62 | 103 client := Client{ |
| paddy@62 | 104 ID: uuid.NewID(), |
| paddy@62 | 105 Secret: "super secret!", |
| paddy@62 | 106 OwnerID: uuid.NewID(), |
| paddy@62 | 107 Name: "My test client", |
| paddy@62 | 108 Type: "public", |
| paddy@62 | 109 } |
| paddy@62 | 110 err := testContext.SaveClient(client) |
| paddy@62 | 111 if err != nil { |
| paddy@62 | 112 t.Fatal("Can't store client:", err) |
| paddy@62 | 113 } |
| paddy@62 | 114 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@62 | 115 if err != nil { |
| paddy@62 | 116 t.Fatal("Can't build request:", err) |
| paddy@62 | 117 } |
| paddy@62 | 118 w := httptest.NewRecorder() |
| paddy@62 | 119 params := url.Values{} |
| paddy@62 | 120 params.Set("response_type", "code") |
| paddy@62 | 121 params.Set("redirect_uri", "https://test.secondbit.org/") |
| paddy@62 | 122 req.URL.RawQuery = params.Encode() |
| paddy@62 | 123 GetGrantHandler(w, req, testContext) |
| paddy@62 | 124 if w.Code != http.StatusBadRequest { |
| paddy@62 | 125 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@62 | 126 } |
| paddy@62 | 127 if w.Body.String() != "Client ID must be specified in the request." { |
| paddy@62 | 128 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "Client ID must be specified in the request.", w.Body.String()) |
| paddy@62 | 129 } |
| paddy@62 | 130 w = httptest.NewRecorder() |
| paddy@62 | 131 params.Set("client_id", "Not an ID") |
| paddy@62 | 132 req.URL.RawQuery = params.Encode() |
| paddy@62 | 133 GetGrantHandler(w, req, testContext) |
| paddy@62 | 134 if w.Code != http.StatusBadRequest { |
| paddy@62 | 135 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@62 | 136 } |
| paddy@62 | 137 if w.Body.String() != "client_id is not a valid Client ID." { |
| paddy@62 | 138 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "client_id is not a valid Client ID.", w.Body.String()) |
| paddy@62 | 139 } |
| paddy@62 | 140 w = httptest.NewRecorder() |
| paddy@62 | 141 params.Set("client_id", uuid.NewID().String()) |
| paddy@62 | 142 req.URL.RawQuery = params.Encode() |
| paddy@62 | 143 GetGrantHandler(w, req, testContext) |
| paddy@62 | 144 if w.Code != http.StatusBadRequest { |
| paddy@62 | 145 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@62 | 146 } |
| paddy@62 | 147 if w.Body.String() != "The specified Client couldn’t be found." { |
| paddy@62 | 148 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The specified Client couldn’t be found.", w.Body.String()) |
| paddy@62 | 149 } |
| paddy@62 | 150 } |
| paddy@62 | 151 |
| paddy@56 | 152 func TestGetGrantCodeInvalidURI(t *testing.T) { |
| paddy@56 | 153 t.Parallel() |
| paddy@56 | 154 store := NewMemstore() |
| paddy@56 | 155 testContext := Context{ |
| paddy@56 | 156 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")), |
| paddy@56 | 157 clients: store, |
| paddy@56 | 158 grants: store, |
| paddy@56 | 159 profiles: store, |
| paddy@56 | 160 tokens: store, |
| paddy@56 | 161 } |
| paddy@56 | 162 client := Client{ |
| paddy@56 | 163 ID: uuid.NewID(), |
| paddy@56 | 164 Secret: "super secret!", |
| paddy@56 | 165 OwnerID: uuid.NewID(), |
| paddy@56 | 166 Name: "My test client", |
| paddy@56 | 167 Type: "public", |
| paddy@56 | 168 } |
| paddy@56 | 169 uri, err := url.Parse("https://test.secondbit.org/redirect") |
| paddy@56 | 170 if err != nil { |
| paddy@56 | 171 t.Fatal("Can't parse URL:", err) |
| paddy@56 | 172 } |
| paddy@56 | 173 err = testContext.SaveClient(client) |
| paddy@56 | 174 if err != nil { |
| paddy@56 | 175 t.Fatal("Can't store client:", err) |
| paddy@56 | 176 } |
| paddy@56 | 177 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@56 | 178 if err != nil { |
| paddy@56 | 179 t.Fatal("Can't build request:", err) |
| paddy@56 | 180 } |
| paddy@56 | 181 w := httptest.NewRecorder() |
| paddy@56 | 182 params := url.Values{} |
| paddy@56 | 183 params.Set("response_type", "code") |
| paddy@56 | 184 params.Set("client_id", client.ID.String()) |
| paddy@64 | 185 req.URL.RawQuery = params.Encode() |
| paddy@64 | 186 GetGrantHandler(w, req, testContext) |
| paddy@64 | 187 if w.Code != http.StatusBadRequest { |
| paddy@64 | 188 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@64 | 189 } |
| paddy@64 | 190 if w.Body.String() != "The redirect_uri specified is not valid." { |
| paddy@64 | 191 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String()) |
| paddy@64 | 192 } |
| paddy@64 | 193 endpoint := Endpoint{ |
| paddy@64 | 194 ID: uuid.NewID(), |
| paddy@64 | 195 ClientID: client.ID, |
| paddy@64 | 196 URI: *uri, |
| paddy@64 | 197 Added: time.Now(), |
| paddy@64 | 198 } |
| paddy@64 | 199 err = testContext.AddEndpoint(client.ID, endpoint) |
| paddy@64 | 200 if err != nil { |
| paddy@64 | 201 t.Fatal("Can't store endpoint:", err) |
| paddy@64 | 202 } |
| paddy@64 | 203 w = httptest.NewRecorder() |
| paddy@56 | 204 params.Set("redirect_uri", "https://test.secondbit.org/wrong") |
| paddy@56 | 205 req.URL.RawQuery = params.Encode() |
| paddy@56 | 206 GetGrantHandler(w, req, testContext) |
| paddy@56 | 207 if w.Code != http.StatusBadRequest { |
| paddy@56 | 208 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@56 | 209 } |
| paddy@56 | 210 if w.Body.String() != "The redirect_uri specified is not valid." { |
| paddy@56 | 211 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String()) |
| paddy@56 | 212 } |
| paddy@64 | 213 endpoint2 := Endpoint{ |
| paddy@64 | 214 ID: uuid.NewID(), |
| paddy@64 | 215 ClientID: client.ID, |
| paddy@64 | 216 URI: *uri, |
| paddy@64 | 217 Added: time.Now(), |
| paddy@64 | 218 } |
| paddy@64 | 219 err = testContext.AddEndpoint(client.ID, endpoint2) |
| paddy@60 | 220 if err != nil { |
| paddy@64 | 221 t.Fatal("Can't store endpoint:", err) |
| paddy@60 | 222 } |
| paddy@60 | 223 w = httptest.NewRecorder() |
| paddy@64 | 224 params.Set("redirect_uri", "") |
| paddy@64 | 225 req.URL.RawQuery = params.Encode() |
| paddy@64 | 226 GetGrantHandler(w, req, testContext) |
| paddy@64 | 227 if w.Code != http.StatusBadRequest { |
| paddy@64 | 228 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@64 | 229 } |
| paddy@64 | 230 if w.Body.String() != "The redirect_uri specified is not valid." { |
| paddy@64 | 231 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String()) |
| paddy@64 | 232 } |
| paddy@64 | 233 w = httptest.NewRecorder() |
| paddy@64 | 234 params.Set("redirect_uri", "://not a URL") |
| paddy@60 | 235 req.URL.RawQuery = params.Encode() |
| paddy@60 | 236 GetGrantHandler(w, req, testContext) |
| paddy@60 | 237 if w.Code != http.StatusBadRequest { |
| paddy@60 | 238 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@60 | 239 } |
| paddy@60 | 240 if w.Body.String() != "The redirect_uri specified is not valid." { |
| paddy@60 | 241 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String()) |
| paddy@60 | 242 } |
| paddy@56 | 243 } |
| paddy@65 | 244 |
| paddy@65 | 245 func TestGetGrantCodeInvalidResponseType(t *testing.T) { |
| paddy@65 | 246 t.Parallel() |
| paddy@65 | 247 store := NewMemstore() |
| paddy@65 | 248 testContext := Context{ |
| paddy@65 | 249 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")), |
| paddy@65 | 250 clients: store, |
| paddy@65 | 251 grants: store, |
| paddy@65 | 252 profiles: store, |
| paddy@65 | 253 tokens: store, |
| paddy@65 | 254 } |
| paddy@65 | 255 client := Client{ |
| paddy@65 | 256 ID: uuid.NewID(), |
| paddy@65 | 257 Secret: "super secret!", |
| paddy@65 | 258 OwnerID: uuid.NewID(), |
| paddy@65 | 259 Name: "My test client", |
| paddy@65 | 260 Logo: "https://secondbit.org/logo.png", |
| paddy@65 | 261 Website: "https://secondbit.org", |
| paddy@65 | 262 Type: "public", |
| paddy@65 | 263 } |
| paddy@65 | 264 uri, err := url.Parse("https://test.secondbit.org/redirect") |
| paddy@65 | 265 if err != nil { |
| paddy@65 | 266 t.Fatal("Can't parse URL:", err) |
| paddy@65 | 267 } |
| paddy@65 | 268 endpoint := Endpoint{ |
| paddy@65 | 269 ID: uuid.NewID(), |
| paddy@65 | 270 ClientID: client.ID, |
| paddy@65 | 271 URI: *uri, |
| paddy@65 | 272 Added: time.Now(), |
| paddy@65 | 273 } |
| paddy@65 | 274 err = testContext.SaveClient(client) |
| paddy@65 | 275 if err != nil { |
| paddy@65 | 276 t.Fatal("Can't store client:", err) |
| paddy@65 | 277 } |
| paddy@65 | 278 err = testContext.AddEndpoint(client.ID, endpoint) |
| paddy@65 | 279 if err != nil { |
| paddy@65 | 280 t.Fatal("Can't store endpoint:", err) |
| paddy@65 | 281 } |
| paddy@65 | 282 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@65 | 283 if err != nil { |
| paddy@65 | 284 t.Fatal("Can't build request:", err) |
| paddy@65 | 285 } |
| paddy@65 | 286 params := url.Values{} |
| paddy@65 | 287 params.Set("response_type", "totally not code") |
| paddy@65 | 288 params.Set("client_id", client.ID.String()) |
| paddy@65 | 289 params.Set("redirect_uri", endpoint.URI.String()) |
| paddy@65 | 290 params.Set("scope", "testscope") |
| paddy@65 | 291 params.Set("state", "my super secure state string") |
| paddy@65 | 292 req.URL.RawQuery = params.Encode() |
| paddy@65 | 293 w := httptest.NewRecorder() |
| paddy@65 | 294 GetGrantHandler(w, req, testContext) |
| paddy@65 | 295 if w.Code != http.StatusFound { |
| paddy@65 | 296 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code) |
| paddy@65 | 297 } |
| paddy@65 | 298 redirectedTo := w.Header().Get("Location") |
| paddy@65 | 299 red, err := url.Parse(redirectedTo) |
| paddy@65 | 300 if err != nil { |
| paddy@65 | 301 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err) |
| paddy@65 | 302 } |
| paddy@65 | 303 if red.Query().Get("error") != "invalid_request" { |
| paddy@65 | 304 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error")) |
| paddy@65 | 305 } |
| paddy@65 | 306 stripParam("error", red) |
| paddy@65 | 307 if red.Query().Get("state") != params.Get("state") { |
| paddy@65 | 308 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state")) |
| paddy@65 | 309 } |
| paddy@65 | 310 stripParam("state", red) |
| paddy@65 | 311 if red.String() != endpoint.URI.String() { |
| paddy@65 | 312 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String()) |
| paddy@65 | 313 } |
| paddy@65 | 314 stripParam("response_type", req.URL) |
| paddy@65 | 315 w = httptest.NewRecorder() |
| paddy@65 | 316 GetGrantHandler(w, req, testContext) |
| paddy@65 | 317 if w.Code != http.StatusFound { |
| paddy@65 | 318 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code) |
| paddy@65 | 319 } |
| paddy@65 | 320 redirectedTo = w.Header().Get("Location") |
| paddy@65 | 321 red, err = url.Parse(redirectedTo) |
| paddy@65 | 322 if err != nil { |
| paddy@65 | 323 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err) |
| paddy@65 | 324 } |
| paddy@65 | 325 if red.Query().Get("error") != "invalid_request" { |
| paddy@65 | 326 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error")) |
| paddy@65 | 327 } |
| paddy@65 | 328 stripParam("error", red) |
| paddy@65 | 329 if red.Query().Get("state") != params.Get("state") { |
| paddy@65 | 330 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state")) |
| paddy@65 | 331 } |
| paddy@65 | 332 stripParam("state", red) |
| paddy@65 | 333 if red.String() != endpoint.URI.String() { |
| paddy@65 | 334 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String()) |
| paddy@65 | 335 } |
| paddy@65 | 336 } |