auth
82:0a6e3f14b054 Browse Files
Fix go vet, fix imports, render JSON errors, deprecate getBasicAuth. Fix logging functions in our test file that were causing go vet to report errors (and which would have obscured the test output). Move some imports around to make the imports more consistent and our pre-commit hook happy. Create a helper to render JSON errors, and actually render those errors when obtaining a token using a grant. Deprecate our custom getBasicAuth in favour of the new BasicAuth() method on net/http.*Request objects, which was introduced in Go 1.4 (meaning Go 1.4 is now a requirement for compiling this.)
client_test.go oauth2.go oauth2_test.go
1.1 --- a/client_test.go Sat Dec 06 00:35:03 2014 -0500 1.2 +++ b/client_test.go Sat Dec 06 01:47:34 2014 -0500 1.3 @@ -3,10 +3,10 @@ 1.4 import ( 1.5 "fmt" 1.6 "net/url" 1.7 + "sort" 1.8 "testing" 1.9 "time" 1.10 1.11 - "sort" 1.12 "code.secondbit.org/uuid" 1.13 ) 1.14
2.1 --- a/oauth2.go Sat Dec 06 00:35:03 2014 -0500 2.2 +++ b/oauth2.go Sat Dec 06 01:47:34 2014 -0500 2.3 @@ -1,7 +1,7 @@ 2.4 package auth 2.5 2.6 import ( 2.7 - "encoding/base64" 2.8 + "crypto/sha256" 2.9 "encoding/hex" 2.10 "encoding/json" 2.11 "errors" 2.12 @@ -9,13 +9,12 @@ 2.13 "log" 2.14 "net/http" 2.15 "net/url" 2.16 - "strings" 2.17 "time" 2.18 - "github.com/gorilla/mux" 2.19 2.20 - "crypto/sha256" 2.21 "code.secondbit.org/pass" 2.22 "code.secondbit.org/uuid" 2.23 + 2.24 + "github.com/gorilla/mux" 2.25 ) 2.26 2.27 const ( 2.28 @@ -44,24 +43,19 @@ 2.29 RefreshToken string `json:"refresh_token,omitempty"` 2.30 } 2.31 2.32 -func getBasicAuth(r *http.Request) (un, pass string, err error) { 2.33 - auth := r.Header.Get("Authorization") 2.34 - if auth == "" { 2.35 - return "", "", ErrNoAuth 2.36 +type errorResponse struct { 2.37 + Error string `json:"error"` 2.38 + Description string `json:"error_description,omitempty"` 2.39 + URI string `json:"error_uri,omitempty"` 2.40 +} 2.41 + 2.42 +func renderJSONError(enc *json.Encoder, errorType string) { 2.43 + err := enc.Encode(errorResponse{ 2.44 + Error: errorType, 2.45 + }) 2.46 + if err != nil { 2.47 + // TODO(paddy): log this or something 2.48 } 2.49 - pieces := strings.SplitN(auth, " ", 2) 2.50 - if pieces[0] != "Basic" { 2.51 - return "", "", ErrInvalidAuthFormat 2.52 - } 2.53 - decoded, err := base64.StdEncoding.DecodeString(pieces[1]) 2.54 - if err != nil { 2.55 - return "", "", ErrInvalidAuthFormat 2.56 - } 2.57 - info := strings.SplitN(string(decoded), ":", 2) 2.58 - if len(info) < 2 { 2.59 - return "", "", ErrInvalidAuthFormat 2.60 - } 2.61 - return info[0], info[1], nil 2.62 } 2.63 2.64 func checkCookie(r *http.Request, context Context) (Session, error) { 2.65 @@ -308,56 +302,68 @@ 2.66 enc := json.NewEncoder(w) 2.67 grantType := r.PostFormValue("grant_type") 2.68 if grantType != "authorization_code" { 2.69 - // TODO(paddy): render invalid request JSON 2.70 + w.WriteHeader(http.StatusBadRequest) 2.71 + renderJSONError(enc, "invalid_request") 2.72 return 2.73 } 2.74 code := r.PostFormValue("code") 2.75 if code == "" { 2.76 - // TODO(paddy): render invalid request JSON 2.77 + w.WriteHeader(http.StatusBadRequest) 2.78 + renderJSONError(enc, "invalid_request") 2.79 return 2.80 } 2.81 redirectURI := r.PostFormValue("redirect_uri") 2.82 - clientIDStr, clientSecret, err := getBasicAuth(r) 2.83 - if err != nil { 2.84 - // TODO(paddy): render invalid client JSON 2.85 - return 2.86 - } 2.87 - if clientIDStr == "" && err == nil { 2.88 + clientIDStr, clientSecret, fromAuthHeader := r.BasicAuth() 2.89 + if !fromAuthHeader { 2.90 clientIDStr = r.PostFormValue("client_id") 2.91 } 2.92 clientID, err := uuid.Parse(clientIDStr) 2.93 if err != nil { 2.94 - // TODO(paddy): render invalid client JSONN 2.95 + w.WriteHeader(http.StatusUnauthorized) 2.96 + if fromAuthHeader { 2.97 + w.Header().Set("WWW-Authenticate", "Basic") 2.98 + } 2.99 + renderJSONError(enc, "invalid_client") 2.100 return 2.101 } 2.102 client, err := context.GetClient(clientID) 2.103 if err != nil { 2.104 if err == ErrClientNotFound { 2.105 - // TODO(paddy): render invalid client JSON 2.106 + w.WriteHeader(http.StatusUnauthorized) 2.107 + renderJSONError(enc, "invalid_client") 2.108 } else { 2.109 - // TODO(paddy): render internal server error JSON 2.110 + w.WriteHeader(http.StatusInternalServerError) 2.111 + renderJSONError(enc, "server_error") 2.112 } 2.113 return 2.114 } 2.115 if client.Secret != clientSecret { 2.116 - // TODO(paddy): render invalid client JSON 2.117 + w.WriteHeader(http.StatusUnauthorized) 2.118 + if fromAuthHeader { 2.119 + w.Header().Set("WWW-Authenticate", "Basic") 2.120 + } 2.121 + renderJSONError(enc, "invalid_client") 2.122 return 2.123 } 2.124 grant, err := context.GetGrant(code) 2.125 if err != nil { 2.126 if err == ErrGrantNotFound { 2.127 - // TODO(paddy): return invalid grant JSON 2.128 + w.WriteHeader(http.StatusBadRequest) 2.129 + renderJSONError(enc, "invalid_grant") 2.130 return 2.131 } 2.132 - // TODO(paddy): return internal server error JSON 2.133 + w.WriteHeader(http.StatusInternalServerError) 2.134 + renderJSONError(enc, "server_error") 2.135 return 2.136 } 2.137 if grant.RedirectURI != redirectURI { 2.138 - // TODO(paddy): return invalid grant JSON 2.139 + w.WriteHeader(http.StatusBadRequest) 2.140 + renderJSONError(enc, "invalid_grant") 2.141 return 2.142 } 2.143 if !grant.ClientID.Equal(clientID) { 2.144 - // TODO(paddy): return invalid grant JSON 2.145 + w.WriteHeader(http.StatusBadRequest) 2.146 + renderJSONError(enc, "invalid_grant") 2.147 return 2.148 } 2.149 token := Token{ 2.150 @@ -371,7 +377,8 @@ 2.151 } 2.152 err = context.SaveToken(token) 2.153 if err != nil { 2.154 - // TODO(paddy): return internal server error JSON 2.155 + w.WriteHeader(http.StatusInternalServerError) 2.156 + renderJSONError(enc, "server_error") 2.157 return 2.158 } 2.159 resp := tokenResponse{
3.1 --- a/oauth2_test.go Sat Dec 06 00:35:03 2014 -0500 3.2 +++ b/oauth2_test.go Sat Dec 06 01:47:34 2014 -0500 3.3 @@ -132,7 +132,7 @@ 3.4 } 3.5 err = testContext.DeleteGrant(red.Query().Get("code")) 3.6 if err != nil { 3.7 - t.Log(`Unexpected error "%s" deleting grant "%s" for %s`, err, red.Query().Get("code"), req.URL.String()) 3.8 + t.Logf(`Unexpected error "%s" deleting grant "%s" for %s`, err, red.Query().Get("code"), req.URL.String()) 3.9 } 3.10 stripParam("code", red) 3.11 if red.Query().Get("state") != params.Get("state") { 3.12 @@ -538,7 +538,7 @@ 3.13 } 3.14 uri, err := url.Parse("https://client.secondbit.org/") 3.15 if err != nil { 3.16 - t.Errorf("Unexpected error", err) 3.17 + t.Error("Unexpected error", err) 3.18 } 3.19 testContext := Context{ 3.20 loginURI: uri, 3.21 @@ -555,39 +555,6 @@ 3.22 } 3.23 } 3.24 3.25 -func TestGetBasicAuth(t *testing.T) { 3.26 - tests := map[string]struct { 3.27 - un string 3.28 - pass string 3.29 - err error 3.30 - }{ 3.31 - "Basic dGVzdHVzZXI6cGFzc3dvcmQx": {"testuser", "password1", nil}, 3.32 - "": {"", "", ErrNoAuth}, 3.33 - "dGVzdHVzZXI6cGFzc3dvcmQx": {"", "", ErrInvalidAuthFormat}, 3.34 - "Basic _*&^##$@#$@&!!@": {"", "", ErrInvalidAuthFormat}, 3.35 - "Basic abcdefgh": {"", "", ErrInvalidAuthFormat}, 3.36 - "Basic dXNlcjo=": {"user", "", nil}, 3.37 - } 3.38 - 3.39 - for header, test := range tests { 3.40 - req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil) 3.41 - if err != nil { 3.42 - t.Error("Unexpected error creating base request:", err) 3.43 - } 3.44 - req.Header.Set("Authorization", header) 3.45 - un, pass, err := getBasicAuth(req) 3.46 - if un != test.un { 3.47 - t.Errorf(`Expected header "%s" to return username "%s", got "%s"`, header, test.un, un) 3.48 - } 3.49 - if pass != test.pass { 3.50 - t.Errorf(`Expected header "%s" to return password "%s", got "%s"`, header, test.pass, pass) 3.51 - } 3.52 - if err != test.err { 3.53 - t.Errorf(`Expected header "%s" to return error "%s", got "%s"`, header, test.err, err) 3.54 - } 3.55 - } 3.56 -} 3.57 - 3.58 func TestCheckCookie(t *testing.T) { 3.59 t.Parallel() 3.60 req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil)