From e50d727d949cca79d5af60636bb9629aea0cba57 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Wed, 30 Aug 2017 13:16:57 +0100 Subject: [PATCH 01/11] tx_status endpoint --- .../stellar/gateway/compliance/app.go | 4 +- .../gateway/compliance/config/config.go | 7 +- .../handlers/request_handler_tx_status.go | 82 +++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go diff --git a/src/github.com/stellar/gateway/compliance/app.go b/src/github.com/stellar/gateway/compliance/app.go index d9e5d17..61297d7 100644 --- a/src/github.com/stellar/gateway/compliance/app.go +++ b/src/github.com/stellar/gateway/compliance/app.go @@ -2,11 +2,12 @@ package compliance import ( "fmt" - log "github.com/Sirupsen/logrus" "net/http" "os" "time" + log "github.com/Sirupsen/logrus" + "github.com/facebookgo/inject" "github.com/stellar/gateway/compliance/config" "github.com/stellar/gateway/compliance/handlers" @@ -110,6 +111,7 @@ func (a *App) Serve() { external.Use(server.StripTrailingSlashMiddleware()) external.Use(server.HeadersMiddleware()) external.Post("/", a.requestHandler.HandlerAuth) + external.Get("/tx_status", a.requestHandler.HandlerTxStatus) externalPortString := fmt.Sprintf(":%d", *a.config.ExternalPort) log.Println("Starting external server on", externalPortString) go func() { diff --git a/src/github.com/stellar/gateway/compliance/config/config.go b/src/github.com/stellar/gateway/compliance/config/config.go index f7d8ac1..57f4d26 100644 --- a/src/github.com/stellar/gateway/compliance/config/config.go +++ b/src/github.com/stellar/gateway/compliance/config/config.go @@ -36,6 +36,7 @@ type Callbacks struct { Sanctions string AskUser string `mapstructure:"ask_user"` FetchInfo string `mapstructure:"fetch_info"` + TxStatus string `mapstructure:"tx_status"` } // Validate validates config and returns error if any of config values is incorrect @@ -97,10 +98,10 @@ func (c *Config) Validate() (err error) { } } - if c.Callbacks.Sanctions != "" { - _, err = url.Parse(c.Callbacks.Sanctions) + if c.Callbacks.TxStatus != "" { + _, err = url.Parse(c.Callbacks.TxStatus) if err != nil { - err = errors.New("Cannot parse callbacks.sanctions param") + err = errors.New("Cannot parse callbacks.tx_status param") return } } diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go new file mode 100644 index 0000000..9a211eb --- /dev/null +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go @@ -0,0 +1,82 @@ +package handlers + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + + log "github.com/Sirupsen/logrus" + + "github.com/stellar/gateway/protocols" + "github.com/stellar/gateway/server" + "github.com/zenazn/goji/web" +) + +// TransactionStatusResponse represents a response from the tx_status endpoint +type TransactionStatusResponse struct { + protocols.SuccessResponse + Status string `json:"status"` + RecvCode string `json:"recv_code,omitempty"` + RefundTx string `json:"refund_tx,omitempty"` + Msg string `json:"msg,omitempty"` +} + +// HandlerTxStatus implements /tx_status endpoint +func (rh *RequestHandler) HandlerTxStatus(c web.C, w http.ResponseWriter, r *http.Request) { + + txid := r.URL.Query().Get("id") + if txid == "" { + log.Info("unable to get query parameter") + server.Write(w, protocols.InvalidParameterError) + return + } + response := TransactionStatusResponse{} + + if rh.Config.Callbacks.TxStatus == "" { + response.Status = "unknown" + } else { + resp, err := rh.Client.PostForm( + rh.Config.Callbacks.TxStatus, + url.Values{"id": {txid}}, + ) + if err != nil { + log.WithFields(log.Fields{ + "tx_status": rh.Config.Callbacks.TxStatus, + "err": err, + }).Error("Error sending request to tx_status server") + server.Write(w, protocols.InternalServerError) + return + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("Error reading tx_status server response") + server.Write(w, protocols.InternalServerError) + return + } + + switch resp.StatusCode { + case http.StatusOK: + err := json.Unmarshal(body, &response) + if err != nil { + log.WithFields(log.Fields{ + "tx_status": rh.Config.Callbacks.TxStatus, + "body": string(body), + }).Error("Unable to decode tx_status response") + server.Write(w, protocols.InternalServerError) + return + } + if response.Status == "" { + response.Status = "unknown" + } + + default: + response.Status = "unknown" + } + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} From 952854a3815bf5874f867971f4e3c3555168fa41 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Wed, 30 Aug 2017 16:11:49 +0100 Subject: [PATCH 02/11] tests and httpauth --- .../stellar/gateway/compliance/app.go | 1 + .../gateway/compliance/config/config.go | 4 + .../request_handler_tx_status_test.go | 147 ++++++++++++++ vendor/manifest | 6 + vendor/src/github.com/goji/httpauth/LICENSE | 20 ++ vendor/src/github.com/goji/httpauth/README.md | 163 +++++++++++++++ .../github.com/goji/httpauth/basic_auth.go | 185 ++++++++++++++++++ .../goji/httpauth/basic_auth_test.go | 116 +++++++++++ 8 files changed, 642 insertions(+) create mode 100644 src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go create mode 100644 vendor/src/github.com/goji/httpauth/LICENSE create mode 100644 vendor/src/github.com/goji/httpauth/README.md create mode 100644 vendor/src/github.com/goji/httpauth/basic_auth.go create mode 100644 vendor/src/github.com/goji/httpauth/basic_auth_test.go diff --git a/src/github.com/stellar/gateway/compliance/app.go b/src/github.com/stellar/gateway/compliance/app.go index 61297d7..ddf1289 100644 --- a/src/github.com/stellar/gateway/compliance/app.go +++ b/src/github.com/stellar/gateway/compliance/app.go @@ -112,6 +112,7 @@ func (a *App) Serve() { external.Use(server.HeadersMiddleware()) external.Post("/", a.requestHandler.HandlerAuth) external.Get("/tx_status", a.requestHandler.HandlerTxStatus) + // external.Get("/tx_status", httpauth.SimpleBasicAuth(a.config.TxStatusAuth.Username, a.config.TxStatusAuth.Password)(a.requestHandler.HandlerTxStatus)) externalPortString := fmt.Sprintf(":%d", *a.config.ExternalPort) log.Println("Starting external server on", externalPortString) go func() { diff --git a/src/github.com/stellar/gateway/compliance/config/config.go b/src/github.com/stellar/gateway/compliance/config/config.go index 57f4d26..548150e 100644 --- a/src/github.com/stellar/gateway/compliance/config/config.go +++ b/src/github.com/stellar/gateway/compliance/config/config.go @@ -24,6 +24,10 @@ type Config struct { CertificateFile string `mapstructure:"certificate_file"` PrivateKeyFile string `mapstructure:"private_key_file"` } + TxStatusAuth struct { + Username string `valid:"required,alphanum"` + Password string `valid:"required,length(10|1000)"` + } `valid:"required" toml:"tx_status_auth"` } // Keys contains values of `keys` config group diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go new file mode 100644 index 0000000..39e3ff4 --- /dev/null +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go @@ -0,0 +1,147 @@ +package handlers + +import ( + "net/http" + // // "net/http/httptest" + "net/url" + // // "strings" + "testing" + + "github.com/facebookgo/inject" + . "github.com/smartystreets/goconvey/convey" + "github.com/stellar/gateway/compliance/config" + // c"github.com/stellar/gateway/db/entities" + "github.com/stellar/gateway/mocks" + "github.com/stellar/gateway/net" + // // "github.com/stellar/gateway/test" + "github.com/stellar/go/support/http/httptest" + // callback "github.com/stellar/gateway/protocols/compliance" + // //"github.com/stretchr/testify/assert" + "github.com/zenazn/goji/web" +) + +func TestRequestHandlerTxStatus(t *testing.T) { + c := &config.Config{ + NetworkPassphrase: "Test SDF Network ; September 2015", + Keys: config.Keys{ + // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB + SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", + }, + } + txid := "abc123" + mockHTTPClient := new(mocks.MockHTTPClient) + mockEntityManager := new(mocks.MockEntityManager) + mockRepository := new(mocks.MockRepository) + mockFederationResolver := new(mocks.MockFederationResolver) + mockSignerVerifier := new(mocks.MockSignerVerifier) + mockStellartomlResolver := new(mocks.MockStellartomlResolver) + requestHandler := RequestHandler{} + + // Inject mocks + var g inject.Graph + + err := g.Provide( + &inject.Object{Value: &requestHandler}, + &inject.Object{Value: c}, + &inject.Object{Value: mockHTTPClient}, + &inject.Object{Value: mockEntityManager}, + &inject.Object{Value: mockRepository}, + &inject.Object{Value: mockFederationResolver}, + &inject.Object{Value: mockSignerVerifier}, + &inject.Object{Value: mockStellartomlResolver}, + &inject.Object{Value: &TestNonceGenerator{}}, + ) + if err != nil { + panic(err) + } + + if err := g.Populate(); err != nil { + panic(err) + } + + httpHandle := func(w http.ResponseWriter, r *http.Request) { + requestHandler.HandlerTxStatus(web.C{}, w, r) + } + + testServer := httptest.NewServer(t, http.HandlerFunc(httpHandle)) + defer testServer.Close() + + Convey("Given tx_status request", t, func() { + Convey("it returns bad request when no parameter", func() { + testServer.GET("/tx_status"). + Expect(). + Status(http.StatusBadRequest) + }) + Convey("it returns unknown when no tx_status endpoint", func() { + testServer.GET("/tx_status"). + WithQuery("id", "123"). + Expect(). + Status(http.StatusOK). + Body().Equal(`{"status":"unknown"}` + "\n") + }) + Convey("it returns unknown when valid endpoint returns bad request", func() { + c.Callbacks = config.Callbacks{ + TxStatus: "http://tx_status", + } + + mockHTTPClient.On( + "PostForm", + "http://tx_status", + url.Values{"id": {txid}}, + ).Return( + net.BuildHTTPResponse(400, "badrequest"), + nil, + ).Once() + + testServer.GET("/tx_status"). + WithQuery("id", txid). + Expect(). + Status(http.StatusOK). + Body().Equal(`{"status":"unknown"}` + "\n") + }) + + Convey("it returns unknown when valid endpoint returns empty data", func() { + c.Callbacks = config.Callbacks{ + TxStatus: "http://tx_status", + } + + mockHTTPClient.On( + "PostForm", + "http://tx_status", + url.Values{"id": {txid}}, + ).Return( + net.BuildHTTPResponse(200, "{}"), + nil, + ).Once() + + testServer.GET("/tx_status"). + WithQuery("id", txid). + Expect(). + Status(http.StatusOK). + Body().Equal(`{"status":"unknown"}` + "\n") + }) + + Convey("it returns response from valid endpoint with data", func() { + c.Callbacks = config.Callbacks{ + TxStatus: "http://tx_status", + } + + mockHTTPClient.On( + "PostForm", + "http://tx_status", + url.Values{"id": {txid}}, + ).Return( + net.BuildHTTPResponse(200, `{"status":"delivered","msg":"cash paid"}`), + nil, + ).Once() + + testServer.GET("/tx_status"). + WithQuery("id", txid). + Expect(). + Status(http.StatusOK). + Body().Equal(`{"status":"delivered","msg":"cash paid"}` + "\n") + }) + + + }) +} diff --git a/vendor/manifest b/vendor/manifest index 810a500..544d936 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -166,6 +166,12 @@ "revision": "c9c5955059ba2e3d6a25e8f1a555e59015d4abba", "branch": "master" }, + { + "importpath": "github.com/goji/httpauth", + "repository": "https://github.com/goji/httpauth", + "revision": "2da839ab0f4df05a6db5eb277995589dadbd4fb9", + "branch": "master" + }, { "importpath": "github.com/goji/param", "repository": "https://github.com/goji/param", diff --git a/vendor/src/github.com/goji/httpauth/LICENSE b/vendor/src/github.com/goji/httpauth/LICENSE new file mode 100644 index 0000000..316bced --- /dev/null +++ b/vendor/src/github.com/goji/httpauth/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Carl Jackson (carl@avtok.com), Matt Silverlock (matt@eatsleeprepeat.net) + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/src/github.com/goji/httpauth/README.md b/vendor/src/github.com/goji/httpauth/README.md new file mode 100644 index 0000000..a5fb22d --- /dev/null +++ b/vendor/src/github.com/goji/httpauth/README.md @@ -0,0 +1,163 @@ +# goji/httpauth [![GoDoc](https://godoc.org/github.com/goji/httpauth?status.svg)](https://godoc.org/github.com/goji/httpauth) [![Build Status](https://travis-ci.org/goji/httpauth.svg)](https://travis-ci.org/goji/httpauth) + +`httpauth` currently provides [HTTP Basic Authentication middleware](http://tools.ietf.org/html/rfc2617) for Go. It is compatible with Go's own `net/http`, [goji](https://goji.io), Gin & anything that speaks the `http.Handler` interface. + +## Example + +`httpauth` provides a `SimpleBasicAuth` function to get you up and running. Particularly ideal for development servers. + +Note that HTTP Basic Authentication credentials are sent over the wire "in the clear" (read: plaintext!) and therefore should not be considered a robust way to secure a HTTP server. If you're after that, you'll need to use SSL/TLS ("HTTPS") at a minimum. + +### Install It + +```sh +$ go get github.com/goji/httpauth +``` + +### Goji v2 + +#### Simple Usage + +The fastest and simplest way to get started using `httpauth` is to use the +`SimpleBasicAuth` function. + +```go + +package main + +import( + "net/http" + + "goji.io" +) + +func main() { + mux := goji.NewMux() + + mux.Use(httpauth.SimpleBasicAuth("dave", "somepassword")) + mux.Use(SomeOtherMiddleware) + + // YourHandler now requires HTTP Basic Auth + mux.Handle(pat.Get("/some-route"), YourHandler)) + + log.Fatal(http.ListenAndServe("localhost:8000", mux)) +} +``` + +#### Advanced Usage + +For more control over the process, pass a `AuthOptions` struct to `BasicAuth` instead. This allows you to: + +* Configure the authentication realm. +* Provide your own UnauthorizedHandler (anything that satisfies `http.Handler`) so you can return a better looking 401 page. +* Define a custom authentication function, which is discussed in the next section. + +```go + +func main() { + + authOpts := httpauth.AuthOptions{ + Realm: "DevCo", + User: "dave", + Password: "plaintext!", + UnauthorizedHandler: myUnauthorizedHandler, + } + + mux := goji.NewMux() + + mux.Use(BasicAuth(authOpts)) + mux.Use(SomeOtherMiddleware) + + mux.Handle(pat.Get("/some-route"), YourHandler)) + + log.Fatal(http.ListenAndServe("localhost:8000", mux)) +} +``` + +#### Custom Authentication Function + +`httpauth` will accept a custom authentication function. +Normally, you would not set `AuthOptions.User` nor `AuthOptions.Password` in this scenario. +You would instead validate the given credentials against an external system such as a database. +The contrived example below is for demonstration purposes only. + +```go +func main() { + + authOpts := httpauth.AuthOptions{ + Realm: "DevCo", + AuthFunc: myAuthFunc, + UnauthorizedHandler: myUnauthorizedHandler, + } + + mux := goji.NewMux() + + mux.Use(BasicAuth(authOpts)) + mux.Use(SomeOtherMiddleware) + + mux.Handle(pat.Get("/some-route"), YourHandler)) + + log.Fatal(http.ListenAndServe("localhost:8000", mux)) +} + +// myAuthFunc is not secure. It checks to see if the password is simply +// the username repeated three times. +func myAuthFunc(user, pass string, r *http.Request) bool { + return pass == strings.Repeat(user, 3) +} +``` + +### gorilla/mux + +Since it's all `http.Handler`, `httpauth` works with [gorilla/mux](https://github.com/gorilla/mux) (and most other routers) as well: + +```go +package main + +import ( + "net/http" + + "github.com/goji/httpauth" + "github.com/gorilla/mux" +) + +func main() { + r := mux.NewRouter() + + r.HandleFunc("/", YourHandler) + http.Handle("/", httpauth.SimpleBasicAuth("dave", "somepassword")(r)) + + http.ListenAndServe(":7000", nil) +} + +func YourHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Gorilla!\n")) +} +``` + +### net/http + +If you're using vanilla `net/http`: + +```go +package main + +import( + "net/http" + + "github.com/goji/httpauth" +) + +func main() { + http.Handle("/", httpauth.SimpleBasicAuth("dave", "somepassword")(http.HandlerFunc(YourHandler))) + http.ListenAndServe(":7000", nil) +} +``` + +## Contributing + +Send a pull request! Note that features on the (informal) roadmap include HTTP Digest Auth. + +## License + +MIT Licensed. See the LICENSE file for details. diff --git a/vendor/src/github.com/goji/httpauth/basic_auth.go b/vendor/src/github.com/goji/httpauth/basic_auth.go new file mode 100644 index 0000000..a14c27a --- /dev/null +++ b/vendor/src/github.com/goji/httpauth/basic_auth.go @@ -0,0 +1,185 @@ +package httpauth + +import ( + "bytes" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "fmt" + "net/http" + "strings" +) + +type basicAuth struct { + h http.Handler + opts AuthOptions +} + +// AuthOptions stores the configuration for HTTP Basic Authentication. +// +// A http.Handler may also be passed to UnauthorizedHandler to override the +// default error handler if you wish to serve a custom template/response. +type AuthOptions struct { + Realm string + User string + Password string + AuthFunc func(string, string, *http.Request) bool + UnauthorizedHandler http.Handler +} + +// Satisfies the http.Handler interface for basicAuth. +func (b basicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Check if we have a user-provided error handler, else set a default + if b.opts.UnauthorizedHandler == nil { + b.opts.UnauthorizedHandler = http.HandlerFunc(defaultUnauthorizedHandler) + } + + // Check that the provided details match + if b.authenticate(r) == false { + b.requestAuth(w, r) + return + } + + // Call the next handler on success. + b.h.ServeHTTP(w, r) +} + +// authenticate retrieves and then validates the user:password combination provided in +// the request header. Returns 'false' if the user has not successfully authenticated. +func (b *basicAuth) authenticate(r *http.Request) bool { + const basicScheme string = "Basic " + + if r == nil { + return false + } + + // In simple mode, prevent authentication with empty credentials if User is + // not set. Allow empty passwords to support non-password use-cases. + if b.opts.AuthFunc == nil && b.opts.User == "" { + return false + } + + // Confirm the request is sending Basic Authentication credentials. + auth := r.Header.Get("Authorization") + if !strings.HasPrefix(auth, basicScheme) { + return false + } + + // Get the plain-text username and password from the request. + // The first six characters are skipped - e.g. "Basic ". + str, err := base64.StdEncoding.DecodeString(auth[len(basicScheme):]) + if err != nil { + return false + } + + // Split on the first ":" character only, with any subsequent colons assumed to be part + // of the password. Note that the RFC2617 standard does not place any limitations on + // allowable characters in the password. + creds := bytes.SplitN(str, []byte(":"), 2) + + if len(creds) != 2 { + return false + } + + givenUser := string(creds[0]) + givenPass := string(creds[1]) + + // Default to Simple mode if no AuthFunc is defined. + if b.opts.AuthFunc == nil { + b.opts.AuthFunc = b.simpleBasicAuthFunc + } + + return b.opts.AuthFunc(givenUser, givenPass, r) +} + +// simpleBasicAuthFunc authenticates the supplied username and password against +// the User and Password set in the Options struct. +func (b *basicAuth) simpleBasicAuthFunc(user, pass string, r *http.Request) bool { + // Equalize lengths of supplied and required credentials + // by hashing them + givenUser := sha256.Sum256([]byte(user)) + givenPass := sha256.Sum256([]byte(pass)) + requiredUser := sha256.Sum256([]byte(b.opts.User)) + requiredPass := sha256.Sum256([]byte(b.opts.Password)) + + // Compare the supplied credentials to those set in our options + if subtle.ConstantTimeCompare(givenUser[:], requiredUser[:]) == 1 && + subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1 { + return true + } + + return false +} + +// Require authentication, and serve our error handler otherwise. +func (b *basicAuth) requestAuth(w http.ResponseWriter, r *http.Request) { + w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, b.opts.Realm)) + b.opts.UnauthorizedHandler.ServeHTTP(w, r) +} + +// defaultUnauthorizedHandler provides a default HTTP 401 Unauthorized response. +func defaultUnauthorizedHandler(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) +} + +// BasicAuth provides HTTP middleware for protecting URIs with HTTP Basic Authentication +// as per RFC 2617. The server authenticates a user:password combination provided in the +// "Authorization" HTTP header. +// +// Example: +// +// package main +// +// import( +// "net/http" +// "github.com/zenazn/goji" +// "github.com/goji/httpauth" +// ) +// +// func main() { +// basicOpts := httpauth.AuthOptions{ +// Realm: "Restricted", +// User: "Dave", +// Password: "ClearText", +// } +// +// goji.Use(httpauth.BasicAuth(basicOpts), SomeOtherMiddleware) +// goji.Get("/thing", myHandler) +// } +// +// Note: HTTP Basic Authentication credentials are sent in plain text, and therefore it does +// not make for a wholly secure authentication mechanism. You should serve your content over +// HTTPS to mitigate this, noting that "Basic Authentication" is meant to be just that: basic! +func BasicAuth(o AuthOptions) func(http.Handler) http.Handler { + fn := func(h http.Handler) http.Handler { + return basicAuth{h, o} + } + return fn +} + +// SimpleBasicAuth is a convenience wrapper around BasicAuth. It takes a user and password, and +// returns a pre-configured BasicAuth handler using the "Restricted" realm and a default 401 handler. +// +// Example: +// +// package main +// +// import( +// "net/http" +// "github.com/zenazn/goji/web/httpauth" +// ) +// +// func main() { +// +// goji.Use(httpauth.SimpleBasicAuth("dave", "somepassword"), SomeOtherMiddleware) +// goji.Get("/thing", myHandler) +// } +// +func SimpleBasicAuth(user, password string) func(http.Handler) http.Handler { + opts := AuthOptions{ + Realm: "Restricted", + User: user, + Password: password, + } + return BasicAuth(opts) +} diff --git a/vendor/src/github.com/goji/httpauth/basic_auth_test.go b/vendor/src/github.com/goji/httpauth/basic_auth_test.go new file mode 100644 index 0000000..a4c33fb --- /dev/null +++ b/vendor/src/github.com/goji/httpauth/basic_auth_test.go @@ -0,0 +1,116 @@ +package httpauth + +import ( + "encoding/base64" + "net/http" + "testing" +) + +func TestBasicAuthAuthenticateWithFunc(t *testing.T) { + requiredUser := "jqpublic" + requiredPass := "secret.sauce" + + r := &http.Request{Method: "GET"} + + // Dumb test function + fn := func(u, p string, req *http.Request) bool { + return u == requiredUser && p == requiredPass && req == r + } + + // Provide a minimal test implementation. + authOpts := AuthOptions{ + Realm: "Restricted", + AuthFunc: fn, + } + + b := &basicAuth{opts: authOpts} + + if b.authenticate(nil) { + t.Fatal("Should not succeed when http.Request is nil") + } + + // Provide auth data, but no Authorization header + if b.authenticate(r) != false { + t.Fatal("No Authorization header supplied.") + } + + // Initialise the map for HTTP headers + r.Header = http.Header(make(map[string][]string)) + + // Set a malformed/bad header + r.Header.Set("Authorization", " Basic") + if b.authenticate(r) != false { + t.Fatal("Malformed Authorization header supplied.") + } + + // Test correct credentials + auth := base64.StdEncoding.EncodeToString([]byte("jqpublic:secret.sauce")) + r.Header.Set("Authorization", "Basic "+auth) + if b.authenticate(r) != true { + t.Fatal("Failed on correct credentials") + } + + // Test incorrect credentials + auth = base64.StdEncoding.EncodeToString([]byte("jqpublic:hackydoo")) + r.Header.Set("Authorization", "Basic "+auth) + if b.authenticate(r) == true { + t.Fatal("Success when expecting failure") + } +} + +func TestBasicAuthAuthenticate(t *testing.T) { + // Provide a minimal test implementation. + authOpts := AuthOptions{ + Realm: "Restricted", + User: "test-user", + Password: "plain-text-password", + } + + b := &basicAuth{ + opts: authOpts, + } + + r := &http.Request{Method: "GET"} + + // Provide auth data, but no Authorization header + if b.authenticate(r) != false { + t.Fatal("No Authorization header supplied.") + } + + // Initialise the map for HTTP headers + r.Header = http.Header(make(map[string][]string)) + + // Set a malformed/bad header + r.Header.Set("Authorization", " Basic") + if b.authenticate(r) != false { + t.Fatal("Malformed Authorization header supplied.") + } + + // Test correct credentials + auth := base64.StdEncoding.EncodeToString([]byte(b.opts.User + ":" + b.opts.Password)) + r.Header.Set("Authorization", "Basic "+auth) + if b.authenticate(r) != true { + t.Fatal("Failed on correct credentials") + } +} + +func TestBasicAuthAuthenticateWithoutUserAndPass(t *testing.T) { + b := basicAuth{opts: AuthOptions{}} + + r := &http.Request{Method: "GET"} + + // Provide auth data, but no Authorization header + if b.authenticate(r) != false { + t.Fatal("No Authorization header supplied.") + } + + // Initialise the map for HTTP headers + r.Header = http.Header(make(map[string][]string)) + + // Test correct credentials + auth := base64.StdEncoding.EncodeToString([]byte(b.opts.User + ":" + b.opts.Password)) + r.Header.Set("Authorization", "Basic "+auth) + if b.authenticate(r) != false { + t.Fatal("Success when expecting failure") + } +} From 95d085b73355bbdc9066a7010ef78c2b0d587341 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Wed, 30 Aug 2017 18:28:53 +0100 Subject: [PATCH 03/11] httpauth and tests --- .../stellar/gateway/compliance/app.go | 4 +- .../handlers/request_handler_tx_status.go | 3 +- .../request_handler_tx_status_test.go | 75 +++++++++++-------- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/github.com/stellar/gateway/compliance/app.go b/src/github.com/stellar/gateway/compliance/app.go index ddf1289..2716927 100644 --- a/src/github.com/stellar/gateway/compliance/app.go +++ b/src/github.com/stellar/gateway/compliance/app.go @@ -7,6 +7,7 @@ import ( "time" log "github.com/Sirupsen/logrus" + "github.com/goji/httpauth" "github.com/facebookgo/inject" "github.com/stellar/gateway/compliance/config" @@ -111,8 +112,7 @@ func (a *App) Serve() { external.Use(server.StripTrailingSlashMiddleware()) external.Use(server.HeadersMiddleware()) external.Post("/", a.requestHandler.HandlerAuth) - external.Get("/tx_status", a.requestHandler.HandlerTxStatus) - // external.Get("/tx_status", httpauth.SimpleBasicAuth(a.config.TxStatusAuth.Username, a.config.TxStatusAuth.Password)(a.requestHandler.HandlerTxStatus)) + external.Get("/tx_status", httpauth.SimpleBasicAuth(a.config.TxStatusAuth.Username, a.config.TxStatusAuth.Password)(http.HandlerFunc(a.requestHandler.HandlerTxStatus))) externalPortString := fmt.Sprintf(":%d", *a.config.ExternalPort) log.Println("Starting external server on", externalPortString) go func() { diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go index 9a211eb..5cc80ac 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go @@ -10,7 +10,6 @@ import ( "github.com/stellar/gateway/protocols" "github.com/stellar/gateway/server" - "github.com/zenazn/goji/web" ) // TransactionStatusResponse represents a response from the tx_status endpoint @@ -23,7 +22,7 @@ type TransactionStatusResponse struct { } // HandlerTxStatus implements /tx_status endpoint -func (rh *RequestHandler) HandlerTxStatus(c web.C, w http.ResponseWriter, r *http.Request) { +func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request) { txid := r.URL.Query().Get("id") if txid == "" { diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go index 39e3ff4..61ea82f 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go @@ -2,22 +2,16 @@ package handlers import ( "net/http" - // // "net/http/httptest" "net/url" - // // "strings" "testing" "github.com/facebookgo/inject" + "github.com/goji/httpauth" . "github.com/smartystreets/goconvey/convey" "github.com/stellar/gateway/compliance/config" - // c"github.com/stellar/gateway/db/entities" "github.com/stellar/gateway/mocks" "github.com/stellar/gateway/net" - // // "github.com/stellar/gateway/test" "github.com/stellar/go/support/http/httptest" - // callback "github.com/stellar/gateway/protocols/compliance" - // //"github.com/stretchr/testify/assert" - "github.com/zenazn/goji/web" ) func TestRequestHandlerTxStatus(t *testing.T) { @@ -28,6 +22,9 @@ func TestRequestHandlerTxStatus(t *testing.T) { SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", }, } + c.TxStatusAuth.Username = "username" + c.TxStatusAuth.Password = "password" + txid := "abc123" mockHTTPClient := new(mocks.MockHTTPClient) mockEntityManager := new(mocks.MockEntityManager) @@ -60,20 +57,34 @@ func TestRequestHandlerTxStatus(t *testing.T) { } httpHandle := func(w http.ResponseWriter, r *http.Request) { - requestHandler.HandlerTxStatus(web.C{}, w, r) + requestHandler.HandlerTxStatus(w, r) } - testServer := httptest.NewServer(t, http.HandlerFunc(httpHandle)) + testServer := httptest.NewServer(t, httpauth.SimpleBasicAuth(c.TxStatusAuth.Username, + c.TxStatusAuth.Password)(http.HandlerFunc(httpHandle))) defer testServer.Close() Convey("Given tx_status request", t, func() { + Convey("it returns unathorised when no auth", func() { + testServer.GET("/tx_status"). + Expect(). + Status(http.StatusUnauthorized) + }) + Convey("it returns unauthorised when bad auth", func() { + testServer.GET("/tx_status"). + WithBasicAuth("username", "wrong_password"). + Expect(). + Status(http.StatusUnauthorized) + }) Convey("it returns bad request when no parameter", func() { testServer.GET("/tx_status"). + WithBasicAuth("username", "password"). Expect(). Status(http.StatusBadRequest) }) Convey("it returns unknown when no tx_status endpoint", func() { testServer.GET("/tx_status"). + WithBasicAuth("username", "password"). WithQuery("id", "123"). Expect(). Status(http.StatusOK). @@ -85,15 +96,16 @@ func TestRequestHandlerTxStatus(t *testing.T) { } mockHTTPClient.On( - "PostForm", - "http://tx_status", - url.Values{"id": {txid}}, - ).Return( - net.BuildHTTPResponse(400, "badrequest"), - nil, - ).Once() + "PostForm", + "http://tx_status", + url.Values{"id": {txid}}, + ).Return( + net.BuildHTTPResponse(400, "badrequest"), + nil, + ).Once() testServer.GET("/tx_status"). + WithBasicAuth("username", "password"). WithQuery("id", txid). Expect(). Status(http.StatusOK). @@ -106,15 +118,16 @@ func TestRequestHandlerTxStatus(t *testing.T) { } mockHTTPClient.On( - "PostForm", - "http://tx_status", - url.Values{"id": {txid}}, - ).Return( - net.BuildHTTPResponse(200, "{}"), - nil, - ).Once() + "PostForm", + "http://tx_status", + url.Values{"id": {txid}}, + ).Return( + net.BuildHTTPResponse(200, "{}"), + nil, + ).Once() testServer.GET("/tx_status"). + WithBasicAuth("username", "password"). WithQuery("id", txid). Expect(). Status(http.StatusOK). @@ -127,21 +140,21 @@ func TestRequestHandlerTxStatus(t *testing.T) { } mockHTTPClient.On( - "PostForm", - "http://tx_status", - url.Values{"id": {txid}}, - ).Return( - net.BuildHTTPResponse(200, `{"status":"delivered","msg":"cash paid"}`), - nil, - ).Once() + "PostForm", + "http://tx_status", + url.Values{"id": {txid}}, + ).Return( + net.BuildHTTPResponse(200, `{"status":"delivered","msg":"cash paid"}`), + nil, + ).Once() testServer.GET("/tx_status"). + WithBasicAuth("username", "password"). WithQuery("id", txid). Expect(). Status(http.StatusOK). Body().Equal(`{"status":"delivered","msg":"cash paid"}` + "\n") }) - }) } From 68f97da035a91230467fc5d69a6fec31e8810653 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Wed, 30 Aug 2017 18:39:47 +0100 Subject: [PATCH 04/11] tx_status readme --- readme_compliance.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/readme_compliance.md b/readme_compliance.md index 442d48b..c944ffe 100644 --- a/readme_compliance.md +++ b/readme_compliance.md @@ -34,10 +34,14 @@ The `compliance.cfg` file must be present in a working directory (you can load a * `sanctions` - Callback that performs sanctions check. Read [Callbacks](#callbacks) section. * `ask_user` - Callback that asks user for permission for reading their data. Read [Callbacks](#callbacks) section. * `fetch_info` - Callback that returns user data. Read [Callbacks](#callbacks) section. + * `tx_status` - Callback that returns user data. Read [Callbacks](#callbacks) section. * `tls` (only when running HTTPS external server) * `certificate_file` - a file containing a certificate * `private_key_file` - a file containing a matching private key * `log_format` - set to `json` for JSON logs +* `tx_status_auth` - authentication credentials for `/tx_status` endpoint. + * `username` + * `password` - minimum 10 chars Check [`compliance_example.cfg`](./compliance_example.cfg). @@ -76,7 +80,7 @@ Returns [Auth response](https://www.stellar.org/developers/learn/integration-gui ### POST :internal_port/send -Typically called by the bridge server when a user initiates a payment. This endpoint causes the compliance server to send an Auth request to another organization. It will call the Auth endpoint of the receiving instition. +Typically called by the bridge server when a user initiates a payment. This endpoint causes the compliance server to send an Auth request to another organization. It will call the Auth endpoint of the receiving instition. #### Request Parameters @@ -239,6 +243,27 @@ This callback should return `200 OK` status code and JSON object with the custom "date_of_birth": "1990-01-01" } ``` +### `callbacks.tx_status` +This callback should return the status of a transaction as explained in [`SEP-0001`](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md). + +#### Request + +name | description +--- | --- +`id` | Stellar transaction ID. + +#### Response +This callback should return `200 OK` status code and JSON object with the transaction status info: + +```json +{ + "status": "status code as defined in [`SEP-0001`](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md)", + "recv_code": "arbitrary string", + "refund_tx": "tx_hash", + "msg": "arbitrary string" +} +``` + Any other status code will be considered an error. From 2c8c1ccbafe8d3963b362b6cad645bdd3cea71f7 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Wed, 30 Aug 2017 18:49:44 +0100 Subject: [PATCH 05/11] ts_status readme --- readme_compliance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme_compliance.md b/readme_compliance.md index c944ffe..7fa154b 100644 --- a/readme_compliance.md +++ b/readme_compliance.md @@ -257,7 +257,7 @@ This callback should return `200 OK` status code and JSON object with the transa ```json { - "status": "status code as defined in [`SEP-0001`](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md)", + "status": "status code as defined in SEP-0001", "recv_code": "arbitrary string", "refund_tx": "tx_hash", "msg": "arbitrary string" From b691e86472c62fe6c4bbd39697aadf9136e68911 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Thu, 31 Aug 2017 12:21:34 +0100 Subject: [PATCH 06/11] update compliance_example.cfg --- compliance_example.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compliance_example.cfg b/compliance_example.cfg index 93d2fae..ca05345 100644 --- a/compliance_example.cfg +++ b/compliance_example.cfg @@ -18,7 +18,12 @@ encryption_key = "" sanctions = "http://sanctions" ask_user = "http://ask_user" fetch_info = "http://fetch_info" +tx_status = "http://tx_status" [tls] certificate_file = "server.crt" private_key_file = "server.key" + +[tx_status_auth] +username = "username" +password = "password" From 6801edf1244e85178a0f3f0148f49c4095d1176b Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Tue, 5 Sep 2017 13:58:35 +0100 Subject: [PATCH 07/11] add GET mocks --- src/github.com/stellar/gateway/mocks/main.go | 6 ++++++ src/github.com/stellar/gateway/net/main.go | 1 + 2 files changed, 7 insertions(+) diff --git a/src/github.com/stellar/gateway/mocks/main.go b/src/github.com/stellar/gateway/mocks/main.go index bb414b3..03305a9 100644 --- a/src/github.com/stellar/gateway/mocks/main.go +++ b/src/github.com/stellar/gateway/mocks/main.go @@ -64,6 +64,12 @@ func (m *MockHTTPClient) PostForm(url string, data url.Values) (resp *http.Respo return a.Get(0).(*http.Response), a.Error(1) } +// Get is a mocking a method +func (m *MockHTTPClient) Get(url string) (resp *http.Response, err error) { + a := m.Called(url) + return a.Get(0).(*http.Response), a.Error(1) +} + // Do is a mocking a method func (m *MockHTTPClient) Do(req *http.Request) (resp *http.Response, err error) { a := m.Called(req) diff --git a/src/github.com/stellar/gateway/net/main.go b/src/github.com/stellar/gateway/net/main.go index e89aaae..4b54492 100644 --- a/src/github.com/stellar/gateway/net/main.go +++ b/src/github.com/stellar/gateway/net/main.go @@ -12,6 +12,7 @@ import ( // HTTPClientInterface helps mocking http.Client in tests type HTTPClientInterface interface { PostForm(url string, data url.Values) (resp *http.Response, err error) + Get(url string) (resp *http.Response, err error) } // BuildHTTPResponse is used in tests From 1055b6cd5e9f2445103f8081b756ad00eecc54b3 Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Tue, 5 Sep 2017 13:59:02 +0100 Subject: [PATCH 08/11] WIP: tx_status --- readme_compliance.md | 4 ++-- .../gateway/compliance/config/config.go | 6 ++--- .../handlers/request_handler_tx_status.go | 24 +++++++++++++++---- .../request_handler_tx_status_test.go | 17 ++++++------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/readme_compliance.md b/readme_compliance.md index 7fa154b..aead1f5 100644 --- a/readme_compliance.md +++ b/readme_compliance.md @@ -258,8 +258,8 @@ This callback should return `200 OK` status code and JSON object with the transa ```json { "status": "status code as defined in SEP-0001", - "recv_code": "arbitrary string", - "refund_tx": "tx_hash", + "recv_code": "arbitrary string", + "refund_tx": "tx_hash", "msg": "arbitrary string" } ``` diff --git a/src/github.com/stellar/gateway/compliance/config/config.go b/src/github.com/stellar/gateway/compliance/config/config.go index 548150e..30939fd 100644 --- a/src/github.com/stellar/gateway/compliance/config/config.go +++ b/src/github.com/stellar/gateway/compliance/config/config.go @@ -25,9 +25,9 @@ type Config struct { PrivateKeyFile string `mapstructure:"private_key_file"` } TxStatusAuth struct { - Username string `valid:"required,alphanum"` - Password string `valid:"required,length(10|1000)"` - } `valid:"required" toml:"tx_status_auth"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + } `mapstructure:"tx_status_auth"` } // Keys contains values of `keys` config group diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go index 5cc80ac..43005c4 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go @@ -2,12 +2,12 @@ package handlers import ( "encoding/json" + "fmt" "io/ioutil" "net/http" "net/url" log "github.com/Sirupsen/logrus" - "github.com/stellar/gateway/protocols" "github.com/stellar/gateway/server" ) @@ -27,7 +27,7 @@ func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request txid := r.URL.Query().Get("id") if txid == "" { log.Info("unable to get query parameter") - server.Write(w, protocols.InvalidParameterError) + server.Write(w, protocols.MissingParameterError) return } response := TransactionStatusResponse{} @@ -35,10 +35,19 @@ func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request if rh.Config.Callbacks.TxStatus == "" { response.Status = "unknown" } else { - resp, err := rh.Client.PostForm( + endpoint := fmt.Sprintf( + "%s?id=%s", rh.Config.Callbacks.TxStatus, - url.Values{"id": {txid}}, + txid, ) + + _, err := url.Parse(endpoint) + if err != nil { + log.Error(err, "failed to parse tx status endpoint") + server.Write(w, protocols.InternalServerError) + return + } + resp, err := rh.Client.Get(endpoint) if err != nil { log.WithFields(log.Fields{ "tx_status": rh.Config.Callbacks.TxStatus, @@ -77,5 +86,10 @@ func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + err := json.NewEncoder(w).Encode(response) + if err != nil { + log.Error("Error encoding tx status response") + server.Write(w, protocols.InternalServerError) + return + } } diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go index 61ea82f..abbacf5 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go @@ -2,7 +2,6 @@ package handlers import ( "net/http" - "net/url" "testing" "github.com/facebookgo/inject" @@ -67,6 +66,7 @@ func TestRequestHandlerTxStatus(t *testing.T) { Convey("Given tx_status request", t, func() { Convey("it returns unathorised when no auth", func() { testServer.GET("/tx_status"). + WithQuery("id", "123"). Expect(). Status(http.StatusUnauthorized) }) @@ -96,9 +96,8 @@ func TestRequestHandlerTxStatus(t *testing.T) { } mockHTTPClient.On( - "PostForm", - "http://tx_status", - url.Values{"id": {txid}}, + "Get", + "http://tx_status?id="+txid, ).Return( net.BuildHTTPResponse(400, "badrequest"), nil, @@ -118,9 +117,8 @@ func TestRequestHandlerTxStatus(t *testing.T) { } mockHTTPClient.On( - "PostForm", - "http://tx_status", - url.Values{"id": {txid}}, + "Get", + "http://tx_status?id="+txid, ).Return( net.BuildHTTPResponse(200, "{}"), nil, @@ -140,9 +138,8 @@ func TestRequestHandlerTxStatus(t *testing.T) { } mockHTTPClient.On( - "PostForm", - "http://tx_status", - url.Values{"id": {txid}}, + "Get", + "http://tx_status?id="+txid, ).Return( net.BuildHTTPResponse(200, `{"status":"delivered","msg":"cash paid"}`), nil, From f0b740a8a029f24b429d349d405ee6286015181c Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Tue, 5 Sep 2017 16:48:41 +0100 Subject: [PATCH 09/11] fix tx_status changes --- .../handlers/request_handler_tx_status.go | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go index 43005c4..2ec2a64 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go @@ -10,30 +10,22 @@ import ( log "github.com/Sirupsen/logrus" "github.com/stellar/gateway/protocols" "github.com/stellar/gateway/server" + "github.com/stellar/go/protocols/compliance" ) -// TransactionStatusResponse represents a response from the tx_status endpoint -type TransactionStatusResponse struct { - protocols.SuccessResponse - Status string `json:"status"` - RecvCode string `json:"recv_code,omitempty"` - RefundTx string `json:"refund_tx,omitempty"` - Msg string `json:"msg,omitempty"` -} - // HandlerTxStatus implements /tx_status endpoint func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request) { txid := r.URL.Query().Get("id") if txid == "" { log.Info("unable to get query parameter") - server.Write(w, protocols.MissingParameterError) + server.Write(w, protocols.NewMissingParameter("id")) return } - response := TransactionStatusResponse{} + response := compliance.TransactionStatusResponse{} if rh.Config.Callbacks.TxStatus == "" { - response.Status = "unknown" + response.Status = compliance.TransactionStatusUnknown } else { endpoint := fmt.Sprintf( "%s?id=%s", @@ -77,11 +69,11 @@ func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request return } if response.Status == "" { - response.Status = "unknown" + response.Status = compliance.TransactionStatusUnknown } default: - response.Status = "unknown" + response.Status = compliance.TransactionStatusUnknown } } From dc2ff029c26767383e532fe2c8e3324d4e2c9b6d Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Thu, 7 Sep 2017 15:47:39 +0100 Subject: [PATCH 10/11] escape url + other changes --- .../compliance/handlers/request_handler_tx_status.go | 10 +++++++--- .../handlers/request_handler_tx_status_test.go | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go index 2ec2a64..fa68b5c 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go @@ -33,16 +33,20 @@ func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request txid, ) - _, err := url.Parse(endpoint) + u, err := url.Parse(endpoint) if err != nil { log.Error(err, "failed to parse tx status endpoint") server.Write(w, protocols.InternalServerError) return } - resp, err := rh.Client.Get(endpoint) + + q := u.Query() + q.Set("id", txid) + u.RawQuery = q.Encode() + resp, err := rh.Client.Get(u.String()) if err != nil { log.WithFields(log.Fields{ - "tx_status": rh.Config.Callbacks.TxStatus, + "tx_status": u.String(), "err": err, }).Error("Error sending request to tx_status server") server.Write(w, protocols.InternalServerError) diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go index abbacf5..7e6384d 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status_test.go @@ -64,7 +64,7 @@ func TestRequestHandlerTxStatus(t *testing.T) { defer testServer.Close() Convey("Given tx_status request", t, func() { - Convey("it returns unathorised when no auth", func() { + Convey("it returns unauthorised when no auth", func() { testServer.GET("/tx_status"). WithQuery("id", "123"). Expect(). From 4540431128e02264890281e0965a6764b139e57b Mon Sep 17 00:00:00 2001 From: Peter Oliha Date: Fri, 8 Sep 2017 17:28:55 +0100 Subject: [PATCH 11/11] removed endpoint --- .../compliance/handlers/request_handler_tx_status.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go index fa68b5c..fe48958 100644 --- a/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go +++ b/src/github.com/stellar/gateway/compliance/handlers/request_handler_tx_status.go @@ -2,7 +2,6 @@ package handlers import ( "encoding/json" - "fmt" "io/ioutil" "net/http" "net/url" @@ -27,13 +26,8 @@ func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request if rh.Config.Callbacks.TxStatus == "" { response.Status = compliance.TransactionStatusUnknown } else { - endpoint := fmt.Sprintf( - "%s?id=%s", - rh.Config.Callbacks.TxStatus, - txid, - ) - u, err := url.Parse(endpoint) + u, err := url.Parse(rh.Config.Callbacks.TxStatus) if err != nil { log.Error(err, "failed to parse tx status endpoint") server.Write(w, protocols.InternalServerError)