Skip to content

Commit

Permalink
Validate authorization
Browse files Browse the repository at this point in the history
Requires that the user pass in a valid looking `Authorization` header.
stripe-mock will accept any value that looks like a testmode secret key,
and the idea here is to help detect configuration problems before going
live.
  • Loading branch information
brandur committed Jul 26, 2017
1 parent 1c725bc commit 918594e
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 2 deletions.
55 changes: 54 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import (
"github.com/lestrrat/go-jsval/builder"
)

const (
invalidAuthorization = "Please authenticate by specifying an " +
"`Authorization` header with any valid looking testmode secret API " +
"key. For example, `Authorization: Bearer sk_test_123`. " +
"Authorization was '%s'."
)

// ExpansionLevel represents expansions on a single "level" of resource. It may
// have subexpansions that are meant to take effect on resources that are
// nested below it (on other levels).
Expand Down Expand Up @@ -80,6 +87,13 @@ func (s *StubServer) HandleRequest(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Request: %v %v", r.Method, r.URL.Path)

auth := r.Header.Get("Authorization")
if !validateAuth(auth) {
writeResponse(w, start, http.StatusUnauthorized,
fmt.Sprintf(invalidAuthorization, auth))
return
}

route := s.routeRequest(r)
if route == nil {
writeResponse(w, start, http.StatusNotFound, nil)
Expand Down Expand Up @@ -295,7 +309,7 @@ func getValidator(method *spec.Method) (*jsval.JSVal, error) {

func writeResponse(w http.ResponseWriter, start time.Time, status int, data interface{}) {
if data == nil {
data = []byte(http.StatusText(status))
data = http.StatusText(status)
}

encodedData, err := json.Marshal(&data)
Expand All @@ -314,3 +328,42 @@ func writeResponse(w http.ResponseWriter, start time.Time, status int, data inte
}
log.Printf("Response: elapsed=%v status=%v", time.Now().Sub(start), status)
}

func validateAuth(auth string) bool {
if auth == "" {
return false
}

parts := strings.Split(auth, " ")

// Expect ["Bearer", "sk_test_123"]
if len(parts) != 2 {
return false
}

if parts[0] != "Bearer" {
return false
}

keyParts := strings.Split(parts[1], "_")

// Expect ["sk", "test", "123"]
if len(keyParts) != 3 {
return false
}

if keyParts[0] != "sk" {
return false
}

if keyParts[1] != "test" {
return false
}

// Expect something (anything but an empty string) in the third position
if len(keyParts[2]) == 0 {
return false
}

return true
}
24 changes: 23 additions & 1 deletion server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ func TestStubServer_SetsSpecialHeaders(t *testing.T) {
server.HandleRequest(w, req)

resp := w.Result()
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
assert.Equal(t, version, resp.Header.Get("Stripe-Mock-Version"))
}

func TestStubServer_ParameterValidation(t *testing.T) {
server := getStubServer(t)

req := httptest.NewRequest("POST", "https://stripe.com/v1/charges", nil)
req.Header.Set("Authorization", "Bearer sk_test_123")
w := httptest.NewRecorder()
server.HandleRequest(w, req)

Expand Down Expand Up @@ -146,6 +147,27 @@ func TestParseExpansionLevel(t *testing.T) {
ParseExpansionLevel([]string{"*"}))
}

func TestValidateAuth(t *testing.T) {
testCases := []struct {
auth string
want bool
}{
{"Bearer sk_test_123", true},
{"", false},
{"Bearer", false},
{"Bearer sk_test_123 extra", false},
{"Bearer sk_test", false},
{"Bearer sk_test_123_extra", false},
{"Bearer sk_live_123", false},
{"Bearer sk_test_", false},
}
for _, tc := range testCases {
t.Run("Authorization: "+tc.auth, func(t *testing.T) {
assert.Equal(t, tc.want, validateAuth(tc.auth))
})
}
}

//
// ---
//
Expand Down

0 comments on commit 918594e

Please sign in to comment.