diff --git a/.github/workflows/sync_upstream.yaml b/.github/workflows/sync_upstream.yaml new file mode 100644 index 000000000..81130113a --- /dev/null +++ b/.github/workflows/sync_upstream.yaml @@ -0,0 +1,53 @@ +# for reference: https://stackoverflow.com/questions/23793062/can-forks-be-synced-automatically-in-github +# .github/workflows/example.yml + +name: Sync upstream oauth2 repo + +on: + workflow_dispatch: + schedule: + - cron: '0 9 * * 1' + # run once a week on monday morning + # if it fails + # send slack message (iw-engineering or integration-inovators) + + +permissions: + contents: write + +jobs: + merge: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Merge upstream + run: | + git config --global user.name 'upstream-sync-bot' + git config --global user.email 'upstream-sync-bot@users.noreply.github.com' + git config --global push.followTags true + + git pull + + git remote add upstream https://github.com/golang/oauth2.git + git fetch --tags upstream + + git checkout master + git merge --allow-unrelated-histories --no-edit upstream/master + git push origin master + + - name: On Fail, Send Message About Merge to Slack + if: ${{ failure() }} + uses: archive/github-actions-slack@v2.8.0 + id: notify-slack + env: + ACTIONS_STEP_DEBUG: true + ACTIONS_RUNNER_DEBUG: true + with: + slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_TOKEN }} + slack-channel: ${{ secrets.SLACK_BOT_INTEGRATION_INNOVATORS_CHANNEL }} #USE CHANNEL ID, NOT CHANNEL NAME, SINCE ID IS USED IN NEW SLACK API's + slack-text: | + OAuth2 fork upstream sync failed. [action](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + - name: Send Slack Message Result + if: ${{ failure() }} + run: echo "${{ steps.notify-slack.outputs.slack-result }}" diff --git a/clientcredentials/clientcredentials.go b/clientcredentials/clientcredentials.go index 2459d069f..3bfcf690e 100644 --- a/clientcredentials/clientcredentials.go +++ b/clientcredentials/clientcredentials.go @@ -20,6 +20,7 @@ import ( "net/url" "strings" + "github.com/aws/aws-xray-sdk-go/xray" "golang.org/x/oauth2" "golang.org/x/oauth2/internal" ) @@ -67,10 +68,31 @@ func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) { // is returned. See the oauth2.HTTPClient variable. // // The returned Client and its Transport should not be modified. +// +// Deprecated: Client exists for historical compatibility and should not be +// used. It is recommended to use ClientWithXRay instead, as it provides much better visibility into the client's behavior. func (c *Config) Client(ctx context.Context) *http.Client { return oauth2.NewClient(ctx, c.TokenSource(ctx)) } +// ClientWithXRay returns an HTTP client using the provided token with an attached XRay client.. +// The token will auto-refresh as necessary. +// +// The provided context optionally controls which HTTP client +// is returned. See the oauth2.HTTPClient variable. +// +// The returned Client and its Transport should not be modified. +func (c *Config) ClientWithXRay(ctx context.Context, hc *http.Client) *http.Client { + if hc == nil { + hc = http.DefaultClient + } + + client := xray.Client(hc) + ctx = context.WithValue(ctx, oauth2.HTTPClient, client) + + return oauth2.NewClient(ctx, c.TokenSource(ctx)) +} + // TokenSource returns a TokenSource that returns t until t expires, // automatically refreshing it as necessary using the provided context and the // client ID and client secret. diff --git a/go.mod b/go.mod index d73aa6996..b59e5ce8a 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,25 @@ go 1.18 require ( cloud.google.com/go/compute/metadata v0.3.0 + github.com/aws/aws-xray-sdk-go v1.8.3 github.com/google/go-cmp v0.5.9 + google.golang.org/appengine v1.6.7 +) + +require ( + cloud.google.com/go/compute v1.23.0 // indirect + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/aws/aws-sdk-go v1.47.9 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index 0c9052866..e9f3a338b 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,64 @@ +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/aws/aws-sdk-go v1.47.9 h1:rarTsos0mA16q+huicGx0e560aYRtOucV5z2Mw23JRY= +github.com/aws/aws-sdk-go v1.47.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-xray-sdk-go v1.8.3 h1:S8GdgVncBRhzbNnNUgTPwhEqhwt2alES/9rLASyhxjU= +github.com/aws/aws-xray-sdk-go v1.8.3/go.mod h1:tv8uLMOSCABolrIF8YCcp3ghyswArsan8dfLCA1ZATk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/token.go b/internal/token.go index e83ddeef0..04b7db16b 100644 --- a/internal/token.go +++ b/internal/token.go @@ -196,7 +196,9 @@ func newTokenRequest(tokenURL, clientID, clientSecret string, v url.Values, auth } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if authStyle == AuthStyleInHeader { - req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret)) + // NOTE: This should be query-escaped, however the Aviva OAuth + // server does not accept query-escaped credentials. + req.SetBasicAuth(clientID, clientSecret) } return req, nil } diff --git a/jwt/jwt.go b/jwt/jwt.go index b2bf18298..a01f02cce 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -10,6 +10,7 @@ package jwt import ( "context" + "crypto/rsa" "encoding/json" "fmt" "io" @@ -29,6 +30,8 @@ var ( defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"} ) +type Signer = jws.Signer + // Config is the configuration for using JWT to fetch tokens, // commonly known as "two-legged OAuth 2.0". type Config struct { @@ -46,6 +49,11 @@ type Config struct { // PrivateKey []byte + // SignerProvider is a function that is used to create a Signer from the + // PrivateKeyID which is then used to sign JWT payloads. This takes + // precedence over default signer using the PrivateKey. + SignerProvider func(privateKeyID string) (Signer, error) + // PrivateKeyID contains an optional hint indicating which key is being // used. PrivateKeyID string @@ -99,10 +107,6 @@ type jwtSource struct { } func (js jwtSource) Token() (*oauth2.Token, error) { - pk, err := internal.ParseKey(js.conf.PrivateKey) - if err != nil { - return nil, err - } hc := oauth2.NewClient(js.ctx, nil) claimSet := &jws.ClaimSet{ Iss: js.conf.Email, @@ -124,7 +128,23 @@ func (js jwtSource) Token() (*oauth2.Token, error) { } h := *defaultHeader h.KeyID = js.conf.PrivateKeyID - payload, err := jws.Encode(&h, claimSet, pk) + var err error + payload := "" + if js.conf.SignerProvider == nil { + var pk *rsa.PrivateKey + pk, err = internal.ParseKey(js.conf.PrivateKey) + if err != nil { + return nil, err + } + payload, err = jws.Encode(&h, claimSet, pk) + } else { + var signer jws.Signer + signer, err = js.conf.SignerProvider(h.KeyID) + if err != nil { + return nil, err + } + payload, err = jws.EncodeWithSigner(&h, claimSet, signer) + } if err != nil { return nil, err } diff --git a/oauth2_test.go b/oauth2_test.go index 37f0580d7..2a9d09273 100644 --- a/oauth2_test.go +++ b/oauth2_test.go @@ -74,7 +74,7 @@ func TestAuthCodeURL_Optional(t *testing.T) { func TestURLUnsafeClientConfig(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if got, want := r.Header.Get("Authorization"), "Basic Q0xJRU5UX0lEJTNGJTNGOkNMSUVOVF9TRUNSRVQlM0YlM0Y="; got != want { + if got, want := r.Header.Get("Authorization"), "Basic Q0xJRU5UX0lEPz86Q0xJRU5UX1NFQ1JFVD8/"; got != want { t.Errorf("Authorization header = %q; want %q", got, want) }