3
0

Support sink signature in a path

For certain cases where we can not afford to have query params
like OAuth, sink signature can be placed at the end of the sink path
This commit is contained in:
Denis Arh 2020-05-27 13:38:41 +02:00
parent ef47a0d612
commit af428e99f9
2 changed files with 64 additions and 10 deletions

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
@ -29,10 +30,10 @@ type (
// Expect sink request to be of this method
Method string `json:"mtd,omitempty"`
// OpUsed as an identifier, no validation of request params
// Origin is as an identifier, no validation of request params
Origin string `json:"origin,omitempty"`
// If set
// Optional, signature expiration
Expires *time.Time `json:"exp,omitempty"`
// When set it enables body processing (but limits it to that size!)
@ -40,6 +41,10 @@ type (
// Acceptable content type
ContentType string `json:"ct,omitempty"`
// Should we put signature in the path (true)
// or in query string (false, default)
SignatureInPath bool `json:"-"`
}
sinkEventDispatcher interface {
@ -71,9 +76,13 @@ func (svc sink) SignURL(surp SinkRequestUrlParams) (signedURL *url.URL, err erro
var (
params []byte
sap = &sinkActionProps{sinkParams: &surp}
path = svc.GetPath()
qs = url.Values{}
)
err = func() error {
params, err = json.Marshal(surp)
if err != nil {
return SinkErrFailedToSign(sap).Wrap(err)
@ -81,11 +90,17 @@ func (svc sink) SignURL(surp SinkRequestUrlParams) (signedURL *url.URL, err erro
surp.Method = strings.ToUpper(surp.Method)
v := url.Values{}
signature := svc.signer.Sign(0, params) + SinkSignUrlParamDelimiter + base64.StdEncoding.EncodeToString(params)
v.Set(SinkSignUrlParamName, svc.signer.Sign(0, params)+SinkSignUrlParamDelimiter+base64.StdEncoding.EncodeToString(params))
if surp.SignatureInPath {
// Optional, use path for sink signature
path = fmt.Sprintf("%s/%s=%s", path, SinkSignUrlParamName, signature)
} else {
// By default put signature in query string
qs.Set(SinkSignUrlParamName, signature)
}
signedURL = &url.URL{RawQuery: v.Encode(), Path: svc.GetPath()}
signedURL = &url.URL{RawQuery: qs.Encode(), Path: path}
return nil
}()
@ -142,11 +157,26 @@ func (svc *sink) ProcessRequest(w http.ResponseWriter, r *http.Request) {
// Verifies and extracts sink request params
func (svc sink) handleRequest(r *http.Request) (*SinkRequestUrlParams, error) {
var (
srup *SinkRequestUrlParams
srup = &SinkRequestUrlParams{}
sap = &sinkActionProps{}
qs = r.URL.Query()
param string
)
param := r.URL.Query().Get(SinkSignUrlParamName)
// try to find a signature
if _, has := qs[SinkSignUrlParamName]; has {
// first, in a query string
param = r.URL.Query().Get(SinkSignUrlParamName)
} else if i := strings.Index(r.URL.Path, SinkSignUrlParamName); i > -1 {
// fallback to path, expecting signature to be at the end
// offset string index by start of signature param name, length of param name, and = char
param = r.URL.Path[i+len(SinkSignUrlParamName)+1:]
// this is more for consistency and cleaner tests
srup.SignatureInPath = true
}
if len(param) == 0 {
return nil, SinkErrMissingSignature(sap)
}
@ -165,7 +195,6 @@ func (svc sink) handleRequest(r *http.Request) (*SinkRequestUrlParams, error) {
return nil, SinkErrInvalidSignature(sap)
}
srup = &SinkRequestUrlParams{}
if err = json.Unmarshal(params, srup); err != nil {
// Impossible scenario :)
// How can we have verified signature of an invalid JSON ?!

View File

@ -34,6 +34,18 @@ func Test_sink_SignURL(t *testing.T) {
},
wantSignedURL: "/sink?__sign=d8a8c5591acb0f5f6695ab6aa4a205a7066b3bf4_eyJtdGQiOiJQT1NUIiwib3JpZ2luIjoidGVzdCIsIm1icyI6MTAyNCwiY3QiOiJwbGFpbi90ZXh0In0%3D",
},
{
name: "basic",
surp: SinkRequestUrlParams{
Method: "POST",
Origin: "test",
Expires: nil,
MaxBodySize: 1024,
ContentType: "plain/text",
SignatureInPath: true,
},
wantSignedURL: "/sink/__sign=d8a8c5591acb0f5f6695ab6aa4a205a7066b3bf4_eyJtdGQiOiJQT1NUIiwib3JpZ2luIjoidGVzdCIsIm1icyI6MTAyNCwiY3QiOiJwbGFpbi90ZXh0In0=",
},
}
)
@ -45,11 +57,15 @@ func Test_sink_SignURL(t *testing.T) {
gotSignedURL, err := svc.SignURL(tt.surp)
if (err != nil) != tt.wantErr {
t.Errorf("SignURL() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("SignURL() \n"+
" error: %v, \n"+
"wantErr: %v", err, tt.wantErr)
return
}
if gotSignedURL.String() != tt.wantSignedURL {
t.Errorf("SignURL() gotSignedURL = %v, want %v", gotSignedURL, tt.wantSignedURL)
t.Errorf("SignURL() \n"+
"gotSignedURL: %v\n"+
" want: %v", gotSignedURL, tt.wantSignedURL)
}
})
}
@ -74,6 +90,9 @@ func Test_sink_handleRequest(t *testing.T) {
signParamsExp = SinkRequestUrlParams{Expires: &time.Time{}}
signedUrlExp, _ = svc.SignURL(signParamsExp)
signParamsPath = SinkRequestUrlParams{SignatureInPath: true}
signedUrlPath, _ = svc.SignURL(signParamsPath)
)
var (
@ -148,6 +167,12 @@ func Test_sink_handleRequest(t *testing.T) {
withURL: signedUrl.String(),
wantErr: SinkErrContentLengthExceedsMaxAllowedSize(),
},
{
name: "signature in a path",
withMethod: "POST",
withURL: signedUrlPath.String(),
wantParams: &signParamsPath,
},
}
)