auth

Paddy 2014-12-06 Parent:11ad5eca2f82 Child:8630b108ce35

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)