Skip to content
This repository has been archived by the owner on Oct 2, 2022. It is now read-only.

Commit

Permalink
1.0.2: Adding support for www-urlencoded request body
Browse files Browse the repository at this point in the history
This release adds the ability to switch request bodies to the `www-urlencoded` encoding for the purposes of OAuth2 authentication.
  • Loading branch information
Janos Pasztor committed Apr 15, 2021
1 parent 193c837 commit 5f1a873
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.0.2: Adding support for www-urlencoded request body

This release adds the ability to switch request bodies to the `www-urlencoded` encoding for the purposes of OAuth2 authentication.

## 1.0.1: Explicitly setting the `Accept` header

This release explicitly adds the "Accept" header on client requests.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ clientConfig := http.ClientConfiguration{
// Optionally, for client authentication:
ClientCert: "Client certificate in PEM format or file name",
ClientKey: "Client key in PEM format or file name",
// Optional: switch to www-urlencoded request body
RequestEncoding: http.RequestEncodingWWWURLEncoded,
}
client, err := http.NewClient(clientConfig, logger)
if err != nil {
Expand Down
16 changes: 14 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ package http
// Client is a simplified HTTP interface that ensures that a struct is transported to a remote endpoint
// properly encoded, and the response is decoded into the response struct.
type Client interface {
Request(
Method string,
path string,
requestBody interface{},
responseBody interface{},
) (statusCode int, err error)

// Get queries the configured endpoint with the path providing the response in the responseBody structure. It
// returns the HTTP status code and any potential errors.
Get(
path string,
responseBody interface{},
) (statusCode int, err error)

// Post queries the configured endpoint with the path, sending the requestBody and providing the
// response in the responseBody structure. It returns the HTTP status code and any potential errors.
//
// The returned error is always one of ClientError
Post(
path string,
requestBody interface{},
Expand Down
71 changes: 62 additions & 9 deletions client_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"

"github.com/containerssh/log"
"github.com/gorilla/schema"
)

type client struct {
Expand All @@ -17,6 +20,24 @@ type client struct {
tlsConfig *tls.Config
}

func (c *client) Request(Method string, path string, requestBody interface{}, responseBody interface{}) (statusCode int, err error) {
return c.request(
Method,
path,
requestBody,
responseBody,
)
}

func (c *client) Get(path string, responseBody interface{}) (statusCode int, err error) {
return c.request(
http.MethodGet,
path,
nil,
responseBody,
)
}

func (c *client) Post(
path string,
requestBody interface{},
Expand Down Expand Up @@ -68,7 +89,14 @@ func (c *client) request(
resp.StatusCode,
).Label("statusCode", resp.StatusCode))

decoder := json.NewDecoder(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = log.Wrap(err, EFailureConnectionFailed, "HTTP %s request to %s%s failed", method, c.config.URL, path)
logger.Debug(err)
return 0, err
}

decoder := json.NewDecoder(bytes.NewReader(body))
decoder.DisallowUnknownFields()
if err := decoder.Decode(responseBody); err != nil {
err = log.Wrap(err, EFailureDecodeFailed, "Failed to decode HTTP response")
Expand All @@ -83,12 +111,28 @@ func (c *client) createRequest(method string, path string, requestBody interface
error,
) {
buffer := &bytes.Buffer{}
err := json.NewEncoder(buffer).Encode(requestBody)
if err != nil {
//This is a bug
err := log.Wrap(err, EFailureEncodeFailed, "BUG: HTTP request encoding failed")
logger.Debug(err)
return nil, err
switch c.config.RequestEncoding {
case RequestEncodingDefault:
fallthrough
case RequestEncodingJSON:
err := json.NewEncoder(buffer).Encode(requestBody)
if err != nil {
//This is a bug
err := log.Wrap(err, EFailureEncodeFailed, "BUG: HTTP request encoding failed")
logger.Critical(err)
return nil, err
}
case RequestEncodingWWWURLEncoded:
encoder := schema.NewEncoder()
form := url.Values{}
if err := encoder.Encode(requestBody, form); err != nil {
err := log.Wrap(err, EFailureEncodeFailed, "BUG: HTTP request encoding failed")
logger.Critical(err)
return nil, err
}
buffer.WriteString(form.Encode())
default:
panic(fmt.Errorf("invalid request encoding: %s", c.config.RequestEncoding))
}
req, err := http.NewRequest(
method,
Expand All @@ -97,10 +141,19 @@ func (c *client) createRequest(method string, path string, requestBody interface
)
if err != nil {
err := log.Wrap(err, EFailureEncodeFailed, "BUG: HTTP request encoding failed")
logger.Debug(err)
logger.Critical(err)
return nil, err
}
req.Header.Set("Content-Type", "application/json")
switch c.config.RequestEncoding {
case RequestEncodingDefault:
fallthrough
case RequestEncodingJSON:
req.Header.Set("Content-Type", "application/json")
case RequestEncodingWWWURLEncoded:
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
default:
panic(fmt.Errorf("invalid request encoding: %s", c.config.RequestEncoding))
}
req.Header.Set("Accept", "application/json")
return req, nil
}
Expand Down
33 changes: 33 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ type ClientConfiguration struct {
// CipherSuites is a list of supported cipher suites.
CipherSuites CipherSuiteList `json:"cipher" yaml:"cipher" default:"[\"TLS_AES_128_GCM_SHA256\",\"TLS_AES_256_GCM_SHA384\",\"TLS_CHACHA20_POLY1305_SHA256\",\"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\",\"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\"]"`

// RequestEncoding is the means by which the request body is encoded. It defaults to JSON encoding.
RequestEncoding RequestEncoding `json:"-" yaml:"-"`

// caCertPool is for internal use only. It contains the loaded CA certificates after Validate.
caCertPool *x509.CertPool `json:"-" yaml:"-"`

Expand All @@ -333,6 +336,10 @@ func (c *ClientConfiguration) Validate() error {
return err
}

if err := c.RequestEncoding.Validate(); err != nil {
return err
}

if strings.HasPrefix(c.URL, "https://") {
if err := c.TLSVersion.Validate(); err != nil {
return fmt.Errorf("invalid TLS version (%w)", err)
Expand Down Expand Up @@ -480,3 +487,29 @@ func (config *ServerConfiguration) Validate() error {

return nil
}

// RequestEncoding is the method by which the response is encoded.
type RequestEncoding string

// RequestEncodingJSON is the default encoding and encodes the body to JSON.
const RequestEncodingDefault = ""

// RequestEncodingJSON encodes the body to JSON.
const RequestEncodingJSON = "JSON"

// RequestEncodingWWURLEncoded encodes the body via www-urlencoded.
const RequestEncodingWWWURLEncoded = "WWW-URLENCODED"

// Validate validates the RequestEncoding
func (r RequestEncoding) Validate() error {
switch r {
case RequestEncodingDefault:
return nil
case RequestEncodingJSON:
return nil
case RequestEncodingWWWURLEncoded:
return nil
default:
return fmt.Errorf("invalid request encoding: %s", r)
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/containerssh/log v1.0.0
github.com/containerssh/service v1.0.0
github.com/containerssh/structutils v1.0.0
github.com/gorilla/schema v1.2.0
github.com/stretchr/testify v1.7.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqL
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down

0 comments on commit 5f1a873

Please sign in to comment.