From 18288b7c16553f80613458c10dc3981adc62b4dd Mon Sep 17 00:00:00 2001 From: Christian Som Date: Fri, 16 Aug 2019 14:10:15 +0200 Subject: [PATCH 1/7] add requestsigner --- .../mediatype/mediatype_test.go | 3 +- idp/authmiddleware.go | 6 +- idp/authmiddleware_test.go | 97 +++++----- idp/scim/user_test.go | 3 +- lambda/server_test.go | 4 +- log/log_test.go | 3 +- log/syslog/syslog_test.go | 3 +- requestid/requestidmiddleware.go | 3 +- requestid/requestidmiddleware_test.go | 3 +- requestlog/requestlogmiddleware_test.go | 3 +- requestsigner/go.mod | 3 + requestsigner/requestsigner.go | 173 ++++++++++++++++++ requestsigner/requestsigner_test.go | 95 ++++++++++ tenant/tenantmiddleware_test.go | 3 +- 14 files changed, 343 insertions(+), 59 deletions(-) create mode 100644 requestsigner/go.mod create mode 100644 requestsigner/requestsigner.go create mode 100644 requestsigner/requestsigner_test.go diff --git a/contentnegotiation/mediatype/mediatype_test.go b/contentnegotiation/mediatype/mediatype_test.go index 37e725a..c9db658 100644 --- a/contentnegotiation/mediatype/mediatype_test.go +++ b/contentnegotiation/mediatype/mediatype_test.go @@ -1,8 +1,9 @@ package mediatype_test import ( - "github.com/d-velop/dvelop-sdk-go/contentnegotiation/mediatype" "testing" + + "github.com/d-velop/dvelop-sdk-go/contentnegotiation/mediatype" ) func TestRequestedTypeNotSupported_ReturnsErrorNotSupported(t *testing.T) { diff --git a/idp/authmiddleware.go b/idp/authmiddleware.go index 49df9b8..2a140a6 100644 --- a/idp/authmiddleware.go +++ b/idp/authmiddleware.go @@ -6,16 +6,18 @@ import ( "encoding/json" "errors" "fmt" - "github.com/d-velop/dvelop-sdk-go/idp/scim" "io/ioutil" "net/http" "net/url" "regexp" "time" - "github.com/patrickmn/go-cache" + "github.com/d-velop/dvelop-sdk-go/idp/scim" + "strconv" "strings" + + "github.com/patrickmn/go-cache" ) type contextKey string diff --git a/idp/authmiddleware_test.go b/idp/authmiddleware_test.go index 801391c..65a0296 100644 --- a/idp/authmiddleware_test.go +++ b/idp/authmiddleware_test.go @@ -5,14 +5,15 @@ import ( "encoding/json" "errors" "fmt" - "github.com/d-velop/dvelop-sdk-go/idp" - "github.com/d-velop/dvelop-sdk-go/idp/scim" "net/http" "net/http/httptest" "reflect" "regexp" "testing" + "github.com/d-velop/dvelop-sdk-go/idp" + "github.com/d-velop/dvelop-sdk-go/idp/scim" + "time" ) @@ -25,7 +26,7 @@ func TestGetRequestWithFalseAuthorizationType_RedirectsToIdp(t *testing.T) { req.Header.Set("Authorization", "Basic adadbk") handlerSpy := handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(&handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(&handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusFound); err != nil { t.Error(err) @@ -47,7 +48,7 @@ func TestGetRequestWithFalseCookie_RedirectsToIdp(t *testing.T) { req.AddCookie(&http.Cookie{Name: "AnyCookie", Value: "adadbk"}) handlerSpy := handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(&handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(&handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusFound); err != nil { t.Error(err) @@ -67,7 +68,7 @@ func TestHeadRequestWithoutAuthorizationInfos_RedirectsToIdp(t *testing.T) { } responseSpy := responseSpy{httptest.NewRecorder()} handlerSpy := &handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusFound); err != nil { t.Error(err) @@ -87,7 +88,7 @@ func TestPostRequestWithoutAuthorizationInfos_ReturnsStatus401(t *testing.T) { } responseSpy := responseSpy{httptest.NewRecorder()} handlerSpy := &handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusUnauthorized); err != nil { t.Error(err) @@ -107,7 +108,7 @@ func TestPutRequestWithoutAuthorizationInfos_ReturnsStatus401(t *testing.T) { } responseSpy := responseSpy{httptest.NewRecorder()} handlerSpy := &handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusUnauthorized); err != nil { t.Error(err) @@ -127,7 +128,7 @@ func TestDeleteRequestWithoutAuthorizationInfos_ReturnsStatus401(t *testing.T) { } responseSpy := responseSpy{httptest.NewRecorder()} handlerSpy := &handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusUnauthorized); err != nil { t.Error(err) @@ -147,7 +148,7 @@ func TestPatchRequestWithoutAuthorizationInfos_ReturnsStatus401(t *testing.T) { } responseSpy := responseSpy{httptest.NewRecorder()} handlerSpy := &handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(http.StatusUnauthorized); err != nil { t.Error(err) @@ -173,10 +174,10 @@ func TestRequestWithBearerAuthorization_PopulatesContextWithPrincipalAndAuthsess principal := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e1"} req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if err := handlerSpy.assertAuthSessionIdIs(authSessionId); err != nil { t.Error(err) @@ -195,10 +196,10 @@ func TestRequestWithLowerCaseBearerAuthorization_PopulatesContextWithPrincipalAn principal := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e2"} req.Header.Set("Authorization", "bearer "+authSessionId) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if err := handlerSpy.assertAuthSessionIdIs(authSessionId); err != nil { t.Error(err) @@ -218,10 +219,10 @@ func TestRequestWithAuthSessionIdCookie_PopulatesContextWithPrincipalAndAuthsess principal := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e3"} req.AddCookie(&http.Cookie{Name: "AuthSessionId", Value: base64EncodedAuthSessionId}) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if err := handlerSpy.assertAuthSessionIdIs(authSessionId); err != nil { t.Error(err) @@ -241,11 +242,11 @@ func TestRequestWithBadUrlEncodedAuthSessionIdCookie_ReturnsStatus500(t *testing principal := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e3"} req.AddCookie(&http.Cookie{Name: "AuthSessionId", Value: base64EncodedAuthSessionId}) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),false, log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusInternalServerError); err != nil { t.Error(err) @@ -266,10 +267,10 @@ func TestRequestWithBearerTokenAndCookie_PopulatesContextUsingBearerToken(t *tes req.AddCookie(&http.Cookie{Name: "AuthSessionId", Value: cookieValue}) req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if err := handlerSpy.assertAuthSessionIdIs(authSessionId); err != nil { t.Error(err) @@ -295,7 +296,7 @@ func TestRequestWithBadTokenAndExternalValidationIsNotAllowed_RedirectsToIdp(t * defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),false, log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusFound); err != nil { t.Error(err) @@ -324,7 +325,7 @@ func TestRequestWithBadTokenAndExternalValidationIsAllowed_RedirectsToIdp(t *tes defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),true, log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), true, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusFound); err != nil { t.Error(err) @@ -346,11 +347,11 @@ func TestGetSystemBaseUriFromCtxReturnsError_ReturnsStatus500(t *testing.T) { principal := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e4"} req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(func(ctx context.Context) (string, error) { return "", errors.New("any error") }, returnFromCtx("1"),false, log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(func(ctx context.Context) (string, error) { return "", errors.New("any error") }, returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusInternalServerError); err != nil { t.Error(err) @@ -369,11 +370,11 @@ func TestGetTenantIdFromCtxReturnsError_ReturnsStatus500(t *testing.T) { principal := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e4"} req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := handlerSpy{} - idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), func(ctx context.Context) (string, error) { return "", errors.New("any error") }, false,log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), func(ctx context.Context) (string, error) { return "", errors.New("any error") }, false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusInternalServerError); err != nil { t.Error(err) @@ -398,7 +399,7 @@ func TestIdPReturnsStatus500_ReturnsStatus500(t *testing.T) { defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusInternalServerError); err != nil { t.Error(err) @@ -424,7 +425,7 @@ func TestIdPReturnsMalformedJson_ReturnsStatus500(t *testing.T) { defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusInternalServerError); err != nil { t.Error(err) @@ -450,7 +451,7 @@ func TestIdPReturnsPrincipalWithEmptyId_ReturnsStatus500(t *testing.T) { defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(spy, req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(spy, req) if err := spy.assertStatusCodeIs(http.StatusInternalServerError); err != nil { t.Error(err) @@ -479,9 +480,9 @@ func TestUserIsCachedAndCacheEntryIsNotExpired_ReturnsCachedEntry(t *testing.T) })) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) time.Sleep(1 * time.Nanosecond) - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if idpCalled != 1 { t.Errorf("IdP has been called %v times but expected %v times", idpCalled, 1) @@ -510,9 +511,9 @@ func TestUserIsCachedButCacheEntryIsExpired_CallsIdp(t *testing.T) { })) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) time.Sleep(1 * time.Second) - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if idpCalled != 2 { t.Errorf("IdP has been called %v times but expected %v times", idpCalled, 2) @@ -531,10 +532,10 @@ func TestUserIsCachedForDifferentTenant_CallsIdp(t *testing.T) { } principalT1 := scim.Principal{Id: "9bbbf1b6-017a-449a-ad5f-9723d28223e7"} reqTenant1.Header.Set("Authorization", "Bearer "+authSessionId) - idpStub1 := newIdpStub(map[string]scim.Principal{authSessionId: principalT1},nil) + idpStub1 := newIdpStub(map[string]scim.Principal{authSessionId: principalT1}, nil) defer idpStub1.Close() handlerSpy1 := handlerSpy{} - idp.HandleAuth(returnFromCtx(idpStub1.URL), returnFromCtx("1"), false,log, log)(&handlerSpy1).ServeHTTP(httptest.NewRecorder(), reqTenant1) + idp.HandleAuth(returnFromCtx(idpStub1.URL), returnFromCtx("1"), false, log, log)(&handlerSpy1).ServeHTTP(httptest.NewRecorder(), reqTenant1) if err := handlerSpy1.assertPrincipalIs(principalT1); err != nil { t.Error(err) @@ -546,17 +547,17 @@ func TestUserIsCachedForDifferentTenant_CallsIdp(t *testing.T) { } principalT2 := scim.Principal{Id: "0bbbf1b6-017a-449a-ad5f-9723d28223e7"} reqTenant2.Header.Set("Authorization", "Bearer "+authSessionId) - idpStub2 := newIdpStub(map[string]scim.Principal{authSessionId: principalT2},nil) + idpStub2 := newIdpStub(map[string]scim.Principal{authSessionId: principalT2}, nil) defer idpStub2.Close() handlerSpy2 := handlerSpy{} - idp.HandleAuth(returnFromCtx(idpStub2.URL), returnFromCtx("2"), false,log, log)(&handlerSpy2).ServeHTTP(httptest.NewRecorder(), reqTenant2) + idp.HandleAuth(returnFromCtx(idpStub2.URL), returnFromCtx("2"), false, log, log)(&handlerSpy2).ServeHTTP(httptest.NewRecorder(), reqTenant2) if err := handlerSpy2.assertPrincipalIs(principalT2); err != nil { t.Error(err) } } -func TestRequestAsExternalUserAndExternalUserValidationIsNotAllowed_ReturnsStatus403(t *testing.T){ +func TestRequestAsExternalUserAndExternalUserValidationIsNotAllowed_ReturnsStatus403(t *testing.T) { req, err := http.NewRequest("GET", "/myresource/subresource?query1=abc&query2=123", nil) if err != nil { t.Fatal(err) @@ -564,7 +565,7 @@ func TestRequestAsExternalUserAndExternalUserValidationIsNotAllowed_ReturnsStatu const authSessionId = "hXGxJeb0q+/fS8biFi8FE7TovJPPEPyzlDxT6bh5p5pHA/x7CEi1w9egVhEMz8IWhrtvJRFnkSqJnLr61cOKf/i5eWuu7Duh+OTtTjMOt9w=&Bnh4NNU90wH_OVlgbzbdZOEu1aSuPlbUctiCdYTonZ3Ap_Zd3bVL79I-dPdHf4OOgO8NKEdqyLsqc8RhAOreXgJqXuqsreeI" req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := handlerSpy{} - idpStub := newIdpStub(nil, map[string]scim.Principal{authSessionId:{Emails: []scim.UserValue{{"info@d-velop.de"}},Groups: []scim.UserGroup{{Value:"3E093BE5-CCCE-435D-99F8-544656B98681"}}}}) + idpStub := newIdpStub(nil, map[string]scim.Principal{authSessionId: {Emails: []scim.UserValue{{"info@d-velop.de"}}, Groups: []scim.UserGroup{{Value: "3E093BE5-CCCE-435D-99F8-544656B98681"}}}}) defer idpStub.Close() spy := responseSpy{httptest.NewRecorder()} @@ -577,19 +578,19 @@ func TestRequestAsExternalUserAndExternalUserValidationIsNotAllowed_ReturnsStatu } } -func TestRequestAsExternalUserAndExternalValidationIsAllowed_PopulatesContextWithPrincipalAndAuthsession(t *testing.T){ +func TestRequestAsExternalUserAndExternalValidationIsAllowed_PopulatesContextWithPrincipalAndAuthsession(t *testing.T) { req, err := http.NewRequest("GET", "/myresource/subresource?query1=abc&query2=123", nil) if err != nil { t.Fatal(err) } const authSessionId = "1XGxJeb0q+/fS8biFi8FE7TovJPPEPyzlDxT6bh5p5pHA/x7CEi1w9egVhEMz8IWhrtvJRFnkSqJnLr61cOKf/i5eWuu7Duh+OTtTjMOt9w=&Bnh4NNU90wH_OVlgbzbdZOEu1aSuPlbUctiCdYTonZ3Ap_Zd3bVL79I-dPdHf4OOgO8NKEdqyLsqc8RhAOreXgJqXuqsreeI" - principal := scim.Principal{Emails: []scim.UserValue{{"info@d-velop.de"}},Groups: []scim.UserGroup{{Value:"3E093BE5-CCCE-435D-99F8-544656B98681"}}} + principal := scim.Principal{Emails: []scim.UserValue{{"info@d-velop.de"}}, Groups: []scim.UserGroup{{Value: "3E093BE5-CCCE-435D-99F8-544656B98681"}}} req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := new(handlerSpy) - idpStub := newIdpStub(nil, map[string]scim.Principal{authSessionId:principal}) + idpStub := newIdpStub(nil, map[string]scim.Principal{authSessionId: principal}) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),true, log, log)(handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), true, log, log)(handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if err := handlerSpy.assertAuthSessionIdIs(authSessionId); err != nil { t.Error(err) @@ -599,7 +600,7 @@ func TestRequestAsExternalUserAndExternalValidationIsAllowed_PopulatesContextWit } } -func TestRequestAsInternalUserAndExternalValidationIsAllowed_PopulatesContextWithPrincipalAndAuthsession(t *testing.T){ +func TestRequestAsInternalUserAndExternalValidationIsAllowed_PopulatesContextWithPrincipalAndAuthsession(t *testing.T) { req, err := http.NewRequest("GET", "/myresource/subresource?query1=abc&query2=123", nil) if err != nil { t.Fatal(err) @@ -608,10 +609,10 @@ func TestRequestAsInternalUserAndExternalValidationIsAllowed_PopulatesContextWit principal := scim.Principal{Id: "7bbbf1b6-017a-449a-ad5f-9723d28223e1"} req.Header.Set("Authorization", "Bearer "+authSessionId) handlerSpy := new(handlerSpy) - idpStub := newIdpStub( map[string]scim.Principal{authSessionId:principal},nil) + idpStub := newIdpStub(map[string]scim.Principal{authSessionId: principal}, nil) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"),true, log, log)(handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), true, log, log)(handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if err := handlerSpy.assertAuthSessionIdIs(authSessionId); err != nil { t.Error(err) @@ -639,8 +640,8 @@ func TestIdpSendsNoCacheHeader_CallsIdp(t *testing.T) { })) defer idpStub.Close() - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) - idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false,log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) + idp.HandleAuth(returnFromCtx(idpStub.URL), returnFromCtx("1"), false, log, log)(&handlerSpy).ServeHTTP(httptest.NewRecorder(), req) if idpCalled != 2 { t.Errorf("IdP has been called %v times but expected %v times", idpCalled, 2) @@ -690,7 +691,7 @@ func TestGetRequestWithoutAuthorizationInfosWithAcceptHeader(t *testing.T) { responseSpy := responseSpy{httptest.NewRecorder()} handlerSpy := &handlerSpy{} - idp.HandleAuth(nil, nil,false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) + idp.HandleAuth(nil, nil, false, log, log)(handlerSpy).ServeHTTP(responseSpy, req) if handlerSpy.hasBeenCalled { t.Error("inner handler should not have been called") diff --git a/idp/scim/user_test.go b/idp/scim/user_test.go index e03e5cc..0719a97 100644 --- a/idp/scim/user_test.go +++ b/idp/scim/user_test.go @@ -2,9 +2,10 @@ package scim_test import ( "encoding/json" - "github.com/d-velop/dvelop-sdk-go/idp/scim" "reflect" "testing" + + "github.com/d-velop/dvelop-sdk-go/idp/scim" ) const donaldDuckJson = `{"id":"146bc69e-1edf-40f6-bf68-849906998838","userName":"d-velop\\donald","name":{"familyName":"Duck","givenName":"Donald"},"displayName":"Donald Duck","title":"Scrum Duck","emails":[{"value":"donal.duck@entenhausen.de"}],"phoneNumbers":[{"value":"+49 1235 9455-1234"}],"groups":[{"value":"d84b34da-c60e-495e-9a0d-59507630be3a","display":"Developer"},{"value":"759eaed7-4f4e-4fac-a5ef-49f03d0811a1","display":"Scrum People"}],"photos":[{"value":"/identityprovider/scim/photo/donaldbig"}]}` diff --git a/lambda/server_test.go b/lambda/server_test.go index 3ab7e4d..e860274 100644 --- a/lambda/server_test.go +++ b/lambda/server_test.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/base64" "fmt" - "github.com/d-velop/dvelop-sdk-go/lambda" "io" "io/ioutil" "net/http" @@ -12,7 +11,10 @@ import ( "reflect" "testing" + "github.com/d-velop/dvelop-sdk-go/lambda" + "context" + "github.com/aws/aws-lambda-go/events" ) diff --git a/log/log_test.go b/log/log_test.go index 36745af..ab0ec2b 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -3,9 +3,10 @@ package log_test import ( "bytes" "context" - "github.com/d-velop/dvelop-sdk-go/log" "regexp" "testing" + + "github.com/d-velop/dvelop-sdk-go/log" ) func TestMessageEndsWithNewline_Print_WritesMessageWithNewline(t *testing.T) { diff --git a/log/syslog/syslog_test.go b/log/syslog/syslog_test.go index 1d2d628..f2992ea 100644 --- a/log/syslog/syslog_test.go +++ b/log/syslog/syslog_test.go @@ -3,10 +3,11 @@ package syslog_test import ( "context" "fmt" - "github.com/d-velop/dvelop-sdk-go/log/syslog" "os" "regexp" "testing" + + "github.com/d-velop/dvelop-sdk-go/log/syslog" ) func TestWriteHeaderInfoFunc_WritesAppnameAndInfoSeverity(t *testing.T) { diff --git a/requestid/requestidmiddleware.go b/requestid/requestidmiddleware.go index 34577ee..44748b8 100644 --- a/requestid/requestidmiddleware.go +++ b/requestid/requestidmiddleware.go @@ -24,8 +24,9 @@ package requestid import ( "context" "errors" - "github.com/satori/go.uuid" "net/http" + + "github.com/satori/go.uuid" ) type contextKey string diff --git a/requestid/requestidmiddleware_test.go b/requestid/requestidmiddleware_test.go index 591454a..46488a5 100644 --- a/requestid/requestidmiddleware_test.go +++ b/requestid/requestidmiddleware_test.go @@ -2,10 +2,11 @@ package requestid_test import ( "fmt" - "github.com/d-velop/dvelop-sdk-go/requestid" "net/http" "net/http/httptest" "testing" + + "github.com/d-velop/dvelop-sdk-go/requestid" ) func TestShouldCallInnerHandler(t *testing.T) { diff --git a/requestlog/requestlogmiddleware_test.go b/requestlog/requestlogmiddleware_test.go index 5e7e1c6..f461b1d 100644 --- a/requestlog/requestlogmiddleware_test.go +++ b/requestlog/requestlogmiddleware_test.go @@ -3,12 +3,13 @@ package requestlog_test import ( "context" "fmt" - "github.com/d-velop/dvelop-sdk-go/requestlog" "net/http" "net/http/httptest" "regexp" "strings" "testing" + + "github.com/d-velop/dvelop-sdk-go/requestlog" ) func TestShouldCallInnerHandler(t *testing.T) { diff --git a/requestsigner/go.mod b/requestsigner/go.mod new file mode 100644 index 0000000..48df3ba --- /dev/null +++ b/requestsigner/go.mod @@ -0,0 +1,3 @@ +module github.com/d-velop/dvelop-sdk-go/requestsigner + +go 1.12 \ No newline at end of file diff --git a/requestsigner/requestsigner.go b/requestsigner/requestsigner.go new file mode 100644 index 0000000..3ececa9 --- /dev/null +++ b/requestsigner/requestsigner.go @@ -0,0 +1,173 @@ +package requestsigner + +import ( + "crypto/hmac" + "crypto/sha256" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "path" + "reflect" + "regexp" + "sort" + "strings" + "time" +) + +type RequestSigner interface { + ValidateSignedRequest(req *http.Request) error +} + +// The DvelopLifeCycleEventPath is path of an app endpoint, that apps must be provide +const DvelopLifeCycleEventPath = "dvelop-cloud-lifecycle-event" + +// validate signed request as middleware +func HandleSignMiddleware(appSecret []byte, timeDifferenzInMinutes int, timeNow func() time.Time) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if appSecret == nil { + log.Print("error validation signed request because app secret has not been configured") + http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + if req.Method != http.MethodPost { + log.Printf("only POST request can be signed. Got method %v", req.Method) + http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + if pathBase := path.Base(req.URL.Path); pathBase != DvelopLifeCycleEventPath { + log.Printf("path %v is not life cycle path. Life cycle path is %v", req.URL.Path, DvelopLifeCycleEventPath) + http.Error(rw, "wrong life cylce path", http.StatusBadRequest) + return + } + validAcceptHeaderValue := "application/json" + if accept := req.Header.Get("accept"); accept != validAcceptHeaderValue { + log.Printf("wrong accept header found. Got %v want %v", accept, validAcceptHeaderValue) + http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + signer := NewRequestSigner(appSecret, timeDifferenzInMinutes, timeNow) + err := signer.ValidateSignedRequest(req) + if err != nil { + log.Print("validate signed request failed: ", err) + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + }) + } +} + +type requestSigner struct { + appSecret []byte + timeDifferenzInMinute int + now func() time.Time +} + +func NewRequestSigner(appSecret []byte, timeDifferenzInMinute int, timeNow func() time.Time) RequestSigner { + if timeDifferenzInMinute < 1 { + timeDifferenzInMinute = 5 + } + return &requestSigner{ + appSecret, + timeDifferenzInMinute, + timeNow, + } +} + +// validate signed request as function +func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { + bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) + authorizationHeaderValue := req.Header.Get("Authorization") + if !bearerRegex.MatchString(authorizationHeaderValue) { + return errors.New(fmt.Sprintf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue)) + } + authorizationHeaderValue = strings.TrimPrefix(authorizationHeaderValue, "Bearer ") + + validAcceptHeaderValue := "application/json" + if accept := req.Header.Get("accept"); accept != validAcceptHeaderValue { + return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", accept, validAcceptHeaderValue)) + } + + err := signer.validateTimestamp(req) + if err != nil { + return err + } + normalizedRequestHash, err := signer.getHexHashForNormalizedHeaders(req) + if err != nil { + return err + } + hmacHexValue := signer.getHmacHash(normalizedRequestHash) + + if !reflect.DeepEqual(authorizationHeaderValue, hmacHexValue) { + return errors.New(fmt.Sprintf("wrong authorization header. Got %v want %v", authorizationHeaderValue, hmacHexValue)) + } + return nil +} + +func (signer *requestSigner) validateTimestamp(req *http.Request) error { + timestampHeaderValue, err := time.Parse(time.RFC3339, req.Header.Get("x-dv-signature-timestamp")) + if err != nil { + return err + } + diffDuration := time.Duration(signer.timeDifferenzInMinute) * time.Minute + timeNow := signer.now().UTC() + timeBeforTimestamp := timeNow.Add(-diffDuration) + timeAfterTimestamp := timeNow.Add(diffDuration) + if !(timestampHeaderValue.After(timeBeforTimestamp) && timestampHeaderValue.Before(timeAfterTimestamp)) { + return errors.New(fmt.Sprintf("request is timed out: timestamp from request: %v | current time before %v minutes: %v | current time in %v minutes: %v", + timestampHeaderValue.Format(time.RFC3339), + signer.timeDifferenzInMinute, + timeBeforTimestamp.Format(time.RFC3339), + signer.timeDifferenzInMinute, + timeAfterTimestamp.Format(time.RFC3339), + )) + } + return nil +} + +func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) (string, error) { + if req.Body == nil { + return "", errors.New("payload missing") + } + bodyReader, err := req.GetBody() + if err != nil { + return "", err + } + body, err := ioutil.ReadAll(bodyReader) + if err != nil { + return "", err + } + + signedHeaders := strings.Split(req.Header.Get("x-dv-signed-headers"), ",") + sort.Strings(signedHeaders) + normalizedHeaders := []string{} + for _, name := range signedHeaders { + headerValue := req.Header.Get(name) + normalizedHeaders = append(normalizedHeaders, fmt.Sprintf("%v:%v", strings.ToLower(name), strings.TrimSpace(headerValue))) + } + normalizedRequest := []string{} + normalizedRequest = append(normalizedRequest, req.Method) + normalizedRequest = append(normalizedRequest, req.URL.Path) + normalizedRequest = append(normalizedRequest, req.URL.RawQuery) + normalizedRequest = append(normalizedRequest, strings.Join(normalizedHeaders, "\n")) + normalizedRequest = append(normalizedRequest, signer.getHexHashedPayload(body)) + + strNormalizedRequest := strings.Join(normalizedRequest, "\n") + hashNormalizedRequest := sha256.Sum256([]byte(strNormalizedRequest)) + return strings.ToLower(fmt.Sprintf("%x", hashNormalizedRequest)), nil +} + +func (signer *requestSigner) getHexHashedPayload(payload []byte) string { + hash := sha256.Sum256(payload) + return strings.ToLower(fmt.Sprintf("%x", hash)) +} + +func (signer *requestSigner) getHmacHash(normalizedRequestHash string) string { + hmacHash := hmac.New(sha256.New, signer.appSecret) + hmacHash.Write([]byte(normalizedRequestHash)) + hmacResult := hmacHash.Sum(nil) + return strings.ToLower(fmt.Sprintf("%x", hmacResult)) +} diff --git a/requestsigner/requestsigner_test.go b/requestsigner/requestsigner_test.go new file mode 100644 index 0000000..269a007 --- /dev/null +++ b/requestsigner/requestsigner_test.go @@ -0,0 +1,95 @@ +package requestsigner_test + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/d-velop/dvelop-sdk-go/requestsigner" +) + +const timestampHeader = "x-dv-signature-timestamp" +const appNameHeader = "x-dv-signer-name" +const algorithmHeader = "x-dv-signature-algorithm" +const signedHeadersHeader = "x-dv-signed-headers" +const authorizationHeader = "authorization" + +const algorithm = "DV1-HMAC-SHA256" + +func TestHandleSignMiddleware_HappyPath_Works(t *testing.T) { + body := []byte(`{"type":"subscribe","tenantId":"vw","baseUri":"https://myfancy.d-velop.cloud"}`) + req, err := http.NewRequest("POST", "https://myapp.service.d-velop.cloud/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("accept", "application/json") + req.Header.Set(timestampHeader, "2019-08-16T12:00:27Z") + req.Header.Set(algorithmHeader, algorithm) + req.Header.Set(appNameHeader, "myapp") + req.Header.Set(signedHeadersHeader, "x-dv-signature-timestamp,x-dv-signer-name,x-dv-signature-algorithm,x-dv-signed-headers") + req.Header.Set(authorizationHeader, "Bearer 1a6e9cec49889113210d57e9659f0739bb8b1772f16b0ce56792c73d742b991c") + handlerSpy := handlerSpy{} + responseSpy := responseSpy{httptest.NewRecorder()} + + timeNow := func() time.Time { + location, _ := time.LoadLocation("Europe/Berlin") + return time.Date(2019, time.August, 16, 14, 00, 27, 0, location) + } + + requestsigner.HandleSignMiddleware([]byte("foobar"), 5, timeNow)(&handlerSpy).ServeHTTP(responseSpy, req) + if err := responseSpy.assertStatusCodeIs(200); err != nil { + t.Error(err) + } +} + +// req.method != POST +func TestHandleSignMiddleware_WrongHttpMethodIsUsed_Returns405(t *testing.T) { + +} + +// req.Header.Accept wrong +func TestHandleSignMiddleware_WrongAcceptHeaderUsed_Returns400(t *testing.T) { + +} + +// wrong life cylce event path +func TestHandleSignMiddleware_PathIsNotLifeCylceEventPath_Returns400(t *testing.T) { + +} + +// wrong sign found +func TestHandleSignMiddleware_RequestHasInvalidSignature_Returns403(t *testing.T) { + +} + +type handlerSpy struct { + hasBeenCalled bool +} + +func (spy *handlerSpy) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + spy.hasBeenCalled = true +} + +type responseSpy struct { + *httptest.ResponseRecorder +} + +func (spy *responseSpy) assertStatusCodeIs(expectedStatusCode int) error { + if status := spy.Code; status != expectedStatusCode { + return fmt.Errorf("handler returned wrong status code: got %v want %v", status, expectedStatusCode) + } + return nil +} + +// time stamp is to much in the past + +// time stamp is to much in the future + +// missing signed headers + +// missing headers described by signed headers + +// authorization hash not equals calculated hash diff --git a/tenant/tenantmiddleware_test.go b/tenant/tenantmiddleware_test.go index ae3b6aa..5d87259 100644 --- a/tenant/tenantmiddleware_test.go +++ b/tenant/tenantmiddleware_test.go @@ -3,11 +3,12 @@ package tenant_test import ( "context" "fmt" - "github.com/d-velop/dvelop-sdk-go/tenant" "net/http" "net/http/httptest" "testing" + "github.com/d-velop/dvelop-sdk-go/tenant" + "encoding/base64" "crypto/hmac" From 2aa29aa8813fbed6679a38849f1cf43618907398 Mon Sep 17 00:00:00 2001 From: Christian Som Date: Mon, 19 Aug 2019 14:51:35 +0200 Subject: [PATCH 2/7] test cases --- requestsigner/requestsigner.go | 49 +++---- requestsigner/requestsigner_test.go | 218 ++++++++++++++++++++++++++-- 2 files changed, 226 insertions(+), 41 deletions(-) diff --git a/requestsigner/requestsigner.go b/requestsigner/requestsigner.go index 3ececa9..299e355 100644 --- a/requestsigner/requestsigner.go +++ b/requestsigner/requestsigner.go @@ -22,9 +22,11 @@ type RequestSigner interface { // The DvelopLifeCycleEventPath is path of an app endpoint, that apps must be provide const DvelopLifeCycleEventPath = "dvelop-cloud-lifecycle-event" +const signatureHeaderKey = "x-dv-signature-headers" +const timeDiff = 5 * time.Minute // validate signed request as middleware -func HandleSignMiddleware(appSecret []byte, timeDifferenzInMinutes int, timeNow func() time.Time) func(http.Handler) http.Handler { +func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if appSecret == nil { @@ -49,7 +51,7 @@ func HandleSignMiddleware(appSecret []byte, timeDifferenzInMinutes int, timeNow return } - signer := NewRequestSigner(appSecret, timeDifferenzInMinutes, timeNow) + signer := NewRequestSigner(appSecret, timeNow) err := signer.ValidateSignedRequest(req) if err != nil { log.Print("validate signed request failed: ", err) @@ -61,36 +63,38 @@ func HandleSignMiddleware(appSecret []byte, timeDifferenzInMinutes int, timeNow } type requestSigner struct { - appSecret []byte - timeDifferenzInMinute int - now func() time.Time + appSecret []byte + now func() time.Time } -func NewRequestSigner(appSecret []byte, timeDifferenzInMinute int, timeNow func() time.Time) RequestSigner { - if timeDifferenzInMinute < 1 { - timeDifferenzInMinute = 5 - } +func NewRequestSigner(appSecret []byte, timeNow func() time.Time) RequestSigner { return &requestSigner{ appSecret, - timeDifferenzInMinute, timeNow, } } // validate signed request as function func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { - bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) - authorizationHeaderValue := req.Header.Get("Authorization") - if !bearerRegex.MatchString(authorizationHeaderValue) { - return errors.New(fmt.Sprintf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue)) + if signer.appSecret == nil { + return errors.New("app secret has not been configured") } - authorizationHeaderValue = strings.TrimPrefix(authorizationHeaderValue, "Bearer ") validAcceptHeaderValue := "application/json" if accept := req.Header.Get("accept"); accept != validAcceptHeaderValue { return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", accept, validAcceptHeaderValue)) } + bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) + authorizationHeaderValue := req.Header.Get("Authorization") + if authorizationHeaderValue == "" { + return errors.New("authorization header missing") + } + if !bearerRegex.MatchString(authorizationHeaderValue) { + return errors.New(fmt.Sprintf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue)) + } + authorizationHeaderValue = strings.TrimPrefix(authorizationHeaderValue, "Bearer ") + err := signer.validateTimestamp(req) if err != nil { return err @@ -112,18 +116,11 @@ func (signer *requestSigner) validateTimestamp(req *http.Request) error { if err != nil { return err } - diffDuration := time.Duration(signer.timeDifferenzInMinute) * time.Minute timeNow := signer.now().UTC() - timeBeforTimestamp := timeNow.Add(-diffDuration) - timeAfterTimestamp := timeNow.Add(diffDuration) + timeBeforTimestamp := timeNow.Add(-timeDiff) + timeAfterTimestamp := timeNow.Add(timeDiff) if !(timestampHeaderValue.After(timeBeforTimestamp) && timestampHeaderValue.Before(timeAfterTimestamp)) { - return errors.New(fmt.Sprintf("request is timed out: timestamp from request: %v | current time before %v minutes: %v | current time in %v minutes: %v", - timestampHeaderValue.Format(time.RFC3339), - signer.timeDifferenzInMinute, - timeBeforTimestamp.Format(time.RFC3339), - signer.timeDifferenzInMinute, - timeAfterTimestamp.Format(time.RFC3339), - )) + return errors.New(fmt.Sprintf("request is timed out: timestamp from request: %v, current time: %v", timestampHeaderValue.Format(time.RFC3339), timeNow.Format(time.RFC3339))) } return nil } @@ -141,7 +138,7 @@ func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) ( return "", err } - signedHeaders := strings.Split(req.Header.Get("x-dv-signed-headers"), ",") + signedHeaders := strings.Split(req.Header.Get(signatureHeaderKey), ",") sort.Strings(signedHeaders) normalizedHeaders := []string{} for _, name := range signedHeaders { diff --git a/requestsigner/requestsigner_test.go b/requestsigner/requestsigner_test.go index 269a007..860a245 100644 --- a/requestsigner/requestsigner_test.go +++ b/requestsigner/requestsigner_test.go @@ -12,9 +12,8 @@ import ( ) const timestampHeader = "x-dv-signature-timestamp" -const appNameHeader = "x-dv-signer-name" const algorithmHeader = "x-dv-signature-algorithm" -const signedHeadersHeader = "x-dv-signed-headers" +const signedHeadersHeader = "x-dv-signature-headers" const authorizationHeader = "authorization" const algorithm = "DV1-HMAC-SHA256" @@ -28,9 +27,8 @@ func TestHandleSignMiddleware_HappyPath_Works(t *testing.T) { req.Header.Set("accept", "application/json") req.Header.Set(timestampHeader, "2019-08-16T12:00:27Z") req.Header.Set(algorithmHeader, algorithm) - req.Header.Set(appNameHeader, "myapp") - req.Header.Set(signedHeadersHeader, "x-dv-signature-timestamp,x-dv-signer-name,x-dv-signature-algorithm,x-dv-signed-headers") - req.Header.Set(authorizationHeader, "Bearer 1a6e9cec49889113210d57e9659f0739bb8b1772f16b0ce56792c73d742b991c") + req.Header.Set(signedHeadersHeader, "x-dv-signature-timestamp,x-dv-signature-algorithm,x-dv-signature-headers") + req.Header.Set(authorizationHeader, "Bearer 1ed80bcfdfdd67455413fa4a2b47e013aca9f1a846501f545931bead0e21cb12") handlerSpy := handlerSpy{} responseSpy := responseSpy{httptest.NewRecorder()} @@ -39,30 +37,65 @@ func TestHandleSignMiddleware_HappyPath_Works(t *testing.T) { return time.Date(2019, time.August, 16, 14, 00, 27, 0, location) } - requestsigner.HandleSignMiddleware([]byte("foobar"), 5, timeNow)(&handlerSpy).ServeHTTP(responseSpy, req) + requestsigner.HandleSignMiddleware([]byte("foobar"), timeNow)(&handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(200); err != nil { t.Error(err) } } -// req.method != POST -func TestHandleSignMiddleware_WrongHttpMethodIsUsed_Returns405(t *testing.T) { - +func TestHandleSignMiddleware_AppSecretNotSet_Returns500(t *testing.T) { + req, _ := http.NewRequest("GET", "https://foobar.com", nil) + h := handlerSpy{} + w := responseSpy{httptest.NewRecorder()} + requestsigner.HandleSignMiddleware(nil, nil)(&h).ServeHTTP(w, req) + if err := w.assertStatusCodeIs(500); err != nil { + t.Error(err) + } } -// req.Header.Accept wrong -func TestHandleSignMiddleware_WrongAcceptHeaderUsed_Returns400(t *testing.T) { - +// req.method != POST +func TestHandleSignMiddleware_WrongHttpMethodIsUsed_Returns405(t *testing.T) { + req, _ := http.NewRequest("GET", "https://foobar.com", nil) + h := handlerSpy{} + w := responseSpy{httptest.NewRecorder()} + requestsigner.HandleSignMiddleware([]byte("foobar"), nil)(&h).ServeHTTP(w, req) + if err := w.assertStatusCodeIs(405); err != nil { + t.Error(err) + } } // wrong life cylce event path func TestHandleSignMiddleware_PathIsNotLifeCylceEventPath_Returns400(t *testing.T) { + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + h := handlerSpy{} + w := responseSpy{httptest.NewRecorder()} + requestsigner.HandleSignMiddleware([]byte("foobar"), nil)(&h).ServeHTTP(w, req) + if err := w.assertStatusCodeIs(400); err != nil { + t.Error(err) + } +} +// req.Header.Accept wrong +func TestHandleSignMiddleware_WrongAcceptHeaderUsed_Returns400(t *testing.T) { + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar/dvelop-cloud-lifecycle-event", nil) + h := handlerSpy{} + w := responseSpy{httptest.NewRecorder()} + requestsigner.HandleSignMiddleware([]byte("foobar"), nil)(&h).ServeHTTP(w, req) + if err := w.assertStatusCodeIs(400); err != nil { + t.Error(err) + } } // wrong sign found func TestHandleSignMiddleware_RequestHasInvalidSignature_Returns403(t *testing.T) { - + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar/dvelop-cloud-lifecycle-event", nil) + req.Header.Set("accept", "application/json") + h := handlerSpy{} + w := responseSpy{httptest.NewRecorder()} + requestsigner.HandleSignMiddleware([]byte("foobar"), time.Now)(&h).ServeHTTP(w, req) + if err := w.assertStatusCodeIs(403); err != nil { + t.Error(err) + } } type handlerSpy struct { @@ -84,12 +117,167 @@ func (spy *responseSpy) assertStatusCodeIs(expectedStatusCode int) error { return nil } +// working +func TestRequestSigner_ValidateSignedRequest_HappyPath_Works(t *testing.T) { + getNow := func() time.Time { + location, _ := time.LoadLocation("Europe/Berlin") + return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) + } + dto := `{"type": "subscribe","tenantId":"vw","baseUri":"https://mycloud.d-velop.cloud"}` + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", bytes.NewBuffer([]byte(dto))) + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer 997bb670e6c61ed1de186c68d7016e01a67045353be9908fa5c53df61507559c") + req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") + req.Header.Set("x-dv-signature-headers", "x-dv-signuature-headers, x-dv-signature-algorithm, x-dv-signature-timestamp") + req.Header.Set("x-dv-signature-algorithm", "DV1-HMAC-SHA256") + + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + err := requestSigner.ValidateSignedRequest(req) + if err != nil { + t.Errorf("no error expected but got error %v", err) + } +} + +func TestRequestSigner_ValidateSignedRequest_AppSecretNotConfigured_ReturnsError(t *testing.T) { + requestSigner := requestsigner.NewRequestSigner(nil, nil) + err := requestSigner.ValidateSignedRequest(nil) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "app secret has not been configured"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} + +// invalid accept header +func TestRequestSigner_ValidateSignedRequest_WrongAcceptHeaderInRequest_ReturnsError(t *testing.T) { + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), nil) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "wrong accept header found. Got want application/json"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} + +// authorization header missing on request +func TestRequestSigner_ValidateSignedRequest_AuthorizationHeaderMissingInRequest_ReturnsError(t *testing.T) { + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + req.Header.Set("accept", "application/json") + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), nil) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "authorization header missing"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} + +// invalid bearer token found +func TestRequestSigner_ValidateSignedRequest_AuthorizationBearerTokenInvalid_ReturnsError(t *testing.T) { + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer foobar") + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), nil) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "found authorization header is not a valid Bearer token. Got Bearer foobar"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} + // time stamp is to much in the past +func TestRequestSigner_ValidateSignedRequest_TimestampIs10MinutesInThePast_ReturnsError(t *testing.T) { + getNow := func() time.Time { + location, _ := time.LoadLocation("Europe/Berlin") + return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) + } + + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer 0adf") + req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:29:27Z") + + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "request is timed out: timestamp from request: 2019-08-05T11:29:27Z, current time: 2019-08-05T11:39:27Z"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} // time stamp is to much in the future +func TestRequestSigner_ValidateSignedRequest_TimestampIs10MinutesInTheFuture_ReturnsError(t *testing.T) { + getNow := func() time.Time { + location, _ := time.LoadLocation("Europe/Berlin") + return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) + } -// missing signed headers + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer 0adf") + req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:49:27Z") -// missing headers described by signed headers + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "request is timed out: timestamp from request: 2019-08-05T11:49:27Z, current time: 2019-08-05T11:39:27Z"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} + +// payload missing +func TestRequestSigner_ValidateSignedRequest_PayloadMissingInRequest_ReturnsError(t *testing.T) { + getNow := func() time.Time { + location, _ := time.LoadLocation("Europe/Berlin") + return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) + } + + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer 0adf") + req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") + + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + } + if expectedError := "payload missing"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} // authorization hash not equals calculated hash +func TestRequestSigner_ValidateSignedRequest_InvalidAuthorizationBearerToken_ReturnsError(t *testing.T) { + getNow := func() time.Time { + location, _ := time.LoadLocation("Europe/Berlin") + return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) + } + dto := `{"type": "subscribe","tenantId":"vw","baseUri":"https://mycloud.d-velop.cloud"}` + req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", bytes.NewBuffer([]byte(dto))) + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer 0adf") + req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") + req.Header.Set("x-dv-signature-headers", "x-dv-signuature-headers, x-dv-signature-algorithm, x-dv-signature-timestamp") + req.Header.Set("x-dv-signature-algorithm", "DV1-HMAC-SHA256") + + requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + err := requestSigner.ValidateSignedRequest(req) + if err == nil { + t.Errorf("error expected but no error returned") + return + } + if expectedError := "wrong authorization header. Got 0adf want 997bb670e6c61ed1de186c68d7016e01a67045353be9908fa5c53df61507559c"; err.Error() != expectedError { + t.Errorf("wrong error returned: got %v want %v", err, expectedError) + } +} From 2f1c2a3bd643e58aa8553f86b7b34a1d7e82df3c Mon Sep 17 00:00:00 2001 From: Christian Som Date: Tue, 20 Aug 2019 11:16:00 +0200 Subject: [PATCH 3/7] - rename to requestsignature and requestSignatureValidator - check header "content-type" instead of "accept"-header - add documentation of signatureValidatore middleware --- {requestsigner => requestsignature}/go.mod | 0 .../requestsignature.go | 114 +++++++++++++----- .../requestsignature_test.go | 68 ++++++----- 3 files changed, 124 insertions(+), 58 deletions(-) rename {requestsigner => requestsignature}/go.mod (100%) rename requestsigner/requestsigner.go => requestsignature/requestsignature.go (54%) rename requestsigner/requestsigner_test.go => requestsignature/requestsignature_test.go (79%) diff --git a/requestsigner/go.mod b/requestsignature/go.mod similarity index 100% rename from requestsigner/go.mod rename to requestsignature/go.mod diff --git a/requestsigner/requestsigner.go b/requestsignature/requestsignature.go similarity index 54% rename from requestsigner/requestsigner.go rename to requestsignature/requestsignature.go index 299e355..1e15d63 100644 --- a/requestsigner/requestsigner.go +++ b/requestsignature/requestsignature.go @@ -1,10 +1,12 @@ -package requestsigner +package requestsignature import ( + "bytes" "crypto/hmac" "crypto/sha256" "errors" "fmt" + "io" "io/ioutil" "log" "net/http" @@ -16,17 +18,60 @@ import ( "time" ) -type RequestSigner interface { +type RequestSignatureValidator interface { ValidateSignedRequest(req *http.Request) error } -// The DvelopLifeCycleEventPath is path of an app endpoint, that apps must be provide +// the DvelopLifeCycleEventPath is path of an app endpoint, that apps must be provide const DvelopLifeCycleEventPath = "dvelop-cloud-lifecycle-event" + +// header who contains relevant headers for signature const signatureHeaderKey = "x-dv-signature-headers" + +// valid time differenz of request const timeDiff = 5 * time.Minute -// validate signed request as middleware -func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http.Handler) http.Handler { +// Validate signature of request i.e. for event handling +// The middleware "HandleSignaturValidation" checks the signature of incoming requests. This is important for +// cloud center to app authentication. The cloudcenter make an POST request to app with a signature. The middleware +// checks if request is a POST request and the content-type header is set to "application/json". +// If the requested signature is valid, then your handler is invoke to handle http events from cloud center. If the +// signature is invalid, the middleware returns the HTTP error 403 "Forbidden" and log the reason to your application log. +// +// The parameter for the "appSecret" is the base64 decoded app secret string of your app as byte array. +// +// More information about signature algorithm please visit the following documentation: +// https://portal.d-velop.de/documentation/ccapi/de/cloudcenter-api-197757199.html +// +// Example: +// func main() { +// // replace `Zm9vYmFy` with your app secret (base64-string) +// myAppSecret, err := base64.StdEncoding.DecodeString(`Zm9vYmFy`) +// if err != nil { +// panic(err) +// } +// mux := http.NewServeMux() +// mux.Handle("/app/dvelop-cloud-lifecycle-event", requestsignatur.HandleSignaturValidation(myAppSecret, time.Now)(eventHandler())) +// } +// +// func eventHandler() http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { +// eventDto := &struct { +// EventType string `json:"type"` +// TenantId string `json:"tenantId"` +// BaseUri string `json:"baseUri"` +// }{} +// err := json.NewDecoder(req.Body).Decode(eventDto) +// if err != nil { +// log.Print(err) +// http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) +// return +// } +// doSomeStuff(eventDto) +// }) +// } + +func HandleSignaturValidation(appSecret []byte, timeNow func() time.Time) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if appSecret == nil { @@ -44,14 +89,14 @@ func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http. http.Error(rw, "wrong life cylce path", http.StatusBadRequest) return } - validAcceptHeaderValue := "application/json" - if accept := req.Header.Get("accept"); accept != validAcceptHeaderValue { - log.Printf("wrong accept header found. Got %v want %v", accept, validAcceptHeaderValue) + validContentTypeHeaderValue := "application/json" + if accept := req.Header.Get("content-type"); accept != validContentTypeHeaderValue { + log.Printf("wrong content-type header found. Got %v want %v", accept, validContentTypeHeaderValue) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - signer := NewRequestSigner(appSecret, timeNow) + signer := NewRequestSignaturValidator(appSecret, timeNow) err := signer.ValidateSignedRequest(req) if err != nil { log.Print("validate signed request failed: ", err) @@ -62,27 +107,27 @@ func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http. } } -type requestSigner struct { +type requestSignaturValidator struct { appSecret []byte now func() time.Time } -func NewRequestSigner(appSecret []byte, timeNow func() time.Time) RequestSigner { - return &requestSigner{ +func NewRequestSignaturValidator(appSecret []byte, timeNow func() time.Time) RequestSignatureValidator { + return &requestSignaturValidator{ appSecret, timeNow, } } // validate signed request as function -func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { +// todo: write description of function with example code +func (signer *requestSignaturValidator) ValidateSignedRequest(req *http.Request) error { if signer.appSecret == nil { return errors.New("app secret has not been configured") } - - validAcceptHeaderValue := "application/json" - if accept := req.Header.Get("accept"); accept != validAcceptHeaderValue { - return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", accept, validAcceptHeaderValue)) + validContentTypeHeaderValue := "application/json" + if accept := req.Header.Get("content-type"); accept != validContentTypeHeaderValue { + return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", accept, validContentTypeHeaderValue)) } bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) @@ -111,7 +156,7 @@ func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { return nil } -func (signer *requestSigner) validateTimestamp(req *http.Request) error { +func (signer *requestSignaturValidator) validateTimestamp(req *http.Request) error { timestampHeaderValue, err := time.Parse(time.RFC3339, req.Header.Get("x-dv-signature-timestamp")) if err != nil { return err @@ -125,17 +170,28 @@ func (signer *requestSigner) validateTimestamp(req *http.Request) error { return nil } -func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) (string, error) { +func (signer *requestSignaturValidator) getHexHashForNormalizedHeaders(req *http.Request) (string, error) { if req.Body == nil { return "", errors.New("payload missing") } - bodyReader, err := req.GetBody() - if err != nil { - return "", err - } - body, err := ioutil.ReadAll(bodyReader) - if err != nil { - return "", err + var body []byte + var err error + if req.GetBody != nil { + var bodyReader io.Reader + bodyReader, err = req.GetBody() + if err != nil { + return "", err + } + body, err = ioutil.ReadAll(bodyReader) + if err != nil { + return "", err + } + } else { + body, err = ioutil.ReadAll(req.Body) + if err != nil { + return "", err + } + req.Body = ioutil.NopCloser(bytes.NewReader(body)) } signedHeaders := strings.Split(req.Header.Get(signatureHeaderKey), ",") @@ -149,7 +205,7 @@ func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) ( normalizedRequest = append(normalizedRequest, req.Method) normalizedRequest = append(normalizedRequest, req.URL.Path) normalizedRequest = append(normalizedRequest, req.URL.RawQuery) - normalizedRequest = append(normalizedRequest, strings.Join(normalizedHeaders, "\n")) + normalizedRequest = append(normalizedRequest, fmt.Sprintf("%v\n", strings.Join(normalizedHeaders, "\n"))) normalizedRequest = append(normalizedRequest, signer.getHexHashedPayload(body)) strNormalizedRequest := strings.Join(normalizedRequest, "\n") @@ -157,12 +213,12 @@ func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) ( return strings.ToLower(fmt.Sprintf("%x", hashNormalizedRequest)), nil } -func (signer *requestSigner) getHexHashedPayload(payload []byte) string { +func (signer *requestSignaturValidator) getHexHashedPayload(payload []byte) string { hash := sha256.Sum256(payload) return strings.ToLower(fmt.Sprintf("%x", hash)) } -func (signer *requestSigner) getHmacHash(normalizedRequestHash string) string { +func (signer *requestSignaturValidator) getHmacHash(normalizedRequestHash string) string { hmacHash := hmac.New(sha256.New, signer.appSecret) hmacHash.Write([]byte(normalizedRequestHash)) hmacResult := hmacHash.Sum(nil) diff --git a/requestsigner/requestsigner_test.go b/requestsignature/requestsignature_test.go similarity index 79% rename from requestsigner/requestsigner_test.go rename to requestsignature/requestsignature_test.go index 860a245..b5c838b 100644 --- a/requestsigner/requestsigner_test.go +++ b/requestsignature/requestsignature_test.go @@ -1,14 +1,15 @@ -package requestsigner_test +package requestsignature_test import ( "bytes" "fmt" + "io/ioutil" "net/http" "net/http/httptest" "testing" "time" - "github.com/d-velop/dvelop-sdk-go/requestsigner" + "github.com/d-velop/dvelop-sdk-go/requestsignature" ) const timestampHeader = "x-dv-signature-timestamp" @@ -24,11 +25,11 @@ func TestHandleSignMiddleware_HappyPath_Works(t *testing.T) { if err != nil { t.Fatal(err) } - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") req.Header.Set(timestampHeader, "2019-08-16T12:00:27Z") req.Header.Set(algorithmHeader, algorithm) req.Header.Set(signedHeadersHeader, "x-dv-signature-timestamp,x-dv-signature-algorithm,x-dv-signature-headers") - req.Header.Set(authorizationHeader, "Bearer 1ed80bcfdfdd67455413fa4a2b47e013aca9f1a846501f545931bead0e21cb12") + req.Header.Set(authorizationHeader, "Bearer 9174da8f8b1b2ce1acb7cee0b412b4f15c882746a420f8ca1ca955823d13becc") handlerSpy := handlerSpy{} responseSpy := responseSpy{httptest.NewRecorder()} @@ -37,7 +38,7 @@ func TestHandleSignMiddleware_HappyPath_Works(t *testing.T) { return time.Date(2019, time.August, 16, 14, 00, 27, 0, location) } - requestsigner.HandleSignMiddleware([]byte("foobar"), timeNow)(&handlerSpy).ServeHTTP(responseSpy, req) + requestsignature.HandleSignaturValidation([]byte("foobar"), timeNow)(&handlerSpy).ServeHTTP(responseSpy, req) if err := responseSpy.assertStatusCodeIs(200); err != nil { t.Error(err) } @@ -47,7 +48,7 @@ func TestHandleSignMiddleware_AppSecretNotSet_Returns500(t *testing.T) { req, _ := http.NewRequest("GET", "https://foobar.com", nil) h := handlerSpy{} w := responseSpy{httptest.NewRecorder()} - requestsigner.HandleSignMiddleware(nil, nil)(&h).ServeHTTP(w, req) + requestsignature.HandleSignaturValidation(nil, nil)(&h).ServeHTTP(w, req) if err := w.assertStatusCodeIs(500); err != nil { t.Error(err) } @@ -58,7 +59,7 @@ func TestHandleSignMiddleware_WrongHttpMethodIsUsed_Returns405(t *testing.T) { req, _ := http.NewRequest("GET", "https://foobar.com", nil) h := handlerSpy{} w := responseSpy{httptest.NewRecorder()} - requestsigner.HandleSignMiddleware([]byte("foobar"), nil)(&h).ServeHTTP(w, req) + requestsignature.HandleSignaturValidation([]byte("foobar"), nil)(&h).ServeHTTP(w, req) if err := w.assertStatusCodeIs(405); err != nil { t.Error(err) } @@ -69,7 +70,7 @@ func TestHandleSignMiddleware_PathIsNotLifeCylceEventPath_Returns400(t *testing. req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) h := handlerSpy{} w := responseSpy{httptest.NewRecorder()} - requestsigner.HandleSignMiddleware([]byte("foobar"), nil)(&h).ServeHTTP(w, req) + requestsignature.HandleSignaturValidation([]byte("foobar"), nil)(&h).ServeHTTP(w, req) if err := w.assertStatusCodeIs(400); err != nil { t.Error(err) } @@ -80,7 +81,7 @@ func TestHandleSignMiddleware_WrongAcceptHeaderUsed_Returns400(t *testing.T) { req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar/dvelop-cloud-lifecycle-event", nil) h := handlerSpy{} w := responseSpy{httptest.NewRecorder()} - requestsigner.HandleSignMiddleware([]byte("foobar"), nil)(&h).ServeHTTP(w, req) + requestsignature.HandleSignaturValidation([]byte("foobar"), nil)(&h).ServeHTTP(w, req) if err := w.assertStatusCodeIs(400); err != nil { t.Error(err) } @@ -89,10 +90,10 @@ func TestHandleSignMiddleware_WrongAcceptHeaderUsed_Returns400(t *testing.T) { // wrong sign found func TestHandleSignMiddleware_RequestHasInvalidSignature_Returns403(t *testing.T) { req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar/dvelop-cloud-lifecycle-event", nil) - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") h := handlerSpy{} w := responseSpy{httptest.NewRecorder()} - requestsigner.HandleSignMiddleware([]byte("foobar"), time.Now)(&h).ServeHTTP(w, req) + requestsignature.HandleSignaturValidation([]byte("foobar"), time.Now)(&h).ServeHTTP(w, req) if err := w.assertStatusCodeIs(403); err != nil { t.Error(err) } @@ -125,21 +126,30 @@ func TestRequestSigner_ValidateSignedRequest_HappyPath_Works(t *testing.T) { } dto := `{"type": "subscribe","tenantId":"vw","baseUri":"https://mycloud.d-velop.cloud"}` req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", bytes.NewBuffer([]byte(dto))) - req.Header.Set("accept", "application/json") - req.Header.Set("Authorization", "Bearer 997bb670e6c61ed1de186c68d7016e01a67045353be9908fa5c53df61507559c") + req.Header.Set("content-type", "application/json") + req.Header.Set("Authorization", "Bearer bb7f9f2c18785bd4c28ab3ea298d19c7960032cad8307d2d6c42bba9051d3aec") req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") req.Header.Set("x-dv-signature-headers", "x-dv-signuature-headers, x-dv-signature-algorithm, x-dv-signature-timestamp") req.Header.Set("x-dv-signature-algorithm", "DV1-HMAC-SHA256") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) err := requestSigner.ValidateSignedRequest(req) if err != nil { t.Errorf("no error expected but got error %v", err) } + body, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("get body after validate request failed: %v", err) + return + } + if strBody := string(body); strBody != dto { + t.Errorf("wrong body found after validate request: got %v want %v", strBody, dto) + } + } func TestRequestSigner_ValidateSignedRequest_AppSecretNotConfigured_ReturnsError(t *testing.T) { - requestSigner := requestsigner.NewRequestSigner(nil, nil) + requestSigner := requestsignature.NewRequestSignaturValidator(nil, nil) err := requestSigner.ValidateSignedRequest(nil) if err == nil { t.Errorf("error expected but no error returned") @@ -152,7 +162,7 @@ func TestRequestSigner_ValidateSignedRequest_AppSecretNotConfigured_ReturnsError // invalid accept header func TestRequestSigner_ValidateSignedRequest_WrongAcceptHeaderInRequest_ReturnsError(t *testing.T) { req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), nil) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), nil) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") @@ -165,8 +175,8 @@ func TestRequestSigner_ValidateSignedRequest_WrongAcceptHeaderInRequest_ReturnsE // authorization header missing on request func TestRequestSigner_ValidateSignedRequest_AuthorizationHeaderMissingInRequest_ReturnsError(t *testing.T) { req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("accept", "application/json") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), nil) + req.Header.Set("content-type", "application/json") + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), nil) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") @@ -179,9 +189,9 @@ func TestRequestSigner_ValidateSignedRequest_AuthorizationHeaderMissingInRequest // invalid bearer token found func TestRequestSigner_ValidateSignedRequest_AuthorizationBearerTokenInvalid_ReturnsError(t *testing.T) { req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") req.Header.Set("Authorization", "Bearer foobar") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), nil) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), nil) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") @@ -199,11 +209,11 @@ func TestRequestSigner_ValidateSignedRequest_TimestampIs10MinutesInThePast_Retur } req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") req.Header.Set("Authorization", "Bearer 0adf") req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:29:27Z") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") @@ -221,11 +231,11 @@ func TestRequestSigner_ValidateSignedRequest_TimestampIs10MinutesInTheFuture_Ret } req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") req.Header.Set("Authorization", "Bearer 0adf") req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:49:27Z") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") @@ -243,11 +253,11 @@ func TestRequestSigner_ValidateSignedRequest_PayloadMissingInRequest_ReturnsErro } req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") req.Header.Set("Authorization", "Bearer 0adf") req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") @@ -265,19 +275,19 @@ func TestRequestSigner_ValidateSignedRequest_InvalidAuthorizationBearerToken_Ret } dto := `{"type": "subscribe","tenantId":"vw","baseUri":"https://mycloud.d-velop.cloud"}` req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", bytes.NewBuffer([]byte(dto))) - req.Header.Set("accept", "application/json") + req.Header.Set("content-type", "application/json") req.Header.Set("Authorization", "Bearer 0adf") req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") req.Header.Set("x-dv-signature-headers", "x-dv-signuature-headers, x-dv-signature-algorithm, x-dv-signature-timestamp") req.Header.Set("x-dv-signature-algorithm", "DV1-HMAC-SHA256") - requestSigner := requestsigner.NewRequestSigner([]byte("foobar"), getNow) + requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) err := requestSigner.ValidateSignedRequest(req) if err == nil { t.Errorf("error expected but no error returned") return } - if expectedError := "wrong authorization header. Got 0adf want 997bb670e6c61ed1de186c68d7016e01a67045353be9908fa5c53df61507559c"; err.Error() != expectedError { + if expectedError := "wrong authorization header. Got 0adf want bb7f9f2c18785bd4c28ab3ea298d19c7960032cad8307d2d6c42bba9051d3aec"; err.Error() != expectedError { t.Errorf("wrong error returned: got %v want %v", err, expectedError) } } From 1a83d33ab3b0829dc024e5c72af046f9ddf4dbdd Mon Sep 17 00:00:00 2001 From: Christian Som Date: Fri, 27 Sep 2019 11:10:37 +0200 Subject: [PATCH 4/7] edit comments --- requestsignature/go.mod | 4 +- requestsignature/requestsignature.go | 58 +++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/requestsignature/go.mod b/requestsignature/go.mod index 48df3ba..a6d990f 100644 --- a/requestsignature/go.mod +++ b/requestsignature/go.mod @@ -1,3 +1,3 @@ -module github.com/d-velop/dvelop-sdk-go/requestsigner +module github.com/d-velop/dvelop-sdk-go/requestsignature -go 1.12 \ No newline at end of file +go 1.12 diff --git a/requestsignature/requestsignature.go b/requestsignature/requestsignature.go index 1e15d63..5873044 100644 --- a/requestsignature/requestsignature.go +++ b/requestsignature/requestsignature.go @@ -31,17 +31,17 @@ const signatureHeaderKey = "x-dv-signature-headers" // valid time differenz of request const timeDiff = 5 * time.Minute -// Validate signature of request i.e. for event handling +// Validate signature of request i.e. for validate HTTP events from cloud center // The middleware "HandleSignaturValidation" checks the signature of incoming requests. This is important for // cloud center to app authentication. The cloudcenter make an POST request to app with a signature. The middleware // checks if request is a POST request and the content-type header is set to "application/json". -// If the requested signature is valid, then your handler is invoke to handle http events from cloud center. If the +// If the requested signature is valid, then your handler is invoke to handle the request. If the // signature is invalid, the middleware returns the HTTP error 403 "Forbidden" and log the reason to your application log. // // The parameter for the "appSecret" is the base64 decoded app secret string of your app as byte array. // // More information about signature algorithm please visit the following documentation: -// https://portal.d-velop.de/documentation/ccapi/de/cloudcenter-api-197757199.html +// coming soon... // // Example: // func main() { @@ -51,7 +51,9 @@ const timeDiff = 5 * time.Minute // panic(err) // } // mux := http.NewServeMux() -// mux.Handle("/app/dvelop-cloud-lifecycle-event", requestsignatur.HandleSignaturValidation(myAppSecret, time.Now)(eventHandler())) +// // the path must a ressource for dvelop-cloud-lifecycle-event +// path := "/app/dvelop-cloud-lifecycle-event" +// mux.Handle(path, requestsignatur.HandleSignaturValidation(myAppSecret, time.Now)(eventHandler())) // } // // func eventHandler() http.Handler { @@ -112,6 +114,51 @@ type requestSignaturValidator struct { now func() time.Time } +// Validate signature of request as function i.e. for validate HTTP events from cloud center +// The function "ValidateSignedRequest" in "RequestSignatureValidator" checks the signature of a requests. +// This is important for cloud center to app authentication. The cloudcenter make an POST request to app with a signature. +// It checks if request is a POST request and the content-type header is set to "application/json". Then an own signature +// will be calculated by information from header "dv-signature-headers" and a hash of request body. If the calculcated +// signature is equals to signature of Authorization-header, the signature in request is valid. If signature is valid, +// no error is returned from the function. Otherwise it returns an error and you must abort the request by returning +// HTTP error 403 "Forbidden". +// +// The parameter for the "appSecret" is the base64 decoded app secret string of your app as byte array. +// +// More information about signature algorithm please visit the following documentation on +// coming soon... +// +// Example: +// func eventHandler() http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { +// // replace `Zm9vYmFy` with your app secret (base64-string) +// myAppSecret, err := base64.StdEncoding.DecodeString(`Zm9vYmFy`) +// if err != nil { +// panic(err) +// } +// signatureValidator := NewRequestSignaturValidator(myAppSecret, time.Now) +// err = signatureValidator.ValidateSignedRequest(req) +// if err != nil { +// log.Print(err) +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// +// eventDto := &struct { +// EventType string `json:"type"` +// TenantId string `json:"tenantId"` +// BaseUri string `json:"baseUri"` +// }{} +// err = json.NewDecoder(req.Body).Decode(eventDto) +// if err != nil { +// log.Print(err) +// http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) +// return +// } +// doSomeStuff(eventDto) +// }) +//} + func NewRequestSignaturValidator(appSecret []byte, timeNow func() time.Time) RequestSignatureValidator { return &requestSignaturValidator{ appSecret, @@ -119,8 +166,7 @@ func NewRequestSignaturValidator(appSecret []byte, timeNow func() time.Time) Req } } -// validate signed request as function -// todo: write description of function with example code +// validate signature in request as function func (signer *requestSignaturValidator) ValidateSignedRequest(req *http.Request) error { if signer.appSecret == nil { return errors.New("app secret has not been configured") From e5ae15ceea7a167b35f81498eca4f5580839bfc7 Mon Sep 17 00:00:00 2001 From: Christian Som Date: Fri, 22 Nov 2019 07:25:36 +0100 Subject: [PATCH 5/7] add link to developer portal --- environment/environmentmiddleware.go | 2 +- environment/environmentmiddleware_test.go | 3 ++- idp/authmiddleware.go | 5 ----- idp/authmiddleware_test.go | 3 --- idp/idpclient/client.go | 3 ++- idp/test/testing.go | 3 ++- lambda/environment.go | 5 +++-- lambda/environment_test.go | 5 +++-- lambda/server_test.go | 4 ---- requestsignature/requestsignature.go | 4 ++-- tenant/tenantmiddleware_test.go | 5 ----- 11 files changed, 15 insertions(+), 27 deletions(-) diff --git a/environment/environmentmiddleware.go b/environment/environmentmiddleware.go index c3ed457..e8ba756 100644 --- a/environment/environmentmiddleware.go +++ b/environment/environmentmiddleware.go @@ -63,4 +63,4 @@ func Get(ctx context.Context) string { return "" } return value -} \ No newline at end of file +} diff --git a/environment/environmentmiddleware_test.go b/environment/environmentmiddleware_test.go index 6069a7b..e021865 100644 --- a/environment/environmentmiddleware_test.go +++ b/environment/environmentmiddleware_test.go @@ -1,10 +1,11 @@ package environment_test import ( - "github.com/d-velop/dvelop-sdk-go/environment" "net/http" "net/http/httptest" "testing" + + "github.com/d-velop/dvelop-sdk-go/environment" ) func TestRequestWithEnvironmentFunction_UsesReturnedEnvironment(t *testing.T) { diff --git a/idp/authmiddleware.go b/idp/authmiddleware.go index fcaeaf8..612f50a 100644 --- a/idp/authmiddleware.go +++ b/idp/authmiddleware.go @@ -12,11 +12,6 @@ import ( "strings" "github.com/d-velop/dvelop-sdk-go/idp/scim" - - "strconv" - "strings" - - "github.com/patrickmn/go-cache" ) type contextKey string diff --git a/idp/authmiddleware_test.go b/idp/authmiddleware_test.go index b2cee42..4f20f30 100644 --- a/idp/authmiddleware_test.go +++ b/idp/authmiddleware_test.go @@ -11,9 +11,6 @@ import ( "reflect" "testing" - "github.com/d-velop/dvelop-sdk-go/idp" - "github.com/d-velop/dvelop-sdk-go/idp/scim" - "time" "github.com/d-velop/dvelop-sdk-go/idp" diff --git a/idp/idpclient/client.go b/idp/idpclient/client.go index dfb78a3..75aec52 100644 --- a/idp/idpclient/client.go +++ b/idp/idpclient/client.go @@ -6,13 +6,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/patrickmn/go-cache" "io/ioutil" "net/http" "net/url" "regexp" "time" + "github.com/patrickmn/go-cache" + "github.com/d-velop/dvelop-sdk-go/idp/scim" ) diff --git a/idp/test/testing.go b/idp/test/testing.go index f6a69da..b4fc88e 100644 --- a/idp/test/testing.go +++ b/idp/test/testing.go @@ -2,10 +2,11 @@ package test import ( "encoding/json" - "github.com/d-velop/dvelop-sdk-go/idp/scim" "net/http" "net/http/httptest" "regexp" + + "github.com/d-velop/dvelop-sdk-go/idp/scim" ) var bearerTokenRegex = regexp.MustCompile("^(?i)bearer (.*)$") diff --git a/lambda/environment.go b/lambda/environment.go index a641eb7..3b063f9 100644 --- a/lambda/environment.go +++ b/lambda/environment.go @@ -1,9 +1,10 @@ package lambda import ( - "github.com/aws/aws-lambda-go/lambdacontext" "net/http" "strings" + + "github.com/aws/aws-lambda-go/lambdacontext" ) func GetAliasFromRequest(req http.Request) string { @@ -18,4 +19,4 @@ func GetAliasFromRequest(req http.Request) string { } return "" -} \ No newline at end of file +} diff --git a/lambda/environment_test.go b/lambda/environment_test.go index abe5180..b4ed7f2 100644 --- a/lambda/environment_test.go +++ b/lambda/environment_test.go @@ -2,10 +2,11 @@ package lambda_test import ( "context" - "github.com/aws/aws-lambda-go/lambdacontext" - "github.com/d-velop/dvelop-sdk-go/lambda" "net/http" "testing" + + "github.com/aws/aws-lambda-go/lambdacontext" + "github.com/d-velop/dvelop-sdk-go/lambda" ) func TestGetAliasFromRequest_LambdaArnHasNamedLambdaAlias_ReturnsAliasFromArn(t *testing.T) { diff --git a/lambda/server_test.go b/lambda/server_test.go index 2282a81..f25c7e9 100644 --- a/lambda/server_test.go +++ b/lambda/server_test.go @@ -12,10 +12,6 @@ import ( "reflect" "testing" - "github.com/d-velop/dvelop-sdk-go/lambda" - - "context" - "github.com/aws/aws-lambda-go/events" "github.com/d-velop/dvelop-sdk-go/lambda" ) diff --git a/requestsignature/requestsignature.go b/requestsignature/requestsignature.go index 5873044..29060c4 100644 --- a/requestsignature/requestsignature.go +++ b/requestsignature/requestsignature.go @@ -41,7 +41,7 @@ const timeDiff = 5 * time.Minute // The parameter for the "appSecret" is the base64 decoded app secret string of your app as byte array. // // More information about signature algorithm please visit the following documentation: -// coming soon... +// https://developer.d-velop.de/documentation/ccapi/en/cloud-center-api-199623589.html // // Example: // func main() { @@ -126,7 +126,7 @@ type requestSignaturValidator struct { // The parameter for the "appSecret" is the base64 decoded app secret string of your app as byte array. // // More information about signature algorithm please visit the following documentation on -// coming soon... +// https://developer.d-velop.de/documentation/ccapi/en/cloud-center-api-199623589.html // // Example: // func eventHandler() http.Handler { diff --git a/tenant/tenantmiddleware_test.go b/tenant/tenantmiddleware_test.go index 4c2d76f..a735fd8 100644 --- a/tenant/tenantmiddleware_test.go +++ b/tenant/tenantmiddleware_test.go @@ -11,11 +11,6 @@ import ( "testing" "github.com/d-velop/dvelop-sdk-go/tenant" - - "encoding/base64" - - "crypto/hmac" - "crypto/sha256" ) const systemBaseUriHeader = "x-dv-baseuri" From 1e1a045451bf599ed0d07c6d574e0e84090264ee Mon Sep 17 00:00:00 2001 From: Christian Som Date: Fri, 8 May 2020 11:26:59 +0200 Subject: [PATCH 6/7] request signature validator to validate center lifecylce events --- requestsignature/requestsignature.go | 178 ++++--- requestsignature/requestsignature_test.go | 598 ++++++++++++++-------- 2 files changed, 507 insertions(+), 269 deletions(-) diff --git a/requestsignature/requestsignature.go b/requestsignature/requestsignature.go index 29060c4..4bee437 100644 --- a/requestsignature/requestsignature.go +++ b/requestsignature/requestsignature.go @@ -1,7 +1,6 @@ package requestsignature import ( - "bytes" "crypto/hmac" "crypto/sha256" "errors" @@ -18,21 +17,30 @@ import ( "time" ) -type RequestSignatureValidator interface { +type RequestSigner interface { ValidateSignedRequest(req *http.Request) error } +type RequestSignatureDto struct { + EventType string `json:"type"` + TenantId string `json:"tenantId"` + BaseUri string `json:"baseUri"` + } + // the DvelopLifeCycleEventPath is path of an app endpoint, that apps must be provide const DvelopLifeCycleEventPath = "dvelop-cloud-lifecycle-event" // header who contains relevant headers for signature const signatureHeaderKey = "x-dv-signature-headers" +// allowed content-type header value +const validContentTypeHeaderValue = "application/json" + // valid time differenz of request const timeDiff = 5 * time.Minute // Validate signature of request i.e. for validate HTTP events from cloud center -// The middleware "HandleSignaturValidation" checks the signature of incoming requests. This is important for +// The middleware "HandleSignMiddleware" checks the signature of incoming requests. This is important for // cloud center to app authentication. The cloudcenter make an POST request to app with a signature. The middleware // checks if request is a POST request and the content-type header is set to "application/json". // If the requested signature is valid, then your handler is invoke to handle the request. If the @@ -45,8 +53,8 @@ const timeDiff = 5 * time.Minute // // Example: // func main() { -// // replace `Zm9vYmFy` with your app secret (base64-string) -// myAppSecret, err := base64.StdEncoding.DecodeString(`Zm9vYmFy`) +// // replace `Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=` with your app secret (base64-string) +// myAppSecret, err := base64.StdEncoding.DecodeString(`Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=`) // if err != nil { // panic(err) // } @@ -58,11 +66,7 @@ const timeDiff = 5 * time.Minute // // func eventHandler() http.Handler { // return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { -// eventDto := &struct { -// EventType string `json:"type"` -// TenantId string `json:"tenantId"` -// BaseUri string `json:"baseUri"` -// }{} +// eventDto := new(requestsignature.RequestSignatureDto) // err := json.NewDecoder(req.Body).Decode(eventDto) // if err != nil { // log.Print(err) @@ -73,24 +77,28 @@ const timeDiff = 5 * time.Minute // }) // } -func HandleSignaturValidation(appSecret []byte, timeNow func() time.Time) func(http.Handler) http.Handler { +func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if appSecret == nil { log.Print("error validation signed request because app secret has not been configured") http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } + if req.Method != http.MethodPost { log.Printf("only POST request can be signed. Got method %v", req.Method) http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } + if pathBase := path.Base(req.URL.Path); pathBase != DvelopLifeCycleEventPath { log.Printf("path %v is not life cycle path. Life cycle path is %v", req.URL.Path, DvelopLifeCycleEventPath) - http.Error(rw, "wrong life cylce path", http.StatusBadRequest) + http.Error(rw, fmt.Sprintf("wrong life cylce path: got %v", req.URL.Path), http.StatusBadRequest) return } + validContentTypeHeaderValue := "application/json" if accept := req.Header.Get("content-type"); accept != validContentTypeHeaderValue { log.Printf("wrong content-type header found. Got %v want %v", accept, validContentTypeHeaderValue) @@ -98,22 +106,19 @@ func HandleSignaturValidation(appSecret []byte, timeNow func() time.Time) func(h return } - signer := NewRequestSignaturValidator(appSecret, timeNow) + signer := NewRequestSigner(appSecret, timeNow) err := signer.ValidateSignedRequest(req) if err != nil { log.Print("validate signed request failed: ", err) http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } + + next.ServeHTTP(rw, req) }) } } -type requestSignaturValidator struct { - appSecret []byte - now func() time.Time -} - // Validate signature of request as function i.e. for validate HTTP events from cloud center // The function "ValidateSignedRequest" in "RequestSignatureValidator" checks the signature of a requests. // This is important for cloud center to app authentication. The cloudcenter make an POST request to app with a signature. @@ -159,34 +164,34 @@ type requestSignaturValidator struct { // }) //} -func NewRequestSignaturValidator(appSecret []byte, timeNow func() time.Time) RequestSignatureValidator { - return &requestSignaturValidator{ +type requestSigner struct { + appSecret []byte + now func() time.Time +} + +func NewRequestSigner(appSecret []byte, timeNow func() time.Time) RequestSigner { + return &requestSigner{ appSecret, timeNow, } } -// validate signature in request as function -func (signer *requestSignaturValidator) ValidateSignedRequest(req *http.Request) error { +// validate signed request as function +func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { if signer.appSecret == nil { return errors.New("app secret has not been configured") } - validContentTypeHeaderValue := "application/json" - if accept := req.Header.Get("content-type"); accept != validContentTypeHeaderValue { - return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", accept, validContentTypeHeaderValue)) - } - bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) - authorizationHeaderValue := req.Header.Get("Authorization") - if authorizationHeaderValue == "" { - return errors.New("authorization header missing") + if contentType := req.Header.Get("content-type"); contentType != validContentTypeHeaderValue { + return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", contentType, validContentTypeHeaderValue)) } - if !bearerRegex.MatchString(authorizationHeaderValue) { - return errors.New(fmt.Sprintf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue)) + + authorizationHeaderValue, err := signer.isAuthorationHeaderValid(req) + if err != nil { + return err } - authorizationHeaderValue = strings.TrimPrefix(authorizationHeaderValue, "Bearer ") - err := signer.validateTimestamp(req) + err = signer.isTimestampValid(req) if err != nil { return err } @@ -194,59 +199,83 @@ func (signer *requestSignaturValidator) ValidateSignedRequest(req *http.Request) if err != nil { return err } + log.Print("normalized request hex hash: ", normalizedRequestHash) + hmacHexValue := signer.getHmacHash(normalizedRequestHash) + log.Print("hmac hex hash: ", hmacHexValue) - if !reflect.DeepEqual(authorizationHeaderValue, hmacHexValue) { + if !signer.isAuthorizationHeaderEqualsToCalculatedHmacHex(authorizationHeaderValue, hmacHexValue) { return errors.New(fmt.Sprintf("wrong authorization header. Got %v want %v", authorizationHeaderValue, hmacHexValue)) } return nil } -func (signer *requestSignaturValidator) validateTimestamp(req *http.Request) error { +func (signer *requestSigner) isAuthorizationHeaderEqualsToCalculatedHmacHex(authorizationHeaderValue string, hmacHexValue string) bool { + return reflect.DeepEqual(authorizationHeaderValue, hmacHexValue) +} + +func (signer *requestSigner) isAuthorationHeaderValid(req *http.Request) (string, error) { + bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) + authorizationHeaderValue := req.Header.Get("Authorization") + if authorizationHeaderValue == "" { + return "", errors.New("authorization header missing") + } + if !bearerRegex.MatchString(authorizationHeaderValue) { + return "", errors.New(fmt.Sprintf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue)) + } + authorizationHeaderValue = strings.TrimPrefix(authorizationHeaderValue, "Bearer ") + return authorizationHeaderValue, nil +} + +func (signer *requestSigner) isTimestampValid(req *http.Request) error { timestampHeaderValue, err := time.Parse(time.RFC3339, req.Header.Get("x-dv-signature-timestamp")) if err != nil { return err } + timeNow := signer.now().UTC() + timeBeforTimestamp := timeNow.Add(-timeDiff) timeAfterTimestamp := timeNow.Add(timeDiff) + if !(timestampHeaderValue.After(timeBeforTimestamp) && timestampHeaderValue.Before(timeAfterTimestamp)) { return errors.New(fmt.Sprintf("request is timed out: timestamp from request: %v, current time: %v", timestampHeaderValue.Format(time.RFC3339), timeNow.Format(time.RFC3339))) } + return nil } -func (signer *requestSignaturValidator) getHexHashForNormalizedHeaders(req *http.Request) (string, error) { +func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) (hex string, err error) { if req.Body == nil { return "", errors.New("payload missing") } - var body []byte - var err error - if req.GetBody != nil { - var bodyReader io.Reader - bodyReader, err = req.GetBody() - if err != nil { - return "", err - } - body, err = ioutil.ReadAll(bodyReader) - if err != nil { - return "", err - } - } else { - body, err = ioutil.ReadAll(req.Body) - if err != nil { - return "", err - } - req.Body = ioutil.NopCloser(bytes.NewReader(body)) + + body, err := signer.getBodyFromRequest(req) + if err != nil { + return "", err } signedHeaders := strings.Split(req.Header.Get(signatureHeaderKey), ",") sort.Strings(signedHeaders) + + normalizedRequest := signer.getNormalizedRequestWithHeaderAndBody(req, signedHeaders, body) + + strNormalizedRequest := strings.Join(normalizedRequest, "\n") + log.Printf("normalized request: %#v", strNormalizedRequest) + + hashNormalizedRequest := sha256.Sum256([]byte(strNormalizedRequest)) + + return strings.ToLower(fmt.Sprintf("%x", hashNormalizedRequest)), nil +} + +func (signer *requestSigner) getNormalizedRequestWithHeaderAndBody(req *http.Request, signedHeaders []string, body []byte) []string { normalizedHeaders := []string{} + for _, name := range signedHeaders { headerValue := req.Header.Get(name) normalizedHeaders = append(normalizedHeaders, fmt.Sprintf("%v:%v", strings.ToLower(name), strings.TrimSpace(headerValue))) } + normalizedRequest := []string{} normalizedRequest = append(normalizedRequest, req.Method) normalizedRequest = append(normalizedRequest, req.URL.Path) @@ -254,19 +283,46 @@ func (signer *requestSignaturValidator) getHexHashForNormalizedHeaders(req *http normalizedRequest = append(normalizedRequest, fmt.Sprintf("%v\n", strings.Join(normalizedHeaders, "\n"))) normalizedRequest = append(normalizedRequest, signer.getHexHashedPayload(body)) - strNormalizedRequest := strings.Join(normalizedRequest, "\n") - hashNormalizedRequest := sha256.Sum256([]byte(strNormalizedRequest)) - return strings.ToLower(fmt.Sprintf("%x", hashNormalizedRequest)), nil + return normalizedRequest +} + +func (signer *requestSigner) getBodyFromRequest(req *http.Request) ([]byte, error) { + if req.GetBody != nil { + log.Print("get copy of request body") + + var bodyReader io.Reader + bodyReader, err := req.GetBody() + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(bodyReader) + if err != nil { + return nil, err + } + + return body,nil + } else { + log.Print("request.GetBody is nil. Read body and create new request body (reader) with copy of body") + + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil,err + } + + return body, nil + } } -func (signer *requestSignaturValidator) getHexHashedPayload(payload []byte) string { +func (signer *requestSigner) getHexHashedPayload(payload []byte) string { + log.Print("payload: ", string(payload)) hash := sha256.Sum256(payload) return strings.ToLower(fmt.Sprintf("%x", hash)) } -func (signer *requestSignaturValidator) getHmacHash(normalizedRequestHash string) string { +func (signer *requestSigner) getHmacHash(normalizedRequestHash string) string { hmacHash := hmac.New(sha256.New, signer.appSecret) hmacHash.Write([]byte(normalizedRequestHash)) hmacResult := hmacHash.Sum(nil) return strings.ToLower(fmt.Sprintf("%x", hmacResult)) -} +} \ No newline at end of file diff --git a/requestsignature/requestsignature_test.go b/requestsignature/requestsignature_test.go index b5c838b..a389e05 100644 --- a/requestsignature/requestsignature_test.go +++ b/requestsignature/requestsignature_test.go @@ -2,14 +2,13 @@ package requestsignature_test import ( "bytes" - "fmt" - "io/ioutil" + "encoding/base64" + "encoding/json" + "github.com/d-velop/dvelop-sdk-go/requestsignature" "net/http" "net/http/httptest" "testing" "time" - - "github.com/d-velop/dvelop-sdk-go/requestsignature" ) const timestampHeader = "x-dv-signature-timestamp" @@ -19,275 +18,458 @@ const authorizationHeader = "authorization" const algorithm = "DV1-HMAC-SHA256" -func TestHandleSignMiddleware_HappyPath_Works(t *testing.T) { - body := []byte(`{"type":"subscribe","tenantId":"vw","baseUri":"https://myfancy.d-velop.cloud"}`) - req, err := http.NewRequest("POST", "https://myapp.service.d-velop.cloud/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) +func TestRequestSigner_ValidateSignedRequest_HappyPath_Working(t *testing.T) { + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") if err != nil { - t.Fatal(err) + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - req.Header.Set("content-type", "application/json") - req.Header.Set(timestampHeader, "2019-08-16T12:00:27Z") - req.Header.Set(algorithmHeader, algorithm) - req.Header.Set(signedHeadersHeader, "x-dv-signature-timestamp,x-dv-signature-algorithm,x-dv-signature-headers") - req.Header.Set(authorizationHeader, "Bearer 9174da8f8b1b2ce1acb7cee0b412b4f15c882746a420f8ca1ca955823d13becc") - handlerSpy := handlerSpy{} - responseSpy := responseSpy{httptest.NewRecorder()} - timeNow := func() time.Time { - location, _ := time.LoadLocation("Europe/Berlin") - return time.Date(2019, time.August, 16, 14, 00, 27, 0, location) + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) } - requestsignature.HandleSignaturValidation([]byte("foobar"), timeNow)(&handlerSpy).ServeHTTP(responseSpy, req) - if err := responseSpy.assertStatusCodeIs(200); err != nil { - t.Error(err) + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", } -} -func TestHandleSignMiddleware_AppSecretNotSet_Returns500(t *testing.T) { - req, _ := http.NewRequest("GET", "https://foobar.com", nil) - h := handlerSpy{} - w := responseSpy{httptest.NewRecorder()} - requestsignature.HandleSignaturValidation(nil, nil)(&h).ServeHTTP(w, req) - if err := w.assertStatusCodeIs(500); err != nil { - t.Error(err) + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", } -} -// req.method != POST -func TestHandleSignMiddleware_WrongHttpMethodIsUsed_Returns405(t *testing.T) { - req, _ := http.NewRequest("GET", "https://foobar.com", nil) - h := handlerSpy{} - w := responseSpy{httptest.NewRecorder()} - requestsignature.HandleSignaturValidation([]byte("foobar"), nil)(&h).ServeHTTP(w, req) - if err := w.assertStatusCodeIs(405); err != nil { - t.Error(err) + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) } -} -// wrong life cylce event path -func TestHandleSignMiddleware_PathIsNotLifeCylceEventPath_Returns400(t *testing.T) { - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - h := handlerSpy{} - w := responseSpy{httptest.NewRecorder()} - requestsignature.HandleSignaturValidation([]byte("foobar"), nil)(&h).ServeHTTP(w, req) - if err := w.assertStatusCodeIs(400); err != nil { - t.Error(err) + validator := requestsignature.NewRequestSigner(appSecret,now) + err = validator.ValidateSignedRequest(req) + if err != nil { + t.Errorf("got error %v but no error expected", err) } } -// req.Header.Accept wrong -func TestHandleSignMiddleware_WrongAcceptHeaderUsed_Returns400(t *testing.T) { - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar/dvelop-cloud-lifecycle-event", nil) - h := handlerSpy{} - w := responseSpy{httptest.NewRecorder()} - requestsignature.HandleSignaturValidation([]byte("foobar"), nil)(&h).ServeHTTP(w, req) - if err := w.assertStatusCodeIs(400); err != nil { - t.Error(err) +func TestRequestSigner_ValidateSignedRequest_AuthorationHeaderInvalid_ReturnError(t *testing.T) { + wantErrorMessage := "wrong authorization header. Got 12783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104d want 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c" + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } -} -// wrong sign found -func TestHandleSignMiddleware_RequestHasInvalidSignature_Returns403(t *testing.T) { - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar/dvelop-cloud-lifecycle-event", nil) - req.Header.Set("content-type", "application/json") - h := handlerSpy{} - w := responseSpy{httptest.NewRecorder()} - requestsignature.HandleSignaturValidation([]byte("foobar"), time.Now)(&h).ServeHTTP(w, req) - if err := w.assertStatusCodeIs(403); err != nil { - t.Error(err) + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) } -} -type handlerSpy struct { - hasBeenCalled bool -} + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", + } -func (spy *handlerSpy) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - spy.hasBeenCalled = true -} + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 12783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104d", + "Content-Type": "application/json", + } -type responseSpy struct { - *httptest.ResponseRecorder -} + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) + } -func (spy *responseSpy) assertStatusCodeIs(expectedStatusCode int) error { - if status := spy.Code; status != expectedStatusCode { - return fmt.Errorf("handler returned wrong status code: got %v want %v", status, expectedStatusCode) + validator := requestsignature.NewRequestSigner(appSecret,now) + err = validator.ValidateSignedRequest(req) + if err != nil { + if err.Error()!=wantErrorMessage { + t.Errorf("wrong error returned: got %v want %v", err, wantErrorMessage) + } + } else { + t.Errorf("no error returned, but want error %v", wantErrorMessage) } - return nil } -// working -func TestRequestSigner_ValidateSignedRequest_HappyPath_Works(t *testing.T) { - getNow := func() time.Time { - location, _ := time.LoadLocation("Europe/Berlin") - return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) - } - dto := `{"type": "subscribe","tenantId":"vw","baseUri":"https://mycloud.d-velop.cloud"}` - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", bytes.NewBuffer([]byte(dto))) - req.Header.Set("content-type", "application/json") - req.Header.Set("Authorization", "Bearer bb7f9f2c18785bd4c28ab3ea298d19c7960032cad8307d2d6c42bba9051d3aec") - req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") - req.Header.Set("x-dv-signature-headers", "x-dv-signuature-headers, x-dv-signature-algorithm, x-dv-signature-timestamp") - req.Header.Set("x-dv-signature-algorithm", "DV1-HMAC-SHA256") - - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) - err := requestSigner.ValidateSignedRequest(req) +func TestRequestSigner_ValidateSignedRequest_WithWrongDto_ReturnError(t *testing.T) { + wantErrorMessage := "wrong authorization header. Got 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c want daba3a1deb11b646540bcb42161ea0003cf6ca6c1c3282d83e8c80e91cfcd9f9" + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") if err != nil { - t.Errorf("no error expected but got error %v", err) + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - body, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Errorf("get body after validate request failed: %v", err) - return + + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) } - if strBody := string(body); strBody != dto { - t.Errorf("wrong body found after validate request: got %v want %v", strBody, dto) + + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://xyz.d-velop.cloud", } -} + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", + } -func TestRequestSigner_ValidateSignedRequest_AppSecretNotConfigured_ReturnsError(t *testing.T) { - requestSigner := requestsignature.NewRequestSignaturValidator(nil, nil) - err := requestSigner.ValidateSignedRequest(nil) - if err == nil { - t.Errorf("error expected but no error returned") + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) } - if expectedError := "app secret has not been configured"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + validator := requestsignature.NewRequestSigner(appSecret,now) + err = validator.ValidateSignedRequest(req) + if err != nil { + if err.Error()!=wantErrorMessage { + t.Errorf("wrong error returned: got %v want %v", err, wantErrorMessage) + } + } else { + t.Errorf("no error returned, but want error %v", wantErrorMessage) } } -// invalid accept header -func TestRequestSigner_ValidateSignedRequest_WrongAcceptHeaderInRequest_ReturnsError(t *testing.T) { - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), nil) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") +func TestRequestSigner_ValidateSignedRequest_RequestTimeouted_ReturnError(t *testing.T) { + wantErrorMessage := "request is timed out: timestamp from request: 2019-08-09T08:49:42Z, current time: 2019-08-09T09:49:45Z" + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - if expectedError := "wrong accept header found. Got want application/json"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + now := func()time.Time { + return time.Date(2019,time.August,9,9,49,45,0,time.UTC) } -} -// authorization header missing on request -func TestRequestSigner_ValidateSignedRequest_AuthorizationHeaderMissingInRequest_ReturnsError(t *testing.T) { - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("content-type", "application/json") - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), nil) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", } - if expectedError := "authorization header missing"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", + } + + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) + } + + validator := requestsignature.NewRequestSigner(appSecret,now) + err = validator.ValidateSignedRequest(req) + if err != nil { + if err.Error()!=wantErrorMessage { + t.Errorf("wrong error returned: got %v want %v", err, wantErrorMessage) + } + } else { + t.Errorf("no error returned, but want error %v", wantErrorMessage) } } -// invalid bearer token found -func TestRequestSigner_ValidateSignedRequest_AuthorizationBearerTokenInvalid_ReturnsError(t *testing.T) { - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("content-type", "application/json") - req.Header.Set("Authorization", "Bearer foobar") - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), nil) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") +func TestHandleSignMiddleware_HappyPath_Working(t *testing.T) { + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) + } + + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + } + + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", + } + + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", } - if expectedError := "found authorization header is not a valid Bearer token. Got Bearer foobar"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) + } + + handlerCalled := false + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalled = true + t.Log("handler was called") + } + + rr := httptest.NewRecorder() + requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + + if status := rr.Code; status != http.StatusOK { + t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusOK) + } + if !handlerCalled { + t.Fatalf("test handler not called") } } -// time stamp is to much in the past -func TestRequestSigner_ValidateSignedRequest_TimestampIs10MinutesInThePast_ReturnsError(t *testing.T) { - getNow := func() time.Time { - location, _ := time.LoadLocation("Europe/Berlin") - return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) +func TestHandleSignMiddleware_AppSecretMissing_Return500InternalServerError(t *testing.T) { + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) } - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("content-type", "application/json") - req.Header.Set("Authorization", "Bearer 0adf") - req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:29:27Z") + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", + } + + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", + } - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) } - if expectedError := "request is timed out: timestamp from request: 2019-08-05T11:29:27Z, current time: 2019-08-05T11:39:27Z"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + handlerCalled := false + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalled = true + t.Log("handler was called") + } + + rr := httptest.NewRecorder() + requestsignature.HandleSignMiddleware(nil,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + + if status := rr.Code; status != http.StatusInternalServerError { + t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusInternalServerError) + } + if handlerCalled { + t.Fatalf("Handler was called, but should not be called.") } } -// time stamp is to much in the future -func TestRequestSigner_ValidateSignedRequest_TimestampIs10MinutesInTheFuture_ReturnsError(t *testing.T) { - getNow := func() time.Time { - location, _ := time.LoadLocation("Europe/Berlin") - return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) +func TestHandleSignMiddleware_MiddlewareWasCalledByWrongMethod_Return405MethodNotAllowed(t *testing.T) { + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) + } + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) } - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("content-type", "application/json") - req.Header.Set("Authorization", "Bearer 0adf") - req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:49:27Z") + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", + } - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", } - if expectedError := "request is timed out: timestamp from request: 2019-08-05T11:49:27Z, current time: 2019-08-05T11:39:27Z"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodGet, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) + } + + handlerCalled := false + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalled = true + t.Log("handler was called") + } + + rr := httptest.NewRecorder() + requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + + if status := rr.Code; status != http.StatusMethodNotAllowed { + t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusMethodNotAllowed) + } + if handlerCalled { + t.Fatalf("Handler was called, but should not be called.") } } -// payload missing -func TestRequestSigner_ValidateSignedRequest_PayloadMissingInRequest_ReturnsError(t *testing.T) { - getNow := func() time.Time { - location, _ := time.LoadLocation("Europe/Berlin") - return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) +func TestHandleSignMiddleware_MiddlewareWasCalledByPath_Return400BadRequest(t *testing.T) { + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) + } + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + } + + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", } - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", nil) - req.Header.Set("content-type", "application/json") - req.Header.Set("Authorization", "Bearer 0adf") - req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", + } - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event/wrongpath", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) } - if expectedError := "payload missing"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) + + handlerCalled := false + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalled = true + t.Log("handler was called") + } + + rr := httptest.NewRecorder() + requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusBadRequest) + } + if handlerCalled { + t.Fatalf("Handler was called, but should not be called.") } } -// authorization hash not equals calculated hash -func TestRequestSigner_ValidateSignedRequest_InvalidAuthorizationBearerToken_ReturnsError(t *testing.T) { - getNow := func() time.Time { - location, _ := time.LoadLocation("Europe/Berlin") - return time.Date(2019, time.August, 5, 13, 39, 27, 4711, location) - } - dto := `{"type": "subscribe","tenantId":"vw","baseUri":"https://mycloud.d-velop.cloud"}` - req, _ := http.NewRequest("POST", "https://foobar.com/foo/bar", bytes.NewBuffer([]byte(dto))) - req.Header.Set("content-type", "application/json") - req.Header.Set("Authorization", "Bearer 0adf") - req.Header.Set("x-dv-signature-timestamp", "2019-08-05T11:39:27Z") - req.Header.Set("x-dv-signature-headers", "x-dv-signuature-headers, x-dv-signature-algorithm, x-dv-signature-timestamp") - req.Header.Set("x-dv-signature-algorithm", "DV1-HMAC-SHA256") - - requestSigner := requestsignature.NewRequestSignaturValidator([]byte("foobar"), getNow) - err := requestSigner.ValidateSignedRequest(req) - if err == nil { - t.Errorf("error expected but no error returned") - return - } - if expectedError := "wrong authorization header. Got 0adf want bb7f9f2c18785bd4c28ab3ea298d19c7960032cad8307d2d6c42bba9051d3aec"; err.Error() != expectedError { - t.Errorf("wrong error returned: got %v want %v", err, expectedError) +func TestHandleSignMiddleware_MiddlewareWasCalledWithoutContentTypeHeader_Return400BadRequest(t *testing.T) { + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) + } + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + } + + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "id", + "https://someone.d-velop.cloud", + } + + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + //"Content-Type": "application/json", + } + + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) + } + + handlerCalled := false + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalled = true + t.Log("handler was called") + } + + rr := httptest.NewRecorder() + requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusBadRequest) + } + if handlerCalled { + t.Fatalf("Handler was called, but should not be called.") } } + +func TestHandleSignMiddleware_MiddlewareWasCalledButSignatureIsInvalid_Return403Forbidden(t *testing.T) { + appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") + if err != nil { + t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) + } + now := func()time.Time { + return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + } + + dto := requestsignature.RequestSignatureDto{ + "subscribe", + "wrong id to generate wrong body hash", + "https://someone.d-velop.cloud", + } + + headers := map[string]string{ + "x-dv-signature-headers": "x-dv-signature-algorithm,x-dv-signature-headers,x-dv-signature-timestamp", + "x-dv-signature-algorithm": "DV1-HMAC-SHA256", + "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", + "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", + "Content-Type": "application/json", + } + + payload := &bytes.Buffer{} + json.NewEncoder(payload).Encode(dto) + body := payload.Bytes() + req, _ := http.NewRequest(http.MethodPost, "/myapp/dvelop-cloud-lifecycle-event", bytes.NewReader(body)) + for key, value := range headers { + req.Header.Add(key, value) + } + + handlerCalled := false + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalled = true + t.Log("handler was called") + } + + rr := httptest.NewRecorder() + requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + + if status := rr.Code; status != http.StatusForbidden { + t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusForbidden) + } + if handlerCalled { + t.Fatalf("Handler was called, but should not be called.") + } +} \ No newline at end of file From cd509dc71fbdb53361f06083847c83aaa80e922d Mon Sep 17 00:00:00 2001 From: Christian Som Date: Wed, 6 Jan 2021 13:44:47 +0100 Subject: [PATCH 7/7] inject logger and remove not needed log entries --- requestsignature/go.mod | 2 +- requestsignature/requestsignature.go | 109 ++++++++++-------- requestsignature/requestsignature_test.go | 128 ++++++++++++---------- 3 files changed, 131 insertions(+), 108 deletions(-) diff --git a/requestsignature/go.mod b/requestsignature/go.mod index a6d990f..8a430cd 100644 --- a/requestsignature/go.mod +++ b/requestsignature/go.mod @@ -1,3 +1,3 @@ module github.com/d-velop/dvelop-sdk-go/requestsignature -go 1.12 +go 1.13 diff --git a/requestsignature/requestsignature.go b/requestsignature/requestsignature.go index 4bee437..2920c45 100644 --- a/requestsignature/requestsignature.go +++ b/requestsignature/requestsignature.go @@ -1,13 +1,12 @@ package requestsignature import ( + "bytes" + "context" "crypto/hmac" "crypto/sha256" - "errors" "fmt" - "io" "io/ioutil" - "log" "net/http" "path" "reflect" @@ -21,11 +20,13 @@ type RequestSigner interface { ValidateSignedRequest(req *http.Request) error } -type RequestSignatureDto struct { - EventType string `json:"type"` - TenantId string `json:"tenantId"` - BaseUri string `json:"baseUri"` - } +type loggerFunc func(ctx context.Context, message string) + +type Dto struct { + EventType string `json:"type"` + TenantId string `json:"tenantId"` + BaseUri string `json:"baseUri"` +} // the DvelopLifeCycleEventPath is path of an app endpoint, that apps must be provide const DvelopLifeCycleEventPath = "dvelop-cloud-lifecycle-event" @@ -33,6 +34,9 @@ const DvelopLifeCycleEventPath = "dvelop-cloud-lifecycle-event" // header who contains relevant headers for signature const signatureHeaderKey = "x-dv-signature-headers" +// header who contains timestamp of request +const timestampHeaderKey = "x-dv-signature-timestamp" + // allowed content-type header value const validContentTypeHeaderValue = "application/json" @@ -40,7 +44,7 @@ const validContentTypeHeaderValue = "application/json" const timeDiff = 5 * time.Minute // Validate signature of request i.e. for validate HTTP events from cloud center -// The middleware "HandleSignMiddleware" checks the signature of incoming requests. This is important for +// The middleware "HandleCloudSignatureMiddleware" checks the signature of incoming requests. This is important for // cloud center to app authentication. The cloudcenter make an POST request to app with a signature. The middleware // checks if request is a POST request and the content-type header is set to "application/json". // If the requested signature is valid, then your handler is invoke to handle the request. If the @@ -77,39 +81,39 @@ const timeDiff = 5 * time.Minute // }) // } -func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http.Handler) http.Handler { +func HandleCloudSignatureMiddleware(appSecret []byte, timeNow func() time.Time, logError, logInfo loggerFunc) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() if appSecret == nil { - log.Print("error validation signed request because app secret has not been configured") + logError(ctx, "validation signed request failed because app secret has not been configured") http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if req.Method != http.MethodPost { - log.Printf("only POST request can be signed. Got method %v", req.Method) + logError(ctx, fmt.Sprintf("only POST request can be signed. Got method %v", req.Method)) http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } if pathBase := path.Base(req.URL.Path); pathBase != DvelopLifeCycleEventPath { - log.Printf("path %v is not life cycle path. Life cycle path is %v", req.URL.Path, DvelopLifeCycleEventPath) + logError(ctx, fmt.Sprintf("path %v is not life cycle path. Life cycle path must %v", req.URL.Path, DvelopLifeCycleEventPath)) http.Error(rw, fmt.Sprintf("wrong life cylce path: got %v", req.URL.Path), http.StatusBadRequest) return } - validContentTypeHeaderValue := "application/json" if accept := req.Header.Get("content-type"); accept != validContentTypeHeaderValue { - log.Printf("wrong content-type header found. Got %v want %v", accept, validContentTypeHeaderValue) - http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + logError(ctx, fmt.Sprintf("wrong content-type header found. Got %v want %v", accept, validContentTypeHeaderValue)) + http.Error(rw, fmt.Sprintf("%s: please use content-type '%s'", http.StatusText(http.StatusBadRequest), validContentTypeHeaderValue), http.StatusNotAcceptable) return } - signer := NewRequestSigner(appSecret, timeNow) + signer := NewRequestSigner(appSecret, timeNow, logInfo) err := signer.ValidateSignedRequest(req) if err != nil { - log.Print("validate signed request failed: ", err) + logError(ctx, fmt.Sprintf("validate signed request failed: %v", err)) http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } @@ -167,26 +171,28 @@ func HandleSignMiddleware(appSecret []byte, timeNow func() time.Time) func(http. type requestSigner struct { appSecret []byte now func() time.Time + logInfo loggerFunc } -func NewRequestSigner(appSecret []byte, timeNow func() time.Time) RequestSigner { +func NewRequestSigner(appSecret []byte, timeNow func() time.Time, logInfo loggerFunc) RequestSigner { return &requestSigner{ appSecret, timeNow, + logInfo, } } // validate signed request as function func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { - if signer.appSecret == nil { - return errors.New("app secret has not been configured") + if len(signer.appSecret) == 0 { + return fmt.Errorf("app secret has not been configured") } if contentType := req.Header.Get("content-type"); contentType != validContentTypeHeaderValue { - return errors.New(fmt.Sprintf("wrong accept header found. Got %v want %v", contentType, validContentTypeHeaderValue)) + return fmt.Errorf("wrong accept header found. Got %v want %v", contentType, validContentTypeHeaderValue) } - authorizationHeaderValue, err := signer.isAuthorationHeaderValid(req) + authorizationHeaderValue, err := signer.isAuthorizationHeaderValid(req) if err != nil { return err } @@ -195,18 +201,20 @@ func (signer *requestSigner) ValidateSignedRequest(req *http.Request) error { if err != nil { return err } + normalizedRequestHash, err := signer.getHexHashForNormalizedHeaders(req) if err != nil { return err } - log.Print("normalized request hex hash: ", normalizedRequestHash) hmacHexValue := signer.getHmacHash(normalizedRequestHash) - log.Print("hmac hex hash: ", hmacHexValue) if !signer.isAuthorizationHeaderEqualsToCalculatedHmacHex(authorizationHeaderValue, hmacHexValue) { - return errors.New(fmt.Sprintf("wrong authorization header. Got %v want %v", authorizationHeaderValue, hmacHexValue)) + return fmt.Errorf("wrong signature in authorization header. Got %v want %v", authorizationHeaderValue, hmacHexValue) } + + signer.logInfo(req.Context(), "received signature is valid") + return nil } @@ -214,21 +222,22 @@ func (signer *requestSigner) isAuthorizationHeaderEqualsToCalculatedHmacHex(auth return reflect.DeepEqual(authorizationHeaderValue, hmacHexValue) } -func (signer *requestSigner) isAuthorationHeaderValid(req *http.Request) (string, error) { +func (signer *requestSigner) isAuthorizationHeaderValid(req *http.Request) (string, error) { bearerRegex := regexp.MustCompile(`(?m)^(Bearer [[:xdigit:]]+)$`) authorizationHeaderValue := req.Header.Get("Authorization") if authorizationHeaderValue == "" { - return "", errors.New("authorization header missing") + return "", fmt.Errorf("authorization header missing") } if !bearerRegex.MatchString(authorizationHeaderValue) { - return "", errors.New(fmt.Sprintf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue)) + return "", fmt.Errorf("found authorization header is not a valid Bearer token. Got %v", authorizationHeaderValue) } authorizationHeaderValue = strings.TrimPrefix(authorizationHeaderValue, "Bearer ") return authorizationHeaderValue, nil } func (signer *requestSigner) isTimestampValid(req *http.Request) error { - timestampHeaderValue, err := time.Parse(time.RFC3339, req.Header.Get("x-dv-signature-timestamp")) + signer.logInfo(req.Context(), "validate timestamp from request header") + timestampHeaderValue, err := time.Parse(time.RFC3339, req.Header.Get(timestampHeaderKey)) if err != nil { return err } @@ -239,7 +248,7 @@ func (signer *requestSigner) isTimestampValid(req *http.Request) error { timeAfterTimestamp := timeNow.Add(timeDiff) if !(timestampHeaderValue.After(timeBeforTimestamp) && timestampHeaderValue.Before(timeAfterTimestamp)) { - return errors.New(fmt.Sprintf("request is timed out: timestamp from request: %v, current time: %v", timestampHeaderValue.Format(time.RFC3339), timeNow.Format(time.RFC3339))) + return fmt.Errorf("request is timed out: timestamp from request: %v, current time: %v", timestampHeaderValue.Format(time.RFC3339), timeNow.Format(time.RFC3339)) } return nil @@ -247,7 +256,7 @@ func (signer *requestSigner) isTimestampValid(req *http.Request) error { func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) (hex string, err error) { if req.Body == nil { - return "", errors.New("payload missing") + return "", fmt.Errorf("payload missing") } body, err := signer.getBodyFromRequest(req) @@ -261,26 +270,30 @@ func (signer *requestSigner) getHexHashForNormalizedHeaders(req *http.Request) ( normalizedRequest := signer.getNormalizedRequestWithHeaderAndBody(req, signedHeaders, body) strNormalizedRequest := strings.Join(normalizedRequest, "\n") - log.Printf("normalized request: %#v", strNormalizedRequest) + signer.logInfo(req.Context(), fmt.Sprintf("normalized request: %#v", strNormalizedRequest)) hashNormalizedRequest := sha256.Sum256([]byte(strNormalizedRequest)) + signer.logInfo(req.Context(), "hashing normalized request") + return strings.ToLower(fmt.Sprintf("%x", hashNormalizedRequest)), nil } func (signer *requestSigner) getNormalizedRequestWithHeaderAndBody(req *http.Request, signedHeaders []string, body []byte) []string { - normalizedHeaders := []string{} + var normalizedHeaders []string for _, name := range signedHeaders { headerValue := req.Header.Get(name) normalizedHeaders = append(normalizedHeaders, fmt.Sprintf("%v:%v", strings.ToLower(name), strings.TrimSpace(headerValue))) } - normalizedRequest := []string{} + var normalizedRequest []string normalizedRequest = append(normalizedRequest, req.Method) normalizedRequest = append(normalizedRequest, req.URL.Path) normalizedRequest = append(normalizedRequest, req.URL.RawQuery) normalizedRequest = append(normalizedRequest, fmt.Sprintf("%v\n", strings.Join(normalizedHeaders, "\n"))) + + signer.logInfo(req.Context(), "hashing body") normalizedRequest = append(normalizedRequest, signer.getHexHashedPayload(body)) return normalizedRequest @@ -288,12 +301,11 @@ func (signer *requestSigner) getNormalizedRequestWithHeaderAndBody(req *http.Req func (signer *requestSigner) getBodyFromRequest(req *http.Request) ([]byte, error) { if req.GetBody != nil { - log.Print("get copy of request body") + signer.logInfo(req.Context(), "get a copy of request body") - var bodyReader io.Reader bodyReader, err := req.GetBody() if err != nil { - return nil, err + return nil, err } body, err := ioutil.ReadAll(bodyReader) @@ -301,21 +313,22 @@ func (signer *requestSigner) getBodyFromRequest(req *http.Request) ([]byte, erro return nil, err } - return body,nil - } else { - log.Print("request.GetBody is nil. Read body and create new request body (reader) with copy of body") + return body, nil + } - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil,err - } + signer.logInfo(req.Context(), "request.GetBody is nil. Read body and create new request body with read body data") - return body, nil + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err } + + req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + + return body, nil } func (signer *requestSigner) getHexHashedPayload(payload []byte) string { - log.Print("payload: ", string(payload)) hash := sha256.Sum256(payload) return strings.ToLower(fmt.Sprintf("%x", hash)) } @@ -325,4 +338,4 @@ func (signer *requestSigner) getHmacHash(normalizedRequestHash string) string { hmacHash.Write([]byte(normalizedRequestHash)) hmacResult := hmacHash.Sum(nil) return strings.ToLower(fmt.Sprintf("%x", hmacResult)) -} \ No newline at end of file +} diff --git a/requestsignature/requestsignature_test.go b/requestsignature/requestsignature_test.go index a389e05..b512e2a 100644 --- a/requestsignature/requestsignature_test.go +++ b/requestsignature/requestsignature_test.go @@ -2,13 +2,16 @@ package requestsignature_test import ( "bytes" + "context" "encoding/base64" "encoding/json" - "github.com/d-velop/dvelop-sdk-go/requestsignature" + "log" "net/http" "net/http/httptest" "testing" "time" + + "github.com/d-velop/dvelop-sdk-go/requestsignature" ) const timestampHeader = "x-dv-signature-timestamp" @@ -18,17 +21,24 @@ const authorizationHeader = "authorization" const algorithm = "DV1-HMAC-SHA256" +func mockLogInfo(ctx context.Context, msg string) { + log.Printf("INFO: %v", msg) +} +func mockLogError(ctx context.Context, msg string) { + log.Printf("ERROR: %v", msg) +} + func TestRequestSigner_ValidateSignedRequest_HappyPath_Working(t *testing.T) { appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -39,7 +49,7 @@ func TestRequestSigner_ValidateSignedRequest_HappyPath_Working(t *testing.T) { "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -50,7 +60,7 @@ func TestRequestSigner_ValidateSignedRequest_HappyPath_Working(t *testing.T) { req.Header.Add(key, value) } - validator := requestsignature.NewRequestSigner(appSecret,now) + validator := requestsignature.NewRequestSigner(appSecret, now, mockLogInfo) err = validator.ValidateSignedRequest(req) if err != nil { t.Errorf("got error %v but no error expected", err) @@ -58,17 +68,17 @@ func TestRequestSigner_ValidateSignedRequest_HappyPath_Working(t *testing.T) { } func TestRequestSigner_ValidateSignedRequest_AuthorationHeaderInvalid_ReturnError(t *testing.T) { - wantErrorMessage := "wrong authorization header. Got 12783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104d want 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c" + wantErrorMessage := "wrong signature in authorization header. Got 12783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104d want 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c" appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -79,7 +89,7 @@ func TestRequestSigner_ValidateSignedRequest_AuthorationHeaderInvalid_ReturnErro "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 12783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104d", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -90,10 +100,10 @@ func TestRequestSigner_ValidateSignedRequest_AuthorationHeaderInvalid_ReturnErro req.Header.Add(key, value) } - validator := requestsignature.NewRequestSigner(appSecret,now) + validator := requestsignature.NewRequestSigner(appSecret, now, mockLogInfo) err = validator.ValidateSignedRequest(req) if err != nil { - if err.Error()!=wantErrorMessage { + if err.Error() != wantErrorMessage { t.Errorf("wrong error returned: got %v want %v", err, wantErrorMessage) } } else { @@ -102,17 +112,17 @@ func TestRequestSigner_ValidateSignedRequest_AuthorationHeaderInvalid_ReturnErro } func TestRequestSigner_ValidateSignedRequest_WithWrongDto_ReturnError(t *testing.T) { - wantErrorMessage := "wrong authorization header. Got 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c want daba3a1deb11b646540bcb42161ea0003cf6ca6c1c3282d83e8c80e91cfcd9f9" + wantErrorMessage := "wrong signature in authorization header. Got 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c want daba3a1deb11b646540bcb42161ea0003cf6ca6c1c3282d83e8c80e91cfcd9f9" appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://xyz.d-velop.cloud", @@ -123,7 +133,7 @@ func TestRequestSigner_ValidateSignedRequest_WithWrongDto_ReturnError(t *testing "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -134,11 +144,11 @@ func TestRequestSigner_ValidateSignedRequest_WithWrongDto_ReturnError(t *testing req.Header.Add(key, value) } - validator := requestsignature.NewRequestSigner(appSecret,now) + validator := requestsignature.NewRequestSigner(appSecret, now, mockLogInfo) err = validator.ValidateSignedRequest(req) if err != nil { - if err.Error()!=wantErrorMessage { - t.Errorf("wrong error returned: got %v want %v", err, wantErrorMessage) + if err.Error() != wantErrorMessage { + t.Errorf("wrong error returned: got '%v' want '%v'", err, wantErrorMessage) } } else { t.Errorf("no error returned, but want error %v", wantErrorMessage) @@ -152,11 +162,11 @@ func TestRequestSigner_ValidateSignedRequest_RequestTimeouted_ReturnError(t *tes t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,9,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 9, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -167,7 +177,7 @@ func TestRequestSigner_ValidateSignedRequest_RequestTimeouted_ReturnError(t *tes "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -178,10 +188,10 @@ func TestRequestSigner_ValidateSignedRequest_RequestTimeouted_ReturnError(t *tes req.Header.Add(key, value) } - validator := requestsignature.NewRequestSigner(appSecret,now) + validator := requestsignature.NewRequestSigner(appSecret, now, mockLogInfo) err = validator.ValidateSignedRequest(req) if err != nil { - if err.Error()!=wantErrorMessage { + if err.Error() != wantErrorMessage { t.Errorf("wrong error returned: got %v want %v", err, wantErrorMessage) } } else { @@ -195,11 +205,11 @@ func TestHandleSignMiddleware_HappyPath_Working(t *testing.T) { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -210,7 +220,7 @@ func TestHandleSignMiddleware_HappyPath_Working(t *testing.T) { "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -228,7 +238,7 @@ func TestHandleSignMiddleware_HappyPath_Working(t *testing.T) { } rr := httptest.NewRecorder() - requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + requestsignature.HandleCloudSignatureMiddleware(appSecret, now, mockLogInfo, mockLogError)(http.HandlerFunc(handler)).ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusOK) @@ -239,11 +249,11 @@ func TestHandleSignMiddleware_HappyPath_Working(t *testing.T) { } func TestHandleSignMiddleware_AppSecretMissing_Return500InternalServerError(t *testing.T) { - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -254,7 +264,7 @@ func TestHandleSignMiddleware_AppSecretMissing_Return500InternalServerError(t *t "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -272,7 +282,7 @@ func TestHandleSignMiddleware_AppSecretMissing_Return500InternalServerError(t *t } rr := httptest.NewRecorder() - requestsignature.HandleSignMiddleware(nil,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + requestsignature.HandleCloudSignatureMiddleware(nil, now, mockLogInfo, mockLogError)(http.HandlerFunc(handler)).ServeHTTP(rr, req) if status := rr.Code; status != http.StatusInternalServerError { t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusInternalServerError) @@ -287,11 +297,11 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByWrongMethod_Return405MethodNo if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -302,7 +312,7 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByWrongMethod_Return405MethodNo "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -320,7 +330,7 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByWrongMethod_Return405MethodNo } rr := httptest.NewRecorder() - requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + requestsignature.HandleCloudSignatureMiddleware(appSecret, now, mockLogInfo, mockLogError)(http.HandlerFunc(handler)).ServeHTTP(rr, req) if status := rr.Code; status != http.StatusMethodNotAllowed { t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusMethodNotAllowed) @@ -335,11 +345,11 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByPath_Return400BadRequest(t *t if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -350,7 +360,7 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByPath_Return400BadRequest(t *t "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -368,7 +378,7 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByPath_Return400BadRequest(t *t } rr := httptest.NewRecorder() - requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + requestsignature.HandleCloudSignatureMiddleware(appSecret, now, mockLogInfo, mockLogError)(http.HandlerFunc(handler)).ServeHTTP(rr, req) if status := rr.Code; status != http.StatusBadRequest { t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusBadRequest) @@ -378,16 +388,16 @@ func TestHandleSignMiddleware_MiddlewareWasCalledByPath_Return400BadRequest(t *t } } -func TestHandleSignMiddleware_MiddlewareWasCalledWithoutContentTypeHeader_Return400BadRequest(t *testing.T) { +func TestHandleSignMiddleware_MiddlewareWasCalledWithoutContentTypeHeader_Return406NotAcceptable(t *testing.T) { appSecret, err := base64.StdEncoding.DecodeString("Rg9iJXX0Jkun9u4Rp6no8HTNEdHlfX9aZYbFJ9b6YdQ=") if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "id", "https://someone.d-velop.cloud", @@ -416,9 +426,9 @@ func TestHandleSignMiddleware_MiddlewareWasCalledWithoutContentTypeHeader_Return } rr := httptest.NewRecorder() - requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + requestsignature.HandleCloudSignatureMiddleware(appSecret, now, mockLogInfo, mockLogError)(http.HandlerFunc(handler)).ServeHTTP(rr, req) - if status := rr.Code; status != http.StatusBadRequest { + if status := rr.Code; status != http.StatusNotAcceptable { t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusBadRequest) } if handlerCalled { @@ -431,11 +441,11 @@ func TestHandleSignMiddleware_MiddlewareWasCalledButSignatureIsInvalid_Return403 if err != nil { t.Fatalf("app secret string is not valid base64 encoded string. Error = %v", err) } - now := func()time.Time { - return time.Date(2019,time.August,9,8,49,45,0,time.UTC) + now := func() time.Time { + return time.Date(2019, time.August, 9, 8, 49, 45, 0, time.UTC) } - dto := requestsignature.RequestSignatureDto{ + dto := requestsignature.Dto{ "subscribe", "wrong id to generate wrong body hash", "https://someone.d-velop.cloud", @@ -446,7 +456,7 @@ func TestHandleSignMiddleware_MiddlewareWasCalledButSignatureIsInvalid_Return403 "x-dv-signature-algorithm": "DV1-HMAC-SHA256", "x-dv-signature-timestamp": "2019-08-09T08:49:42Z", "Authorization": "Bearer 02783453441665bf27aa465cbbac9b98507ae94c54b6be2b1882fe9a05ec104c", - "Content-Type": "application/json", + "Content-Type": "application/json", } payload := &bytes.Buffer{} @@ -464,7 +474,7 @@ func TestHandleSignMiddleware_MiddlewareWasCalledButSignatureIsInvalid_Return403 } rr := httptest.NewRecorder() - requestsignature.HandleSignMiddleware(appSecret,now)(http.HandlerFunc(handler)).ServeHTTP(rr,req) + requestsignature.HandleCloudSignatureMiddleware(appSecret, now, mockLogInfo, mockLogError)(http.HandlerFunc(handler)).ServeHTTP(rr, req) if status := rr.Code; status != http.StatusForbidden { t.Fatalf("wrong status code returned: got %v want %v", status, http.StatusForbidden) @@ -472,4 +482,4 @@ func TestHandleSignMiddleware_MiddlewareWasCalledButSignatureIsInvalid_Return403 if handlerCalled { t.Fatalf("Handler was called, but should not be called.") } -} \ No newline at end of file +}