auth
113:5bd46746b809 Browse Files
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.go Sat Jan 10 01:52:01 2015 -0500 1.2 +++ b/client.go Sat Jan 10 04:09:46 2015 -0500 1.3 @@ -147,7 +147,7 @@ 1.4 renderJSONError(enc, "server_error") 1.5 return nil, false 1.6 } 1.7 - if client.Secret != clientSecret { 1.8 + if client.Secret != clientSecret { // it's important that any client deemed "public" is not issued a client secret. 1.9 w.WriteHeader(http.StatusUnauthorized) 1.10 if fromAuthHeader { 1.11 w.Header().Set("WWW-Authenticate", "Basic")
2.1 --- a/client_test.go Sat Jan 10 01:52:01 2015 -0500 2.2 +++ b/client_test.go Sat Jan 10 04:09:46 2015 -0500 2.3 @@ -1,9 +1,14 @@ 2.4 package auth 2.5 2.6 import ( 2.7 + "bytes" 2.8 "fmt" 2.9 + "io/ioutil" 2.10 + "net/http" 2.11 + "net/http/httptest" 2.12 "net/url" 2.13 "sort" 2.14 + "strings" 2.15 "testing" 2.16 "time" 2.17 2.18 @@ -463,3 +468,398 @@ 2.19 } 2.20 } 2.21 } 2.22 + 2.23 +func TestVerifyClient(t *testing.T) { 2.24 + t.Parallel() 2.25 + memstore := NewMemstore() 2.26 + context := Context{ 2.27 + clients: memstore, 2.28 + } 2.29 + client := Client{ 2.30 + ID: uuid.NewID(), 2.31 + Secret: "super secret!", 2.32 + OwnerID: uuid.NewID(), 2.33 + Name: "My test client", 2.34 + Logo: "https://secondbit.org/logo.png", 2.35 + Website: "https://secondbit.org/", 2.36 + Type: "confidential", 2.37 + } 2.38 + err := context.SaveClient(client) 2.39 + if err != nil { 2.40 + t.Fatal("Could not save client:", err) 2.41 + } 2.42 + publicClient := Client{ 2.43 + ID: uuid.NewID(), 2.44 + Secret: "", 2.45 + OwnerID: uuid.NewID(), 2.46 + Name: "A public client", 2.47 + Logo: "https://secondbit.org/logo.png", 2.48 + Website: "https://secondbit.org/", 2.49 + Type: "public", 2.50 + } 2.51 + err = context.SaveClient(publicClient) 2.52 + if err != nil { 2.53 + t.Fatal("Could not save client:", err) 2.54 + } 2.55 + 2.56 + // verifyClient with no auth header, no public clients 2.57 + w := httptest.NewRecorder() 2.58 + r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.59 + if err != nil { 2.60 + t.Fatal("Can't build request:", err) 2.61 + } 2.62 + resp, success := verifyClient(w, r, false, context) 2.63 + if success { 2.64 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.65 + } 2.66 + if resp != nil { 2.67 + t.Error("Expected nil client ID, got", resp) 2.68 + } 2.69 + if w.Code != http.StatusBadRequest { 2.70 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.71 + } 2.72 + expectedBody := `{"error":"unauthorized_client"}` 2.73 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.74 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.75 + } 2.76 + 2.77 + // verifyClient with no auth header, public clients, empty client_id 2.78 + w = httptest.NewRecorder() 2.79 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.80 + if err != nil { 2.81 + t.Fatal("Can't build request:", err) 2.82 + } 2.83 + resp, success = verifyClient(w, r, true, context) 2.84 + if success { 2.85 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.86 + } 2.87 + if resp != nil { 2.88 + t.Error("Expected nil client ID, got", resp) 2.89 + } 2.90 + if w.Code != http.StatusUnauthorized { 2.91 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.92 + } 2.93 + expectedBody = `{"error":"invalid_client"}` 2.94 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.95 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.96 + } 2.97 + 2.98 + // verifyClient with auth header, no public clients, empty client id 2.99 + w = httptest.NewRecorder() 2.100 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.101 + if err != nil { 2.102 + t.Fatal("Can't build request:", err) 2.103 + } 2.104 + r.SetBasicAuth("", "no client ID set") 2.105 + resp, success = verifyClient(w, r, false, context) 2.106 + if success { 2.107 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.108 + } 2.109 + if resp != nil { 2.110 + t.Error("Expected nil client ID, got", resp) 2.111 + } 2.112 + if w.Code != http.StatusUnauthorized { 2.113 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.114 + } 2.115 + expectedBody = `{"error":"invalid_client"}` 2.116 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.117 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.118 + } 2.119 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.120 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.121 + } 2.122 + 2.123 + // verifyClient with auth header, public clients, empty client id 2.124 + w = httptest.NewRecorder() 2.125 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.126 + if err != nil { 2.127 + t.Fatal("Can't build request:", err) 2.128 + } 2.129 + r.SetBasicAuth("", "no client ID set") 2.130 + resp, success = verifyClient(w, r, true, context) 2.131 + if success { 2.132 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.133 + } 2.134 + if resp != nil { 2.135 + t.Error("Expected nil client ID, got", resp) 2.136 + } 2.137 + if w.Code != http.StatusUnauthorized { 2.138 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.139 + } 2.140 + expectedBody = `{"error":"invalid_client"}` 2.141 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.142 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.143 + } 2.144 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.145 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.146 + } 2.147 + 2.148 + // verifyClient with auth header, no public clients, invalid client ID 2.149 + w = httptest.NewRecorder() 2.150 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.151 + if err != nil { 2.152 + t.Fatal("Can't build request:", err) 2.153 + } 2.154 + r.SetBasicAuth("not an actual id", "invalid client ID set") 2.155 + resp, success = verifyClient(w, r, false, context) 2.156 + if success { 2.157 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.158 + } 2.159 + if resp != nil { 2.160 + t.Error("Expected nil client ID, got", resp) 2.161 + } 2.162 + if w.Code != http.StatusUnauthorized { 2.163 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.164 + } 2.165 + expectedBody = `{"error":"invalid_client"}` 2.166 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.167 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.168 + } 2.169 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.170 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.171 + } 2.172 + 2.173 + // verifyClient with auth header, public clients, invalid client ID 2.174 + w = httptest.NewRecorder() 2.175 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.176 + if err != nil { 2.177 + t.Fatal("Can't build request:", err) 2.178 + } 2.179 + r.SetBasicAuth("not an actual id", "invalid client ID set") 2.180 + resp, success = verifyClient(w, r, true, context) 2.181 + if success { 2.182 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.183 + } 2.184 + if resp != nil { 2.185 + t.Error("Expected nil client ID, got", resp) 2.186 + } 2.187 + if w.Code != http.StatusUnauthorized { 2.188 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.189 + } 2.190 + expectedBody = `{"error":"invalid_client"}` 2.191 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.192 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.193 + } 2.194 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.195 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.196 + } 2.197 + 2.198 + // verifyClient with auth header, no public clients, client ID valid but not in clientStore 2.199 + w = httptest.NewRecorder() 2.200 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.201 + if err != nil { 2.202 + t.Fatal("Can't build request:", err) 2.203 + } 2.204 + r.SetBasicAuth(uuid.NewID().String(), "non existent client ID set") 2.205 + resp, success = verifyClient(w, r, false, context) 2.206 + if success { 2.207 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.208 + } 2.209 + if resp != nil { 2.210 + t.Error("Expected nil client ID, got", resp) 2.211 + } 2.212 + if w.Code != http.StatusUnauthorized { 2.213 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.214 + } 2.215 + expectedBody = `{"error":"invalid_client"}` 2.216 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.217 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.218 + } 2.219 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.220 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.221 + } 2.222 + 2.223 + // verifyClient with auth header, public clients, client ID valid but not in clientStore 2.224 + w = httptest.NewRecorder() 2.225 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.226 + if err != nil { 2.227 + t.Fatal("Can't build request:", err) 2.228 + } 2.229 + r.SetBasicAuth(uuid.NewID().String(), "non existent client ID set") 2.230 + resp, success = verifyClient(w, r, true, context) 2.231 + if success { 2.232 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.233 + } 2.234 + if resp != nil { 2.235 + t.Error("Expected nil client ID, got", resp) 2.236 + } 2.237 + if w.Code != http.StatusUnauthorized { 2.238 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.239 + } 2.240 + expectedBody = `{"error":"invalid_client"}` 2.241 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.242 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.243 + } 2.244 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.245 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.246 + } 2.247 + 2.248 + // verifyClient with auth header, no public clients, client secret wrong 2.249 + w = httptest.NewRecorder() 2.250 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.251 + if err != nil { 2.252 + t.Fatal("Can't build request:", err) 2.253 + } 2.254 + r.SetBasicAuth(client.ID.String(), "not actually the secret") 2.255 + resp, success = verifyClient(w, r, false, context) 2.256 + if success { 2.257 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.258 + } 2.259 + if resp != nil { 2.260 + t.Error("Expected nil client ID, got", resp) 2.261 + } 2.262 + if w.Code != http.StatusUnauthorized { 2.263 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.264 + } 2.265 + expectedBody = `{"error":"invalid_client"}` 2.266 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.267 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.268 + } 2.269 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.270 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.271 + } 2.272 + 2.273 + // verifyClient with auth header, public clients, client secret wrong 2.274 + w = httptest.NewRecorder() 2.275 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.276 + if err != nil { 2.277 + t.Fatal("Can't build request:", err) 2.278 + } 2.279 + r.SetBasicAuth(client.ID.String(), "not actually the secret") 2.280 + resp, success = verifyClient(w, r, true, context) 2.281 + if success { 2.282 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.283 + } 2.284 + if resp != nil { 2.285 + t.Error("Expected nil client ID, got", resp) 2.286 + } 2.287 + if w.Code != http.StatusUnauthorized { 2.288 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.289 + } 2.290 + expectedBody = `{"error":"invalid_client"}` 2.291 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.292 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.293 + } 2.294 + if w.Header().Get("WWW-Authenticate") != "Basic" { 2.295 + t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) 2.296 + } 2.297 + 2.298 + // verifyClient with no auth header, public clients, invalid client_id post form value 2.299 + w = httptest.NewRecorder() 2.300 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.301 + if err != nil { 2.302 + t.Fatal("Can't build request:", err) 2.303 + } 2.304 + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 2.305 + params := url.Values{} 2.306 + params.Set("client_id", "not an actual id") 2.307 + body := bytes.NewBufferString(params.Encode()) 2.308 + r.Body = ioutil.NopCloser(body) 2.309 + resp, success = verifyClient(w, r, true, context) 2.310 + if success { 2.311 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.312 + } 2.313 + if resp != nil { 2.314 + t.Error("Expected nil client ID, got", resp) 2.315 + } 2.316 + if w.Code != http.StatusUnauthorized { 2.317 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.318 + } 2.319 + expectedBody = `{"error":"invalid_client"}` 2.320 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.321 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.322 + } 2.323 + 2.324 + // verifyClient with no auth header, public clients, client_id valid but not in clientStore 2.325 + w = httptest.NewRecorder() 2.326 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.327 + if err != nil { 2.328 + t.Fatal("Can't build request:", err) 2.329 + } 2.330 + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 2.331 + params = url.Values{} 2.332 + params.Set("client_id", uuid.NewID().String()) 2.333 + body = bytes.NewBufferString(params.Encode()) 2.334 + r.Body = ioutil.NopCloser(body) 2.335 + resp, success = verifyClient(w, r, true, context) 2.336 + if success { 2.337 + t.Error("Expected verification to fail, but succeeded with client ID:", resp) 2.338 + } 2.339 + if resp != nil { 2.340 + t.Error("Expected nil client ID, got", resp) 2.341 + } 2.342 + if w.Code != http.StatusUnauthorized { 2.343 + t.Errorf("Expected status code of %d, got %d", http.StatusBadRequest, w.Code) 2.344 + } 2.345 + expectedBody = `{"error":"invalid_client"}` 2.346 + if expectedBody != strings.TrimSpace(w.Body.String()) { 2.347 + t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) 2.348 + } 2.349 + 2.350 + // verifyClient with auth header, public clients, valid client_id and secret 2.351 + w = httptest.NewRecorder() 2.352 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.353 + if err != nil { 2.354 + t.Fatal("Can't build request:", err) 2.355 + } 2.356 + r.SetBasicAuth(client.ID.String(), client.Secret) 2.357 + resp, success = verifyClient(w, r, true, context) 2.358 + if !success { 2.359 + t.Error("Expected verification to succeed, but it failed") 2.360 + } 2.361 + if !client.ID.Equal(resp) { 2.362 + t.Errorf("Expected client ID to be %s, got %s", client.ID, resp) 2.363 + } 2.364 + if w.Code != http.StatusOK { 2.365 + t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code) 2.366 + } 2.367 + if w.Body.String() != "" { 2.368 + t.Errorf(`Expected empty body, got "%s"`, w.Body.String()) 2.369 + } 2.370 + 2.371 + // verifyClient with auth header, no public clients, valid client_id and secret 2.372 + w = httptest.NewRecorder() 2.373 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.374 + if err != nil { 2.375 + t.Fatal("Can't build request:", err) 2.376 + } 2.377 + r.SetBasicAuth(client.ID.String(), client.Secret) 2.378 + resp, success = verifyClient(w, r, true, context) 2.379 + if !success { 2.380 + t.Error("Expected verification to succeed, but it failed") 2.381 + } 2.382 + if !client.ID.Equal(resp) { 2.383 + t.Errorf("Expected client ID to be %s, got %s", client.ID, resp) 2.384 + } 2.385 + if w.Code != http.StatusOK { 2.386 + t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code) 2.387 + } 2.388 + if w.Body.String() != "" { 2.389 + t.Errorf(`Expected empty body, got "%s"`, w.Body.String()) 2.390 + } 2.391 + 2.392 + // verifyClient with no auth header, public clients, valid client_id and secret 2.393 + w = httptest.NewRecorder() 2.394 + r, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) 2.395 + if err != nil { 2.396 + t.Fatal("Can't build request:", err) 2.397 + } 2.398 + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 2.399 + params = url.Values{} 2.400 + params.Set("client_id", publicClient.ID.String()) 2.401 + body = bytes.NewBufferString(params.Encode()) 2.402 + r.Body = ioutil.NopCloser(body) 2.403 + resp, success = verifyClient(w, r, true, context) 2.404 + if !success { 2.405 + t.Error("Expected verification to succeed, but it failed") 2.406 + } 2.407 + if !publicClient.ID.Equal(resp) { 2.408 + t.Errorf("Expected client ID to be %s, got %s", publicClient.ID, resp) 2.409 + } 2.410 + if w.Code != http.StatusOK { 2.411 + t.Errorf("Expected status code of %d, got %d", http.StatusOK, w.Code) 2.412 + } 2.413 + if w.Body.String() != "" { 2.414 + t.Errorf(`Expected empty body, got "%s"`, w.Body.String()) 2.415 + } 2.416 +}