Skip to content

Commit

Permalink
feat(auth/oauth2adapt): adds a new module to translate types (#8595)
Browse files Browse the repository at this point in the history
This package can be used by us to make sure we preserve expected
strongly typed errors in chains. It can also be used by customers
and other libs to easily convert between the two equivalent
interfaces between the new and old world libraries.
  • Loading branch information
codyoss authored Sep 29, 2023
1 parent 6dbd63b commit 6933c5a
Show file tree
Hide file tree
Showing 10 changed files with 365 additions and 20 deletions.
1 change: 1 addition & 0 deletions .release-please-manifest-individual.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"auth": "0.0.0",
"auth/oauth2adapt": "0.0.0",
"bigquery": "1.55.0",
"bigtable": "1.19.0",
"datastore": "1.14.0",
Expand Down
19 changes: 19 additions & 0 deletions auth/oauth2adapt/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module cloud.google.com/go/auth/oauth2adapt

go 1.19

require (
cloud.google.com/go/auth v0.0.0
github.com/google/go-cmp v0.5.9
golang.org/x/oauth2 v0.11.0
)

require (
github.com/golang/protobuf v1.5.3 // indirect
golang.org/x/net v0.14.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

// TODO(codyoss): remove this once we have a real release.
replace cloud.google.com/go/auth => ../
24 changes: 24 additions & 0 deletions auth/oauth2adapt/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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=
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.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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/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/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=
123 changes: 123 additions & 0 deletions auth/oauth2adapt/oauth2adapt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package oauth2adapt helps converts types used in [cloud.google.com/go/auth]
// and [golang.org/x/oauth2].
package oauth2adapt

import (
"context"
"encoding/json"
"errors"

"cloud.google.com/go/auth"
"golang.org/x/oauth2"
)

// TokenProviderFromTokenSource converts any [golang.org/x/oauth2.TokenSource]
// into a [cloud.google.com/go/auth.TokenProvider].
func TokenProviderFromTokenSource(ts oauth2.TokenSource) auth.TokenProvider {
return &tokenProviderAdapter{ts: ts}
}

type tokenProviderAdapter struct {
ts oauth2.TokenSource
}

// Token fulfills the [cloud.google.com/go/auth.TokenProvider] interface. It
// is a light wrapper around the underlying TokenSource.
func (tp *tokenProviderAdapter) Token(context.Context) (*auth.Token, error) {
tok, err := tp.ts.Token()
if err != nil {
var err2 *oauth2.RetrieveError
if ok := errors.As(err, &err2); ok {
return nil, AuthErrorFromRetrieveError(err2)
}
return nil, err
}
return &auth.Token{
Value: tok.AccessToken,
Expiry: tok.Expiry,
}, nil
}

// TokenSourceFromTokenProvider converts any
// [cloud.google.com/go/auth.TokenProvider] into a
// [golang.org/x/oauth2.TokenSource].
func TokenSourceFromTokenProvider(tp auth.TokenProvider) oauth2.TokenSource {
return &tokenSourceAdapter{tp: tp}
}

type tokenSourceAdapter struct {
tp auth.TokenProvider
}

// Token fulfills the [golang.org/x/oauth2.TokenSource] interface. It
// is a light wrapper around the underlying TokenProvider.
func (ts *tokenSourceAdapter) Token() (*oauth2.Token, error) {
tok, err := ts.tp.Token(context.Background())
if err != nil {
var err2 *auth.Error
if ok := errors.As(err, &err2); ok {
return nil, AddRetrieveErrorToAuthError(err2)
}
return nil, err
}
return &oauth2.Token{
AccessToken: tok.Value,
Expiry: tok.Expiry,
}, nil
}

type oauth2Error struct {
ErrorCode string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorURI string `json:"error_uri"`
}

// AddRetrieveErrorToAuthError returns the same error provided and adds a
// [golang.org/x/oauth2.RetrieveError] to the error chain by setting the `Err` field on the
// [cloud.google.com/go/auth.Error].
func AddRetrieveErrorToAuthError(err *auth.Error) *auth.Error {
if err == nil {
return nil
}
e := &oauth2.RetrieveError{
Response: err.Response,
Body: err.Body,
}
err.Err = e
if len(err.Body) > 0 {
var oErr oauth2Error
// ignore the error as it only fills in extra details
json.Unmarshal(err.Body, &oErr)
e.ErrorCode = oErr.ErrorCode
e.ErrorDescription = oErr.ErrorDescription
e.ErrorURI = oErr.ErrorURI
}
return err
}

// AuthErrorFromRetrieveError returns an [cloud.google.com/go/auth.Error] that
// wraps the provided [golang.org/x/oauth2.RetrieveError].
func AuthErrorFromRetrieveError(err *oauth2.RetrieveError) *auth.Error {
if err == nil {
return nil
}
return &auth.Error{
Response: err.Response,
Body: err.Body,
Err: err,
}
}
169 changes: 169 additions & 0 deletions auth/oauth2adapt/oauth2adapt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oauth2adapt

import (
"context"
"errors"
"net/http"
"testing"

"cloud.google.com/go/auth"
"github.com/google/go-cmp/cmp"
"golang.org/x/oauth2"
)

func TestTokenProviderFromTokenSource(t *testing.T) {
tests := []struct {
name string
token string
err error
}{
{
name: "working token",
token: "fakeToken",
err: nil,
},
{
name: "coverts err",
err: &oauth2.RetrieveError{
Body: []byte("some bytes"),
ErrorCode: "412",
Response: &http.Response{
StatusCode: http.StatusTeapot,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tp := TokenProviderFromTokenSource(tokenSource{
token: tt.token,
err: tt.err,
})
tok, err := tp.Token(context.Background())
if tt.err != nil {
aErr := &auth.Error{}
if !errors.As(err, &aErr) {
t.Fatalf("error not of correct type: %T", err)
}
err := tt.err.(*oauth2.RetrieveError)
if !cmp.Equal(aErr.Body, err.Body) {
t.Errorf("got %s, want %s", aErr.Body, err.Body)
}
if !cmp.Equal(aErr.Err, err) {
t.Errorf("got %s, want %s", aErr.Err, err)
}
if !cmp.Equal(aErr.Response, err.Response) {
t.Errorf("got %s, want %s", aErr.Err, err)
}
return
}
if tok.Value != tt.token {
t.Errorf("got %q, want %q", tok.Value, tt.token)
}
})
}
}

func TestTokenSourceFromTokenProvider(t *testing.T) {
tests := []struct {
name string
token string
err error
}{
{
name: "working token",
token: "fakeToken",
err: nil,
},
{
name: "coverts err",
err: &auth.Error{
Body: []byte("some bytes"),
Response: &http.Response{
StatusCode: http.StatusTeapot,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := TokenSourceFromTokenProvider(tokenProvider{
token: tt.token,
err: tt.err,
})
tok, err := ts.Token()
if tt.err != nil {
// Should be able to be an auth.Error
aErr := &auth.Error{}
if !errors.As(err, &aErr) {
t.Fatalf("error not of correct type: %T", err)
}
err := tt.err.(*auth.Error)
if !cmp.Equal(aErr.Body, err.Body) {
t.Errorf("got %s, want %s", aErr.Body, err.Body)
}
if !cmp.Equal(aErr.Response, err.Response) {
t.Errorf("got %s, want %s", aErr.Err, err)
}

// Should be able to be an oauth2.RetrieveError
rErr := &oauth2.RetrieveError{}
if !errors.As(err, &rErr) {
t.Fatalf("error not of correct type: %T", err)
}
if !cmp.Equal(rErr.Body, err.Body) {
t.Errorf("got %s, want %s", aErr.Body, err.Body)
}
if !cmp.Equal(rErr.Response, err.Response) {
t.Errorf("got %s, want %s", aErr.Err, err)
}
return
}
if tok.AccessToken != tt.token {
t.Errorf("got %q, want %q", tok.AccessToken, tt.token)
}
})
}
}

type tokenSource struct {
token string
err error
}

func (ts tokenSource) Token() (*oauth2.Token, error) {
if ts.err != nil {
return nil, ts.err
}
return &oauth2.Token{
AccessToken: ts.token,
}, nil
}

type tokenProvider struct {
token string
err error
}

func (tp tokenProvider) Token(context.Context) (*auth.Token, error) {
if tp.err != nil {
return nil, tp.err
}
return &auth.Token{
Value: tp.token,
}, nil
}
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use (
./asset
./assuredworkloads
./auth
./auth/oauth2adapt
./automl
./baremetalsolution
./batch
Expand Down
4 changes: 2 additions & 2 deletions internal/generated/snippets/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ require (
cloud.google.com/go/workflows v1.11.1
cloud.google.com/go/workstations v0.0.0-00010101000000-000000000000
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
google.golang.org/api v0.138.0
google.golang.org/api v0.139.0
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d
)

Expand All @@ -152,7 +152,7 @@ require (
cloud.google.com/go/dataproc v1.12.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.5 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
go.opencensus.io v0.24.0 // indirect
Expand Down
Loading

0 comments on commit 6933c5a

Please sign in to comment.