-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for OAuth2 in HTTP target
Inspired by official OAuth2 [RFC](https://www.rfc-editor.org/rfc/rfc6749) and example API provided by [Google Ads](https://developers.google.com/google-ads/api/rest/auth)
- Loading branch information
Showing
4 changed files
with
199 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/** | ||
* Copyright (c) 2020-present Snowplow Analytics Ltd. | ||
* All rights reserved. | ||
* | ||
* This software is made available by Snowplow Analytics, Ltd., | ||
* under the terms of the Snowplow Limited Use License Agreement, Version 1.0 | ||
* located at https://docs.snowplow.io/limited-use-license-1.0 | ||
* BY INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY PORTION | ||
* OF THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT. | ||
*/ | ||
|
||
package target | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/snowplow/snowbridge/pkg/models" | ||
"github.com/snowplow/snowbridge/pkg/testutil" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// that's what we configure in our target | ||
const validClientId = "CLIENT_ID_TEST" | ||
const validClientSecret = "CLIENT_SECRET_TEST" | ||
const validRefreshToken = "REFRESH_TOKEN_TEST" | ||
const validGrantType = "refresh_token" | ||
|
||
// that's what is returned by mock token server and used as bearer token to authorize request to target server | ||
const validAccessToken = "super_secret_access_token" | ||
|
||
// This is mock server providing us the bearer access token. If you provide invalid details/something is misconfigured you get 400 HTTP status | ||
func tokenServer() *httptest.Server { | ||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
req.ParseForm() | ||
clientId, clientSecret, _ := req.BasicAuth() | ||
refreshToken := req.Form.Get("refresh_token") | ||
grantType := req.Form.Get("grant_type") | ||
|
||
if clientId == validClientId && clientSecret == validClientSecret && refreshToken == validRefreshToken && grantType == validGrantType { | ||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(200) | ||
fmt.Fprintf(w, `{"access_token":"%s", "expires_in":3600}`, validAccessToken) | ||
} else { | ||
w.WriteHeader(400) | ||
fmt.Fprintf(w, `{"error":"invalid_client"}`) | ||
} | ||
})) | ||
} | ||
|
||
// This is mock target server which requires us to provide valid access token. Without valid token you set 403 HTTP status | ||
func targetServer() *httptest.Server { | ||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
if req.Header.Get("Authorization") == fmt.Sprintf("Bearer %s", validAccessToken) { | ||
w.WriteHeader(200) | ||
} else { | ||
w.WriteHeader(403) | ||
fmt.Fprintf(w, "Invalid access token") | ||
} | ||
})) | ||
} | ||
|
||
func TestHttpOAuth2_Success(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
writeResult, err := runTest(t, validClientId, validClientSecret, validRefreshToken) | ||
|
||
assert.Nil(err) | ||
assert.Equal(1, len(writeResult.Sent)) | ||
assert.Equal(0, len(writeResult.Failed)) | ||
} | ||
|
||
func TestHttpOAuth2_CanNotFetchToken(t *testing.T) { | ||
testCases := []struct { | ||
Name string | ||
InputClientId string | ||
InputClientSecret string | ||
InputRefreshToken string | ||
}{ | ||
{Name: "Invalid client id", InputClientId: "INVALID", InputClientSecret: validClientSecret, InputRefreshToken: validRefreshToken}, | ||
{Name: "Invalid client secret", InputClientId: validClientId, InputClientSecret: "INVALID", InputRefreshToken: validRefreshToken}, | ||
{Name: "Invalid refresh token", InputClientId: validClientId, InputClientSecret: validClientSecret, InputRefreshToken: "INVALID"}, | ||
} | ||
|
||
for _, tt := range testCases { | ||
t.Run(tt.Name, func(t *testing.T) { | ||
assert := assert.New(t) | ||
writeResult, err := runTest(t, tt.InputClientId, tt.InputClientSecret, tt.InputRefreshToken) | ||
|
||
assert.NotNil(err) | ||
assert.Contains(err.Error(), `{"error":"invalid_client"}`) | ||
assert.Equal(0, len(writeResult.Sent)) | ||
assert.Equal(1, len(writeResult.Failed)) | ||
}) | ||
} | ||
} | ||
|
||
func TestHttpOAuth2_CallTargetWithoutToken(t *testing.T) { | ||
assert := assert.New(t) | ||
writeResult, err := runTest(t, "", "", "") | ||
|
||
assert.NotNil(err) | ||
assert.Contains(err.Error(), `Got response status: 403 Forbidden`) | ||
assert.Equal(0, len(writeResult.Sent)) | ||
assert.Equal(1, len(writeResult.Failed)) | ||
} | ||
|
||
func runTest(t *testing.T, inputClientId string, inputClientSecret string, inputRefreshToken string) (*models.TargetWriteResult, error) { | ||
tokenServer := tokenServer() | ||
server := targetServer() | ||
defer tokenServer.Close() | ||
defer server.Close() | ||
|
||
target := oauth2Target(t, server.URL, inputClientId, inputClientSecret, inputRefreshToken, tokenServer.URL) | ||
|
||
message := testutil.GetTestMessages(1, "Hello Server!!", func() {}) | ||
return target.Write(message) | ||
} | ||
|
||
func oauth2Target(t *testing.T, targetUrl string, inputClientId string, inputClientSecret string, inputRefreshToken string, tokenServerUrl string) *HTTPTarget { | ||
target, err := newHTTPTarget(targetUrl, 5, 1048576, "application/json", "", "", "", "", "", "", true, false, inputClientId, inputClientSecret, inputRefreshToken, tokenServerUrl) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
return target | ||
} |