diff --git a/conf/app.conf b/conf/app.conf index ae58699a9..5223d5ef5 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -1,12 +1,21 @@ appname = github.com/deluan/gosonic -httpport = 8080 -runmode = dev -autorender = false -copyrequestbody = true +httpPort = 8080 +runMode = dev +autoRender = false +copyRequestBody = true -apiversion = 1.0.0 -musicfolder=. +apiVersion = 1.0.0 +musicFolder=. +user=deluan +password=wordpass [dev] -enableadmin = true -indexpath = ./gosonic.index \ No newline at end of file +disableValidation = true +enableAdmin = true +indexPath = ./gosonic.index + +[test] +disableValidation = false +enableAdmin = false +user=deluan +password=wordpass diff --git a/controllers/get_license.go b/controllers/get_license.go index 142212f7f..a998dad41 100644 --- a/controllers/get_license.go +++ b/controllers/get_license.go @@ -9,6 +9,7 @@ type GetLicenseController struct{ beego.Controller } // @router /rest/getLicense.view [get] func (this *GetLicenseController) Get() { + validate(this) response := responses.NewXML(&responses.License{Valid: true}) this.Ctx.Output.Body(response) } diff --git a/controllers/get_music_folders.go b/controllers/get_music_folders.go index 2d3293679..3863b0d90 100644 --- a/controllers/get_music_folders.go +++ b/controllers/get_music_folders.go @@ -1 +1,18 @@ package controllers + +import ( + "github.com/astaxie/beego" + "github.com/deluan/gosonic/controllers/responses" +) + +type GetMusicFoldersController struct{ beego.Controller } + +// @router /rest/getMusicFolders.view [get] +func (this *GetMusicFoldersController) Get() { + validate(this) + response := responses.NewError(responses.ERROR_GENERIC) + this.Ctx.Output.Body(response) +} + + + diff --git a/controllers/ping.go b/controllers/ping.go index df2098ce4..a80b1b945 100644 --- a/controllers/ping.go +++ b/controllers/ping.go @@ -10,6 +10,7 @@ type PingController struct{ beego.Controller } // @router /rest/ping.view [get] func (this *PingController) Get() { + validate(this) response := responses.NewEmpty() xmlBody, _ := xml.Marshal(response) this.Ctx.Output.Body([]byte(xml.Header + string(xmlBody))) diff --git a/controllers/responses/error.go b/controllers/responses/error.go new file mode 100644 index 000000000..b1b872331 --- /dev/null +++ b/controllers/responses/error.go @@ -0,0 +1,50 @@ +package responses + +import ( + "encoding/xml" +) + +const ( + ERROR_GENERIC = iota * 10 + ERROR_MISSING_PARAMETER + ERROR_CLIENT_TOO_OLD + ERROR_SERVER_TOO_OLD + ERROR_AUTHENTICATION_FAIL + ERROR_AUTHORIZATION_FAIL + ERROR_TRIAL_EXPIRED + ERROR_DATA_NOT_FOUND +) + +var ( + errors map[int]string +) + +func init() { + errors = make(map[int]string) + errors[ERROR_GENERIC] = "A generic error" + errors[ERROR_MISSING_PARAMETER] = "Required parameter is missing" + errors[ERROR_CLIENT_TOO_OLD] = "Incompatible Subsonic REST protocol version. Client must upgrade" + errors[ERROR_SERVER_TOO_OLD] = "Incompatible Subsonic REST protocol version. Server must upgrade" + errors[ERROR_AUTHENTICATION_FAIL] = "Wrong username or password" + errors[ERROR_AUTHORIZATION_FAIL] = "User is not authorized for the given operation" + errors[ERROR_TRIAL_EXPIRED] = "The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium. Visit subsonic.org for details" + errors[ERROR_DATA_NOT_FOUND] = "The requested data was not found" +} + +type error struct { + XMLName xml.Name`xml:"error"` + Code int `xml:"code,attr"` + Message string `xml:"message,attr"` +} + +func NewError(errorCode int) []byte { + response := NewEmpty() + response.Status = "fail" + if errors[errorCode] == "" { + errorCode = ERROR_GENERIC + } + xmlBody, _ := xml.Marshal(&error{Code: errorCode, Message: errors[errorCode]}) + response.Body = xmlBody + xmlResponse, _ := xml.Marshal(response) + return []byte(xml.Header + string(xmlResponse)) +} \ No newline at end of file diff --git a/controllers/responses/subsonic.go b/controllers/responses/subsonic.go index 4ea5a2c04..f0c98c05c 100644 --- a/controllers/responses/subsonic.go +++ b/controllers/responses/subsonic.go @@ -13,7 +13,7 @@ type Subsonic struct { } func NewEmpty() Subsonic { - return Subsonic{Status: "ok", Version: beego.AppConfig.String("apiversion")} + return Subsonic{Status: "ok", Version: beego.AppConfig.String("apiVersion")} } func NewXML(body interface{}) []byte { diff --git a/controllers/validation.go b/controllers/validation.go new file mode 100644 index 000000000..a04e6b2c9 --- /dev/null +++ b/controllers/validation.go @@ -0,0 +1,40 @@ +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/deluan/gosonic/controllers/responses" +) + +type ControllerInterface interface { + GetString(key string, def ...string) string + CustomAbort(status int, body string) +} + +func validate(controller ControllerInterface) { + if beego.AppConfig.String("disableValidation") != "true" { + checkParameters(controller) + authenticate(controller) + } +} + +func checkParameters(c ControllerInterface) { + requiredParameters := []string {"u", "p", "v", "c",} + + for _,p := range requiredParameters { + if c.GetString(p) == "" { + cancel(c, responses.ERROR_MISSING_PARAMETER) + } + } +} + +func authenticate(c ControllerInterface) { + user := c.GetString("u") + pass := c.GetString("p") + if (user != beego.AppConfig.String("user") || pass != beego.AppConfig.String("password")) { + cancel(c, responses.ERROR_AUTHENTICATION_FAIL) + } +} + +func cancel(c ControllerInterface, code int) { + c.CustomAbort(200, string(responses.NewError(code))) +} \ No newline at end of file diff --git a/repositories/media_file_repository.go b/repositories/media_file_repository.go index 54a331956..df5e8c351 100644 --- a/repositories/media_file_repository.go +++ b/repositories/media_file_repository.go @@ -1,6 +1,6 @@ package repositories -import "github.com/deluan/gosonic/models" +//import "github.com/deluan/gosonic/models" // //func AddMediaFile(m models.MediaFile) string { // m.ID = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10) diff --git a/repositories/media_folders_repository.go b/repositories/media_folders_repository.go new file mode 100644 index 000000000..c7a835b14 --- /dev/null +++ b/repositories/media_folders_repository.go @@ -0,0 +1,3 @@ +package repositories + + diff --git a/routers/router.go b/routers/router.go index eff9faae1..cc1c000a9 100644 --- a/routers/router.go +++ b/routers/router.go @@ -17,5 +17,6 @@ func init() { beego.Include( &controllers.PingController{}, &controllers.GetLicenseController{}, + &controllers.GetMusicFoldersController{}, ) } diff --git a/tests/controllers/get_license_test.go b/tests/controllers/get_license_test.go index c2cd35c54..0568e69fd 100644 --- a/tests/controllers/get_license_test.go +++ b/tests/controllers/get_license_test.go @@ -12,6 +12,7 @@ import ( . "github.com/smartystreets/goconvey/convey" "encoding/xml" "fmt" + "github.com/deluan/gosonic/tests" ) func init() { @@ -22,7 +23,7 @@ func init() { // TestGet is a sample to run an endpoint test func TestGetLicense(t *testing.T) { - r, _ := http.NewRequest("GET", "/rest/getLicense.view", nil) + r, _ := http.NewRequest("GET", test.AddParams("/rest/getLicense.view"), nil) w := httptest.NewRecorder() beego.BeeApp.Handlers.ServeHTTP(w, r) diff --git a/tests/controllers/ping_test.go b/tests/controllers/ping_test.go index d8ff0c8ec..0ca26ca5a 100644 --- a/tests/controllers/ping_test.go +++ b/tests/controllers/ping_test.go @@ -12,6 +12,7 @@ import ( . "github.com/smartystreets/goconvey/convey" "fmt" "github.com/deluan/gosonic/controllers/responses" + "github.com/deluan/gosonic/tests" ) func init() { @@ -20,13 +21,12 @@ func init() { beego.TestBeegoInit(appPath) } -// TestGet is a sample to run an endpoint test func TestPing(t *testing.T) { - r, _ := http.NewRequest("GET", "/rest/ping.view", nil) + r, _ := http.NewRequest("GET", test.AddParams("/rest/ping.view"), nil) w := httptest.NewRecorder() beego.BeeApp.Handlers.ServeHTTP(w, r) - beego.Trace("testing", "TestPing", fmt.Sprintf("Code[%d]\n%s", w.Code, w.Body.String())) + beego.Trace("testing", "TestPing", fmt.Sprintf("\nUrl: %s\n\nCode[%d]\n%s", r.URL, w.Code, w.Body.String())) Convey("Subject: Ping Endpoint\n", t, func() { Convey("Status code should be 200", func() { diff --git a/tests/controllers/validation_test.go b/tests/controllers/validation_test.go new file mode 100644 index 000000000..76f3ce40b --- /dev/null +++ b/tests/controllers/validation_test.go @@ -0,0 +1,67 @@ +package test + +import ( + "net/http" + "net/http/httptest" + "testing" + "runtime" + "encoding/xml" + "path/filepath" + _ "github.com/deluan/gosonic/routers" + "github.com/astaxie/beego" + . "github.com/smartystreets/goconvey/convey" + "fmt" + "github.com/deluan/gosonic/controllers/responses" +) + +func init() { + _, file, _, _ := runtime.Caller(1) + appPath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, "../.." + string(filepath.Separator)))) + beego.TestBeegoInit(appPath) +} + +func TestCheckParams(t *testing.T) { + r, _ := http.NewRequest("GET", "/rest/ping.view", nil) + w := httptest.NewRecorder() + beego.BeeApp.Handlers.ServeHTTP(w, r) + + beego.Trace("testing", "TestCheckParams", fmt.Sprintf("\nUrl: %s\n\nCode[%d]\n%s", r.URL, w.Code, w.Body.String())) + + Convey("Subject: Validation\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) { + r, _ := http.NewRequest("GET", "/rest/ping.view?u=INVALID&p=INVALID&c=test&v=1.0.0", nil) + w := httptest.NewRecorder() + beego.BeeApp.Handlers.ServeHTTP(w, r) + + beego.Trace("testing", "TestCheckParams", fmt.Sprintf("\nUrl: %s\n\nCode[%d]\n%s", r.URL, w.Code, w.Body.String())) + + Convey("Subject: Validation\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="40" message=`) + }) + Convey("The status should be 'fail'", func() { + v := responses.Subsonic{} + xml.Unmarshal(w.Body.Bytes(), &v) + So(v.Status, ShouldEqual, "fail") + }) + }) +} + + diff --git a/tests/test_helper.go b/tests/test_helper.go new file mode 100644 index 000000000..0c2c3dfe0 --- /dev/null +++ b/tests/test_helper.go @@ -0,0 +1,16 @@ +package test + +import "fmt" + +const ( + testUser = "deluan" + testPassword = "wordpass" + testClient = "test" + testVersion = "1.0.0" +) + +func AddParams(url string) string { + return fmt.Sprintf("%s?u=%s&p=%s&c=%s&v=%s", url, testUser, testPassword, testClient, testVersion) +} + +