auth
auth/client_test.go
Let's test our verifyClient function. C'mon, it'll be fun! Add a function that tests the verifyClient function to our unit test suite. Basically, make sure that all the conceivable types of input have the right logic flow for what a "valid client" is. Also leave a note in client.go that makes it clear that public clients _should not be issued secrets in the first place_, because a public client that is issued a secret and specifies its client ID using the `client_id` POST body format will be told that it is not a valid client. While there are ways around this, the spec clearly states that non-confidential clients are not supposed to be issued secrets, so this seems like a nice way to conform to the spec or break trying.
1.1 --- a/client_test.go Sat Jan 10 01:52:01 2015 -0500 1.2 +++ b/client_test.go Sat Jan 10 04:09:46 2015 -0500 1.3 @@ -1,9 +1,14 @@ 1.4 package auth 1.5 1.6 import ( 1.7 + "bytes" 1.8 "fmt" 1.9 + "io/ioutil" 1.10 + "net/http" 1.11 + "net/http/httptest" 1.12 "net/url" 1.13 "sort" 1.14 + "strings" 1.15 "testing" 1.16 "time" 1.17 1.18 @@ -463,3 +468,398 @@ 1.19 } 1.20 } 1.21 } 1.22 + 1.23 +func TestVerifyClient(t *testing.T) { 1.24 + t.Parallel() 1.25 + memstore := NewMemstore() 1.26 + context := Context{ 1.27 + clients: memstore, 1.28 + } 1.29 + client := Client{ 1.30 + ID: uuid.NewID(), 1.31 + Secret: "super secret!", 1.32 + OwnerID: uuid.NewID(), 1.33 + Name: "My test client", 1.34 + Logo: "https://secondbit.org/logo.png", 1.35 + Website: "https://secondbit.org/", 1.36 + Type: "confidential", 1.37 + } 1.38 + err := context.SaveClient(client) 1.39 + if err != nil { 1.40 + t.Fatal("Could not save client:", err) 1.41 + } 1.42 + publicClient := Client{ 1.43 + ID: uuid.NewID(), 1.44 + Secret: "", 1.45 + OwnerID: uuid.NewID(), 1.46 + Name: "A public client", 1.47 + Logo: "https://secondbit.org/logo.png", 1.48 + Website: "https://secondbit.org/", 1.49 + Type: "public", 1.50 + } 1.51 + err = context.SaveClient(publicClient) 1.52 + if err != nil { 1.53 + t.Fatal("Could not save client:", err) 1.54 + } 1.55 + 1.56 + // verifyClient with no auth header, no public clients 1.57 + w := httptest.NewRecorder() 1.58 + r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.59 + if err != nil { 1.60 + t.Fatal("Can't build request:", err) 1.61 + } 1.62 + resp, success := verifyClient(w, r, false, context) 1.63 + if success { 1.64 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.65 + } 1.66 + if resp != nil { 1.67 + t.Error("Expected nil client ID, got", resp) 1.68 + } 1.69 + if w.Code != http.StatusBadRequest { 1.70 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.71 + } 1.72 + expectedBody := `{"error":"unauthorized_client"}` 1.73 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.74 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.75 + } 1.76 + 1.77 + // verifyClient with no auth header, public clients, empty client_id 1.78 + w = httptest.NewRecorder() 1.79 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.80 + if err != nil { 1.81 + t.Fatal("Can't build request:", err) 1.82 + } 1.83 + resp, success = verifyClient(w, r, true, context) 1.84 + if success { 1.85 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.86 + } 1.87 + if resp != nil { 1.88 + t.Error("Expected nil client ID, got", resp) 1.89 + } 1.90 + if w.Code != http.StatusUnauthorized { 1.91 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.92 + } 1.93 + expectedBody = `{"error":"invalid_client"}` 1.94 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.95 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.96 + } 1.97 + 1.98 + // verifyClient with auth header, no public clients, empty client id 1.99 + w = httptest.NewRecorder() 1.100 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.101 + if err != nil { 1.102 + t.Fatal("Can't build request:", err) 1.103 + } 1.104 + r.SetBasicAuth("", "no client ID set") 1.105 + resp, success = verifyClient(w, r, false, context) 1.106 + if success { 1.107 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.108 + } 1.109 + if resp != nil { 1.110 + t.Error("Expected nil client ID, got", resp) 1.111 + } 1.112 + if w.Code != http.StatusUnauthorized { 1.113 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.114 + } 1.115 + expectedBody = `{"error":"invalid_client"}` 1.116 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.117 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.118 + } 1.119 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.120 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.121 + } 1.122 + 1.123 + // verifyClient with auth header, public clients, empty client id 1.124 + w = httptest.NewRecorder() 1.125 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.126 + if err != nil { 1.127 + t.Fatal("Can't build request:", err) 1.128 + } 1.129 + r.SetBasicAuth("", "no client ID set") 1.130 + resp, success = verifyClient(w, r, true, context) 1.131 + if success { 1.132 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.133 + } 1.134 + if resp != nil { 1.135 + t.Error("Expected nil client ID, got", resp) 1.136 + } 1.137 + if w.Code != http.StatusUnauthorized { 1.138 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.139 + } 1.140 + expectedBody = `{"error":"invalid_client"}` 1.141 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.142 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.143 + } 1.144 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.145 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.146 + } 1.147 + 1.148 + // verifyClient with auth header, no public clients, invalid client ID 1.149 + w = httptest.NewRecorder() 1.150 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.151 + if err != nil { 1.152 + t.Fatal("Can't build request:", err) 1.153 + } 1.154 + r.SetBasicAuth("not an actual id", "invalid client ID set") 1.155 + resp, success = verifyClient(w, r, false, context) 1.156 + if success { 1.157 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.158 + } 1.159 + if resp != nil { 1.160 + t.Error("Expected nil client ID, got", resp) 1.161 + } 1.162 + if w.Code != http.StatusUnauthorized { 1.163 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.164 + } 1.165 + expectedBody = `{"error":"invalid_client"}` 1.166 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.167 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.168 + } 1.169 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.170 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.171 + } 1.172 + 1.173 + // verifyClient with auth header, public clients, invalid client ID 1.174 + w = httptest.NewRecorder() 1.175 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.176 + if err != nil { 1.177 + t.Fatal("Can't build request:", err) 1.178 + } 1.179 + r.SetBasicAuth("not an actual id", "invalid client ID set") 1.180 + resp, success = verifyClient(w, r, true, context) 1.181 + if success { 1.182 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.183 + } 1.184 + if resp != nil { 1.185 + t.Error("Expected nil client ID, got", resp) 1.186 + } 1.187 + if w.Code != http.StatusUnauthorized { 1.188 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.189 + } 1.190 + expectedBody = `{"error":"invalid_client"}` 1.191 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.192 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.193 + } 1.194 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.195 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.196 + } 1.197 + 1.198 + // verifyClient with auth header, no public clients, client ID valid but not in clientStore 1.199 + w = httptest.NewRecorder() 1.200 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.201 + if err != nil { 1.202 + t.Fatal("Can't build request:", err) 1.203 + } 1.204 + r.SetBasicAuth(uuid.NewID().String(), "non existent client ID set") 1.205 + resp, success = verifyClient(w, r, false, context) 1.206 + if success { 1.207 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.208 + } 1.209 + if resp != nil { 1.210 + t.Error("Expected nil client ID, got", resp) 1.211 + } 1.212 + if w.Code != http.StatusUnauthorized { 1.213 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.214 + } 1.215 + expectedBody = `{"error":"invalid_client"}` 1.216 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.217 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.218 + } 1.219 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.220 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.221 + } 1.222 + 1.223 + // verifyClient with auth header, public clients, client ID valid but not in clientStore 1.224 + w = httptest.NewRecorder() 1.225 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.226 + if err != nil { 1.227 + t.Fatal("Can't build request:", err) 1.228 + } 1.229 + r.SetBasicAuth(uuid.NewID().String(), "non existent client ID set") 1.230 + resp, success = verifyClient(w, r, true, context) 1.231 + if success { 1.232 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.233 + } 1.234 + if resp != nil { 1.235 + t.Error("Expected nil client ID, got", resp) 1.236 + } 1.237 + if w.Code != http.StatusUnauthorized { 1.238 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.239 + } 1.240 + expectedBody = `{"error":"invalid_client"}` 1.241 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.242 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.243 + } 1.244 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.245 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.246 + } 1.247 + 1.248 + // verifyClient with auth header, no public clients, client secret wrong 1.249 + w = httptest.NewRecorder() 1.250 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.251 + if err != nil { 1.252 + t.Fatal("Can't build request:", err) 1.253 + } 1.254 + r.SetBasicAuth(client.ID.String(), "not actually the secret") 1.255 + resp, success = verifyClient(w, r, false, context) 1.256 + if success { 1.257 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.258 + } 1.259 + if resp != nil { 1.260 + t.Error("Expected nil client ID, got", resp) 1.261 + } 1.262 + if w.Code != http.StatusUnauthorized { 1.263 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.264 + } 1.265 + expectedBody = `{"error":"invalid_client"}` 1.266 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.267 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.268 + } 1.269 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.270 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.271 + } 1.272 + 1.273 + // verifyClient with auth header, public clients, client secret wrong 1.274 + w = httptest.NewRecorder() 1.275 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.276 + if err != nil { 1.277 + t.Fatal("Can't build request:", err) 1.278 + } 1.279 + r.SetBasicAuth(client.ID.String(), "not actually the secret") 1.280 + resp, success = verifyClient(w, r, true, context) 1.281 + if success { 1.282 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.283 + } 1.284 + if resp != nil { 1.285 + t.Error("Expected nil client ID, got", resp) 1.286 + } 1.287 + if w.Code != http.StatusUnauthorized { 1.288 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.289 + } 1.290 + expectedBody = `{"error":"invalid_client"}` 1.291 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.292 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.293 + } 1.294 + if w.Header().Get("WWW-Authenticate") != "Basic" { 1.295 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 1.296 + } 1.297 + 1.298 + // verifyClient with no auth header, public clients, invalid client_id post form value 1.299 + w = httptest.NewRecorder() 1.300 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.301 + if err != nil { 1.302 + t.Fatal("Can't build request:", err) 1.303 + } 1.304 + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 1.305 + params := url.Values{} 1.306 + params.Set("client_id", "not an actual id") 1.307 + body := bytes.NewBufferString(params.Encode()) 1.308 + r.Body = ioutil.NopCloser(body) 1.309 + resp, success = verifyClient(w, r, true, context) 1.310 + if success { 1.311 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.312 + } 1.313 + if resp != nil { 1.314 + t.Error("Expected nil client ID, got", resp) 1.315 + } 1.316 + if w.Code != http.StatusUnauthorized { 1.317 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.318 + } 1.319 + expectedBody = `{"error":"invalid_client"}` 1.320 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.321 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.322 + } 1.323 + 1.324 + // verifyClient with no auth header, public clients, client_id valid but not in clientStore 1.325 + w = httptest.NewRecorder() 1.326 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.327 + if err != nil { 1.328 + t.Fatal("Can't build request:", err) 1.329 + } 1.330 + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 1.331 + params = url.Values{} 1.332 + params.Set("client_id", uuid.NewID().String()) 1.333 + body = bytes.NewBufferString(params.Encode()) 1.334 + r.Body = ioutil.NopCloser(body) 1.335 + resp, success = verifyClient(w, r, true, context) 1.336 + if success { 1.337 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 1.338 + } 1.339 + if resp != nil { 1.340 + t.Error("Expected nil client ID, got", resp) 1.341 + } 1.342 + if w.Code != http.StatusUnauthorized { 1.343 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 1.344 + } 1.345 + expectedBody = `{"error":"invalid_client"}` 1.346 + if expectedBody != strings.TrimSpace(w.Body.String()) { 1.347 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 1.348 + } 1.349 + 1.350 + // verifyClient with auth header, public clients, valid client_id and secret 1.351 + w = httptest.NewRecorder() 1.352 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.353 + if err != nil { 1.354 + t.Fatal("Can't build request:", err) 1.355 + } 1.356 + r.SetBasicAuth(client.ID.String(), client.Secret) 1.357 + resp, success = verifyClient(w, r, true, context) 1.358 + if !success { 1.359 + t.Error("Expected verification to succeed, but it failed") 1.360 + } 1.361 + if !client.ID.Equal(resp) { 1.362 + t.Errorf("Expected client ID to be %s, got %s", client.ID, resp) 1.363 + } 1.364 + if w.Code != http.StatusOK { 1.365 + t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code) 1.366 + } 1.367 + if w.Body.String() != "" { 1.368 + t.Errorf(`Expected empty body, got "%s"`, w.Body.String()) 1.369 + } 1.370 + 1.371 + // verifyClient with auth header, no public clients, valid client_id and secret 1.372 + w = httptest.NewRecorder() 1.373 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.374 + if err != nil { 1.375 + t.Fatal("Can't build request:", err) 1.376 + } 1.377 + r.SetBasicAuth(client.ID.String(), client.Secret) 1.378 + resp, success = verifyClient(w, r, true, context) 1.379 + if !success { 1.380 + t.Error("Expected verification to succeed, but it failed") 1.381 + } 1.382 + if !client.ID.Equal(resp) { 1.383 + t.Errorf("Expected client ID to be %s, got %s", client.ID, resp) 1.384 + } 1.385 + if w.Code != http.StatusOK { 1.386 + t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code) 1.387 + } 1.388 + if w.Body.String() != "" { 1.389 + t.Errorf(`Expected empty body, got "%s"`, w.Body.String()) 1.390 + } 1.391 + 1.392 + // verifyClient with no auth header, public clients, valid client_id and secret 1.393 + w = httptest.NewRecorder() 1.394 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 1.395 + if err != nil { 1.396 + t.Fatal("Can't build request:", err) 1.397 + } 1.398 + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 1.399 + params = url.Values{} 1.400 + params.Set("client_id", publicClient.ID.String()) 1.401 + body = bytes.NewBufferString(params.Encode()) 1.402 + r.Body = ioutil.NopCloser(body) 1.403 + resp, success = verifyClient(w, r, true, context) 1.404 + if !success { 1.405 + t.Error("Expected verification to succeed, but it failed") 1.406 + } 1.407 + if !publicClient.ID.Equal(resp) { 1.408 + t.Errorf("Expected client ID to be %s, got %s", publicClient.ID, resp) 1.409 + } 1.410 + if w.Code != http.StatusOK { 1.411 + t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code) 1.412 + } 1.413 + if w.Body.String() != "" { 1.414 + t.Errorf(`Expected empty body, got "%s"`, w.Body.String()) 1.415 + } 1.416 +}