diff --git a/api/validation.go b/api/validation.go index 31c1b4d9..d4dd068e 100644 --- a/api/validation.go +++ b/api/validation.go @@ -1,10 +1,10 @@ package api import ( + "crypto/md5" "encoding/hex" - "strings" - "fmt" + "strings" "github.com/astaxie/beego" "github.com/deluan/gosonic/api/responses" @@ -25,7 +25,7 @@ func Validate(controller BaseAPIController) { } func checkParameters(c BaseAPIController) { - requiredParameters := []string{"u", "p", "v", "c"} + requiredParameters := []string{"u", "v", "c"} for _, p := range requiredParameters { if c.GetString(p) == "" { @@ -33,18 +33,35 @@ func checkParameters(c BaseAPIController) { abortRequest(c, responses.ErrorMissingParameter) } } + + if c.GetString("p") == "" && (c.GetString("s") == "" || c.GetString("t") == "") { + logWarn(c, "Missing authentication information") + } } func authenticate(c BaseAPIController) { + password := beego.AppConfig.String("password") user := c.GetString("u") pass := c.GetString("p") - if strings.HasPrefix(pass, "enc:") { - e := strings.TrimPrefix(pass, "enc:") - if dec, err := hex.DecodeString(e); err == nil { - pass = string(dec) + salt := c.GetString("s") + token := c.GetString("t") + valid := false + + switch { + case pass != "": + if strings.HasPrefix(pass, "enc:") { + e := strings.TrimPrefix(pass, "enc:") + if dec, err := hex.DecodeString(e); err == nil { + pass = string(dec) + } } + valid = (pass == password) + case token != "": + t := fmt.Sprintf("%x", md5.Sum([]byte(password+salt))) + valid = (t == token) } - if user != beego.AppConfig.String("user") || pass != beego.AppConfig.String("password") { + + if user != beego.AppConfig.String("user") || !valid { logWarn(c, fmt.Sprintf(`Invalid login for user "%s"`, user)) abortRequest(c, responses.ErrorAuthenticationFail) } diff --git a/api/validation_test.go b/api/validation_test.go index 0bc204af..6689c785 100644 --- a/api/validation_test.go +++ b/api/validation_test.go @@ -2,6 +2,7 @@ package api_test import ( "encoding/xml" + "fmt" "testing" "github.com/deluan/gosonic/api/responses" @@ -32,7 +33,7 @@ func TestCheckParams(t *testing.T) { func TestAuthentication(t *testing.T) { tests.Init(t, false) - Convey("Subject: Authentication\n", t, func() { + 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) @@ -46,7 +47,7 @@ func TestAuthentication(t *testing.T) { So(v.Status, ShouldEqual, "fail") }) }) - Convey("Subject: Authentication Valid\n", t, func() { + 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{} @@ -54,7 +55,7 @@ func TestAuthentication(t *testing.T) { So(v.Status, ShouldEqual, "ok") }) }) - Convey("Subject: Password encoded\n", t, func() { + 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{} @@ -62,4 +63,14 @@ func TestAuthentication(t *testing.T) { 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") + }) + }) }