From 963a5ccb859c844bb9db2b9501497730f62d0013 Mon Sep 17 00:00:00 2001 From: Franz Auernigg Date: Tue, 30 Nov 2021 18:01:06 +0100 Subject: [PATCH 1/2] Add http method option. --- README.md | 4 +++- backends/http.go | 19 +++++++++++++++++-- backends/jwt_remote.go | 12 ++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cf26d50..4a19bf5 100644 --- a/README.md +++ b/README.md @@ -892,8 +892,9 @@ The following `auth_opt_` options are supported by the `jwt` backend when remote | jwt_response_mode | status | N | Response type (status, json, text) | | jwt_params_mode | json | N | Data type (json, form) | | jwt_user_agent | mosquitto | N | User agent for requests | +| jwt_http_method | POST | N | Http method used (POST, GET, PUT) | -URIs (like jwt_getuser_uri) are expected to be in the form `/path`. For example, if jwt_with_tls is `false`, jwt_host is `localhost`, jwt_port `3000` and jwt_getuser_uri is `/user`, mosquitto will send a POST request to `http://localhost:3000/user` to get a response to check against. How data is sent (either json encoded or as form values) and received (as a simple http status code, a json encoded response or plain text), is given by options jwt_response_mode and jwt_params_mode. +URIs (like jwt_getuser_uri) are expected to be in the form `/path`. For example, if jwt_with_tls is `false`, jwt_host is `localhost`, jwt_port `3000` and jwt_getuser_uri is `/user`, mosquitto will send a http request to `http://localhost:3000/user` to get a response to check against. How data is sent (either json encoded or as form values) and received (as a simple http status code, a json encoded response or plain text), is given by options jwt_response_mode and jwt_params_mode. If the option `jwt_superuser_uri` is not set then `superuser` checks are disabled for this mode. @@ -1144,6 +1145,7 @@ The following `auth_opt_` options are supported: | http_params_mode | json | N | Data type (json, form) | | http_timeout | 5 | N | Timeout in seconds | | http_user_agent | mosquitto | N | User Agent to use in requests | +| http_method | POST | N | Http method used (POST, GET, PUT) | #### Response mode diff --git a/backends/http.go b/backends/http.go index e5b21fa..63c1509 100644 --- a/backends/http.go +++ b/backends/http.go @@ -25,6 +25,7 @@ type HTTP struct { WithTLS bool VerifyPeer bool ParamsMode string + httpMethod string ResponseMode string Timeout int Client *h.Client @@ -45,6 +46,7 @@ func NewHTTP(authOpts map[string]string, logLevel log.Level, version string) (HT VerifyPeer: false, ResponseMode: "status", ParamsMode: "json", + httpMethod: "POST", } missingOpts := "" @@ -62,6 +64,12 @@ func NewHTTP(authOpts map[string]string, logLevel log.Level, version string) (HT } } + if httpMethod, ok := authOpts["http_method"]; ok { + if httpMethod == "POST" || httpMethod == "GET" || httpMethod == "PUT" { + http.httpMethod = httpMethod + } + } + if userUri, ok := authOpts["http_getuser_uri"]; ok { http.UserUri = userUri } else { @@ -210,6 +218,13 @@ func (o HTTP) httpRequest(uri, username string, dataMap map[string]interface{}, var err error if o.ParamsMode == "form" { + if o.httpMethod != "POST" { + log.Errorf("error form param only supported for POST.") + err = fmt.Errorf("form only supported for POST, error code: %d", + 500) + return false, err + } + resp, err = o.Client.PostForm(fullUri, urlValues) } else { var dataJson []byte @@ -222,7 +237,7 @@ func (o HTTP) httpRequest(uri, username string, dataMap map[string]interface{}, contentReader := bytes.NewReader(dataJson) var req *h.Request - req, err = h.NewRequest("POST", fullUri, contentReader) + req, err = h.NewRequest(o.httpMethod, fullUri, contentReader) if err != nil { log.Errorf("req error: %s", err) @@ -236,7 +251,7 @@ func (o HTTP) httpRequest(uri, username string, dataMap map[string]interface{}, } if err != nil { - log.Errorf("POST error: %s", err) + log.Errorf("http request error: %s", err) return false, err } diff --git a/backends/jwt_remote.go b/backends/jwt_remote.go index 744c3a2..d1e857c 100644 --- a/backends/jwt_remote.go +++ b/backends/jwt_remote.go @@ -27,6 +27,7 @@ type remoteJWTChecker struct { verifyPeer bool paramsMode string + httpMethod string responseMode string options tokenOptions @@ -45,6 +46,7 @@ func NewRemoteJWTChecker(authOpts map[string]string, options tokenOptions, versi verifyPeer: false, responseMode: "status", paramsMode: "json", + httpMethod: "POST", options: options, } @@ -63,6 +65,12 @@ func NewRemoteJWTChecker(authOpts map[string]string, options tokenOptions, versi } } + if httpMethod, ok := authOpts["jwt_http_method"]; ok { + if httpMethod == "POST" || httpMethod == "GET" || httpMethod == "PUT" { + checker.httpMethod = httpMethod + } + } + if userUri, ok := authOpts["jwt_getuser_uri"]; ok { checker.userUri = userUri } else { @@ -239,7 +247,7 @@ func (o *remoteJWTChecker) jwtRequest(host, uri, token string, dataMap map[strin } contentReader := bytes.NewReader(dataJSON) - req, err = h.NewRequest("POST", fullURI, contentReader) + req, err = h.NewRequest(o.httpMethod, fullURI, contentReader) if err != nil { log.Errorf("req error: %s", err) @@ -248,7 +256,7 @@ func (o *remoteJWTChecker) jwtRequest(host, uri, token string, dataMap map[strin req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", o.userAgent) default: - req, err = h.NewRequest("POST", fullURI, strings.NewReader(urlValues.Encode())) + req, err = h.NewRequest(o.httpMethod, fullURI, strings.NewReader(urlValues.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Length", strconv.Itoa(len(urlValues.Encode()))) req.Header.Set("User-Agent", o.userAgent) From 065ec97ee3749a81810e0e264fc9e5e632386640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20G=C3=B3mez?= Date: Sun, 5 Jun 2022 18:39:10 -0400 Subject: [PATCH 2/2] Use predefined http constants. Add tests to http backend. --- backends/http.go | 12 ++++++------ backends/http_test.go | 29 +++++++++++++++++++++++++++++ backends/jwt_remote.go | 5 +++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/backends/http.go b/backends/http.go index 63c1509..13257e0 100644 --- a/backends/http.go +++ b/backends/http.go @@ -46,7 +46,7 @@ func NewHTTP(authOpts map[string]string, logLevel log.Level, version string) (HT VerifyPeer: false, ResponseMode: "status", ParamsMode: "json", - httpMethod: "POST", + httpMethod: h.MethodPost, } missingOpts := "" @@ -65,7 +65,8 @@ func NewHTTP(authOpts map[string]string, logLevel log.Level, version string) (HT } if httpMethod, ok := authOpts["http_method"]; ok { - if httpMethod == "POST" || httpMethod == "GET" || httpMethod == "PUT" { + switch httpMethod { + case h.MethodGet, h.MethodPut: http.httpMethod = httpMethod } } @@ -218,10 +219,9 @@ func (o HTTP) httpRequest(uri, username string, dataMap map[string]interface{}, var err error if o.ParamsMode == "form" { - if o.httpMethod != "POST" { - log.Errorf("error form param only supported for POST.") - err = fmt.Errorf("form only supported for POST, error code: %d", - 500) + if o.httpMethod != h.MethodPost && o.httpMethod != h.MethodPut { + log.Errorf("error form param only supported for POST/PUT.") + err = fmt.Errorf("form only supported for POST/PUT, error code: %d", 500) return false, err } diff --git a/backends/http_test.go b/backends/http_test.go index e007e0d..6a3fa56 100644 --- a/backends/http_test.go +++ b/backends/http_test.go @@ -103,6 +103,7 @@ func TestHTTPAllJsonServer(t *testing.T) { hb, err := NewHTTP(authOpts, log.DebugLevel, version) So(err, ShouldBeNil) So(hb.UserAgent, ShouldEqual, "mosquitto-2.0.0") + So(hb.httpMethod, ShouldEqual, http.MethodPost) Convey("Given custom user agent, it should override default one", func() { customAuthOpts := make(map[string]string) @@ -118,6 +119,34 @@ func TestHTTPAllJsonServer(t *testing.T) { So(customHb.UserAgent, ShouldEqual, "custom-user-agent") }) + Convey("Given http method GET, it should override the default POST one", func() { + customAuthOpts := make(map[string]string) + + for k, v := range authOpts { + customAuthOpts[k] = v + } + + customAuthOpts["http_method"] = "GET" + + customHb, err := NewHTTP(customAuthOpts, log.DebugLevel, version) + So(err, ShouldBeNil) + So(customHb.httpMethod, ShouldEqual, http.MethodGet) + }) + + Convey("Given http method PUT, it should override the default POST one", func() { + customAuthOpts := make(map[string]string) + + for k, v := range authOpts { + customAuthOpts[k] = v + } + + customAuthOpts["http_method"] = "PUT" + + customHb, err := NewHTTP(customAuthOpts, log.DebugLevel, version) + So(err, ShouldBeNil) + So(customHb.httpMethod, ShouldEqual, http.MethodPut) + }) + Convey("Given correct password/username, get user should return true", func() { authenticated, err := hb.GetUser(username, password, clientId) diff --git a/backends/jwt_remote.go b/backends/jwt_remote.go index d1e857c..f59aa69 100644 --- a/backends/jwt_remote.go +++ b/backends/jwt_remote.go @@ -46,7 +46,7 @@ func NewRemoteJWTChecker(authOpts map[string]string, options tokenOptions, versi verifyPeer: false, responseMode: "status", paramsMode: "json", - httpMethod: "POST", + httpMethod: h.MethodPost, options: options, } @@ -66,7 +66,8 @@ func NewRemoteJWTChecker(authOpts map[string]string, options tokenOptions, versi } if httpMethod, ok := authOpts["jwt_http_method"]; ok { - if httpMethod == "POST" || httpMethod == "GET" || httpMethod == "PUT" { + switch httpMethod { + case h.MethodGet, h.MethodPut: checker.httpMethod = httpMethod } }