-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth/oauth2adapt): adds a new module to translate types (#8595)
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
Showing
10 changed files
with
365 additions
and
20 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
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 => ../ |
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,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= |
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,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, | ||
} | ||
} |
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,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 | ||
} |
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 |
---|---|---|
|
@@ -19,6 +19,7 @@ use ( | |
./asset | ||
./assuredworkloads | ||
./auth | ||
./auth/oauth2adapt | ||
./automl | ||
./baremetalsolution | ||
./batch | ||
|
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
Oops, something went wrong.