diff --git a/api/api_suite_test.go b/api/api_suite_test.go new file mode 100644 index 000000000..8ffd75068 --- /dev/null +++ b/api/api_suite_test.go @@ -0,0 +1,15 @@ +package api + +import ( + "testing" + + "github.com/cloudsonic/sonic-server/log" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestSubsonicApi(t *testing.T) { + log.SetLevel(log.LevelError) + RegisterFailHandler(Fail) + RunSpecs(t, "Subsonic API Suite") +} diff --git a/api/middlewares.go b/api/middlewares.go index 5cbfbeea3..739a5ecee 100644 --- a/api/middlewares.go +++ b/api/middlewares.go @@ -56,8 +56,7 @@ func authenticate(next http.Handler) http.Handler { switch { case pass != "": if strings.HasPrefix(pass, "enc:") { - e := strings.TrimPrefix(pass, "enc:") - if dec, err := hex.DecodeString(e); err == nil { + if dec, err := hex.DecodeString(pass[4:]); err == nil { pass = string(dec) } } diff --git a/api/middlewares_test.go b/api/middlewares_test.go new file mode 100644 index 000000000..247341828 --- /dev/null +++ b/api/middlewares_test.go @@ -0,0 +1,142 @@ +package api + +import ( + "fmt" + "net/http" + "net/http/httptest" + + "github.com/cloudsonic/sonic-server/conf" + "github.com/cloudsonic/sonic-server/log" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func newRequest(queryParams string) *http.Request { + r := httptest.NewRequest("get", "/ping?"+queryParams, nil) + ctx := r.Context() + return r.WithContext(log.NewContext(ctx)) +} + +var _ = Describe("Middlewares", func() { + var next *mockHandler + var w *httptest.ResponseRecorder + + BeforeEach(func() { + next = &mockHandler{} + w = httptest.NewRecorder() + }) + + Describe("CheckParams", func() { + It("passes when all required params are available", func() { + r := newRequest("u=user&v=1.15&c=test") + cp := checkRequiredParameters(next) + cp.ServeHTTP(w, r) + + Expect(next.req.Context().Value("user")).To(Equal("user")) + Expect(next.req.Context().Value("version")).To(Equal("1.15")) + Expect(next.req.Context().Value("client")).To(Equal("test")) + Expect(next.called).To(BeTrue()) + }) + + It("fails when user is missing", func() { + r := newRequest("v=1.15&c=test") + cp := checkRequiredParameters(next) + cp.ServeHTTP(w, r) + + Expect(w.Body.String()).To(ContainSubstring(`code="10"`)) + Expect(next.called).To(BeFalse()) + }) + + It("fails when version is missing", func() { + r := newRequest("u=user&c=test") + cp := checkRequiredParameters(next) + cp.ServeHTTP(w, r) + + Expect(w.Body.String()).To(ContainSubstring(`code="10"`)) + Expect(next.called).To(BeFalse()) + }) + + It("fails when client is missing", func() { + r := newRequest("u=user&v=1.15") + cp := checkRequiredParameters(next) + cp.ServeHTTP(w, r) + + Expect(w.Body.String()).To(ContainSubstring(`code="10"`)) + Expect(next.called).To(BeFalse()) + }) + }) + + Describe("Authenticate", func() { + BeforeEach(func() { + conf.Sonic.User = "admin" + conf.Sonic.Password = "wordpass" + conf.Sonic.DisableAuthentication = false + }) + + Context("Plaintext password", func() { + It("authenticates with plaintext password ", func() { + r := newRequest("u=admin&p=wordpass") + cp := authenticate(next) + cp.ServeHTTP(w, r) + + Expect(next.called).To(BeTrue()) + }) + + It("fails authentication with wrong password", func() { + r := newRequest("u=admin&p=INVALID") + cp := authenticate(next) + cp.ServeHTTP(w, r) + + Expect(w.Body.String()).To(ContainSubstring(`code="40"`)) + Expect(next.called).To(BeFalse()) + }) + }) + + Context("Encoded password", func() { + It("authenticates with simple encoded password ", func() { + r := newRequest("u=admin&p=enc:776f726470617373") + cp := authenticate(next) + cp.ServeHTTP(w, r) + + Expect(next.called).To(BeTrue()) + }) + }) + + Context("Token based authentication", func() { + It("authenticates with token based authentication", func() { + token := "23b342970e25c7928831c3317edd0b67" + salt := "retnlmjetrymazgkt" + query := fmt.Sprintf("u=admin&t=%s&s=%s", token, salt) + + r := newRequest(query) + cp := authenticate(next) + cp.ServeHTTP(w, r) + + Expect(next.called).To(BeTrue()) + }) + + It("fails if salt is missing", func() { + token := "23b342970e25c7928831c3317edd0b67" + query := fmt.Sprintf("u=admin&t=%s", token) + + r := newRequest(query) + cp := authenticate(next) + cp.ServeHTTP(w, r) + + Expect(w.Body.String()).To(ContainSubstring(`code="40"`)) + Expect(next.called).To(BeFalse()) + }) + }) + + }) +}) + +type mockHandler struct { + req *http.Request + called bool +} + +func (mh *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + mh.req = r + mh.called = true +} diff --git a/api/validation_test.go b/api/validation_test.go deleted file mode 100644 index 734f5c386..000000000 --- a/api/validation_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package api_test - -// -//import ( -// "encoding/xml" -// "fmt" -// "testing" -// -// "context" -// -// "github.com/astaxie/beego" -// "github.com/cloudsonic/sonic-server/api" -// "github.com/cloudsonic/sonic-server/api/responses" -// "github.com/cloudsonic/sonic-server/tests" -// . "github.com/smartystreets/goconvey/convey" -//) -// -//func TestCheckParams(t *testing.T) { -// tests.Init(t, false) -// -// _, w := Get("/rest/ping.view", "TestCheckParams") -// -// Convey("Subject: CheckParams\n", t, func() { -// Convey("Status code should be 200", func() { -// So(w.Code, ShouldEqual, 200) -// }) -// Convey("The errorCode should be 10", func() { -// So(w.Body.String(), ShouldContainSubstring, `error code="10" message=`) -// }) -// Convey("The status should be 'fail'", func() { -// v := responses.Subsonic{} -// xml.Unmarshal(w.Body.Bytes(), &v) -// So(v.Status, ShouldEqual, "fail") -// }) -// }) -//} -// -//func TestAuthentication(t *testing.T) { -// tests.Init(t, false) -// -// Convey("Subject: Authentication", t, func() { -// _, w := Get("/rest/ping.view?u=INVALID&p=INVALID&c=test&v=1.0.0", "TestAuthentication") -// Convey("Status code should be 200", func() { -// So(w.Code, ShouldEqual, 200) -// }) -// Convey("The errorCode should be 10", func() { -// So(w.Body.String(), ShouldContainSubstring, `error code="40" message=`) -// }) -// Convey("The status should be 'fail'", func() { -// v := responses.Subsonic{} -// xml.Unmarshal(w.Body.Bytes(), &v) -// So(v.Status, ShouldEqual, "fail") -// }) -// }) -// Convey("Subject: Authentication Valid", t, func() { -// _, w := Get("/rest/ping.view?u=deluan&p=wordpass&c=test&v=1.0.0", "TestAuthentication") -// Convey("The status should be 'ok'", func() { -// v := responses.Subsonic{} -// xml.Unmarshal(w.Body.Bytes(), &v) -// So(v.Status, ShouldEqual, "ok") -// }) -// }) -// Convey("Subject: Password encoded", t, func() { -// _, w := Get("/rest/ping.view?u=deluan&p=enc:776f726470617373&c=test&v=1.0.0", "TestAuthentication") -// Convey("The status should be 'ok'", func() { -// v := responses.Subsonic{} -// xml.Unmarshal(w.Body.Bytes(), &v) -// So(v.Status, ShouldEqual, "ok") -// }) -// }) -// Convey("Subject: Token-based authentication", t, func() { -// salt := "retnlmjetrymazgkt" -// token := "23b342970e25c7928831c3317edd0b67" -// _, w := Get(fmt.Sprintf("/rest/ping.view?u=deluan&s=%s&t=%s&c=test&v=1.0.0", salt, token), "TestAuthentication") -// Convey("The status should be 'ok'", func() { -// v := responses.Subsonic{} -// xml.Unmarshal(w.Body.Bytes(), &v) -// So(v.Status, ShouldEqual, "ok") -// }) -// }) -//} -// -//type mockController struct { -// api.BaseAPIController -//} -// -//func (c *mockController) Get() { -// actualContext = c.Ctx.Input.GetData("context").(context.Context) -// c.Ctx.WriteString("OK") -//} -// -//var actualContext context.Context -// -//func TestContext(t *testing.T) { -// tests.Init(t, false) -// log.Router(r, "/rest/mocktest", &mockController{}) -// -// Convey("Subject: Context", t, func() { -// _, w := GetWithHeader("/rest/mocktest?u=deluan&p=wordpass&c=testClient&v=1.0.0", "X-Request-Id", "123123", "TestContext") -// Convey("The status should be 'OK'", func() { -// resp := string(w.Body.Bytes()) -// So(resp, ShouldEqual, "OK") -// }) -// Convey("user should be set", func() { -// So(actualContext.Value("user"), ShouldEqual, "deluan") -// }) -// Convey("client should be set", func() { -// So(actualContext.Value("client"), ShouldEqual, "testClient") -// }) -// Convey("version should be set", func() { -// So(actualContext.Value("version"), ShouldEqual, "1.0.0") -// }) -// Convey("context should be set", func() { -// So(actualContext.Value("requestId"), ShouldEqual, "123123") -// }) -// }) -//} diff --git a/go.mod b/go.mod index c1846ea15..073cbadc1 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/kennygrant/sanitize v0.0.0-20170120101633-6a0bfdde8629 github.com/koding/multiconfig v0.0.0-20170327155832-26b6dfd3a84a github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 - github.com/onsi/ginkgo v1.11.0 // indirect - github.com/onsi/gomega v1.8.1 // indirect + github.com/onsi/ginkgo v1.11.0 + github.com/onsi/gomega v1.8.1 github.com/siddontang/go v0.0.0-20161005110831-1e9ce2a5ac40 // indirect github.com/siddontang/ledisdb v0.0.0-20170318061737-5929802e2ea5 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect