auth

Paddy 2014-11-11 Parent:4ae226929e92 Child:d43c3fbf00f3

76:6fac0d6d6ca3 Go to Latest

auth/oauth2_test.go

I'm on a typo roll. ErrInvalidSession, not ErrInvalidSessior. Also, we want comparison, not assignment.

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