Skip to content

Commit 6933c5a

Browse files
authored
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.
1 parent 6dbd63b commit 6933c5a

File tree

10 files changed

+365
-20
lines changed

10 files changed

+365
-20
lines changed

.release-please-manifest-individual.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"auth": "0.0.0",
3+
"auth/oauth2adapt": "0.0.0",
34
"bigquery": "1.55.0",
45
"bigtable": "1.19.0",
56
"datastore": "1.14.0",

auth/oauth2adapt/go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module cloud.google.com/go/auth/oauth2adapt
2+
3+
go 1.19
4+
5+
require (
6+
cloud.google.com/go/auth v0.0.0
7+
github.com/google/go-cmp v0.5.9
8+
golang.org/x/oauth2 v0.11.0
9+
)
10+
11+
require (
12+
github.com/golang/protobuf v1.5.3 // indirect
13+
golang.org/x/net v0.14.0 // indirect
14+
google.golang.org/appengine v1.6.7 // indirect
15+
google.golang.org/protobuf v1.31.0 // indirect
16+
)
17+
18+
// TODO(codyoss): remove this once we have a real release.
19+
replace cloud.google.com/go/auth => ../

auth/oauth2adapt/go.sum

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
3+
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
4+
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
5+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
6+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
7+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
8+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
9+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
10+
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
11+
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
12+
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
13+
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
14+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
15+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
16+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
17+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
18+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
19+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
20+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
21+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
22+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
23+
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
24+
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

auth/oauth2adapt/oauth2adapt.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package oauth2adapt helps converts types used in [cloud.google.com/go/auth]
16+
// and [golang.org/x/oauth2].
17+
package oauth2adapt
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"errors"
23+
24+
"cloud.google.com/go/auth"
25+
"golang.org/x/oauth2"
26+
)
27+
28+
// TokenProviderFromTokenSource converts any [golang.org/x/oauth2.TokenSource]
29+
// into a [cloud.google.com/go/auth.TokenProvider].
30+
func TokenProviderFromTokenSource(ts oauth2.TokenSource) auth.TokenProvider {
31+
return &tokenProviderAdapter{ts: ts}
32+
}
33+
34+
type tokenProviderAdapter struct {
35+
ts oauth2.TokenSource
36+
}
37+
38+
// Token fulfills the [cloud.google.com/go/auth.TokenProvider] interface. It
39+
// is a light wrapper around the underlying TokenSource.
40+
func (tp *tokenProviderAdapter) Token(context.Context) (*auth.Token, error) {
41+
tok, err := tp.ts.Token()
42+
if err != nil {
43+
var err2 *oauth2.RetrieveError
44+
if ok := errors.As(err, &err2); ok {
45+
return nil, AuthErrorFromRetrieveError(err2)
46+
}
47+
return nil, err
48+
}
49+
return &auth.Token{
50+
Value: tok.AccessToken,
51+
Expiry: tok.Expiry,
52+
}, nil
53+
}
54+
55+
// TokenSourceFromTokenProvider converts any
56+
// [cloud.google.com/go/auth.TokenProvider] into a
57+
// [golang.org/x/oauth2.TokenSource].
58+
func TokenSourceFromTokenProvider(tp auth.TokenProvider) oauth2.TokenSource {
59+
return &tokenSourceAdapter{tp: tp}
60+
}
61+
62+
type tokenSourceAdapter struct {
63+
tp auth.TokenProvider
64+
}
65+
66+
// Token fulfills the [golang.org/x/oauth2.TokenSource] interface. It
67+
// is a light wrapper around the underlying TokenProvider.
68+
func (ts *tokenSourceAdapter) Token() (*oauth2.Token, error) {
69+
tok, err := ts.tp.Token(context.Background())
70+
if err != nil {
71+
var err2 *auth.Error
72+
if ok := errors.As(err, &err2); ok {
73+
return nil, AddRetrieveErrorToAuthError(err2)
74+
}
75+
return nil, err
76+
}
77+
return &oauth2.Token{
78+
AccessToken: tok.Value,
79+
Expiry: tok.Expiry,
80+
}, nil
81+
}
82+
83+
type oauth2Error struct {
84+
ErrorCode string `json:"error"`
85+
ErrorDescription string `json:"error_description"`
86+
ErrorURI string `json:"error_uri"`
87+
}
88+
89+
// AddRetrieveErrorToAuthError returns the same error provided and adds a
90+
// [golang.org/x/oauth2.RetrieveError] to the error chain by setting the `Err` field on the
91+
// [cloud.google.com/go/auth.Error].
92+
func AddRetrieveErrorToAuthError(err *auth.Error) *auth.Error {
93+
if err == nil {
94+
return nil
95+
}
96+
e := &oauth2.RetrieveError{
97+
Response: err.Response,
98+
Body: err.Body,
99+
}
100+
err.Err = e
101+
if len(err.Body) > 0 {
102+
var oErr oauth2Error
103+
// ignore the error as it only fills in extra details
104+
json.Unmarshal(err.Body, &oErr)
105+
e.ErrorCode = oErr.ErrorCode
106+
e.ErrorDescription = oErr.ErrorDescription
107+
e.ErrorURI = oErr.ErrorURI
108+
}
109+
return err
110+
}
111+
112+
// AuthErrorFromRetrieveError returns an [cloud.google.com/go/auth.Error] that
113+
// wraps the provided [golang.org/x/oauth2.RetrieveError].
114+
func AuthErrorFromRetrieveError(err *oauth2.RetrieveError) *auth.Error {
115+
if err == nil {
116+
return nil
117+
}
118+
return &auth.Error{
119+
Response: err.Response,
120+
Body: err.Body,
121+
Err: err,
122+
}
123+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package oauth2adapt
16+
17+
import (
18+
"context"
19+
"errors"
20+
"net/http"
21+
"testing"
22+
23+
"cloud.google.com/go/auth"
24+
"github.com/google/go-cmp/cmp"
25+
"golang.org/x/oauth2"
26+
)
27+
28+
func TestTokenProviderFromTokenSource(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
token string
32+
err error
33+
}{
34+
{
35+
name: "working token",
36+
token: "fakeToken",
37+
err: nil,
38+
},
39+
{
40+
name: "coverts err",
41+
err: &oauth2.RetrieveError{
42+
Body: []byte("some bytes"),
43+
ErrorCode: "412",
44+
Response: &http.Response{
45+
StatusCode: http.StatusTeapot,
46+
},
47+
},
48+
},
49+
}
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
tp := TokenProviderFromTokenSource(tokenSource{
53+
token: tt.token,
54+
err: tt.err,
55+
})
56+
tok, err := tp.Token(context.Background())
57+
if tt.err != nil {
58+
aErr := &auth.Error{}
59+
if !errors.As(err, &aErr) {
60+
t.Fatalf("error not of correct type: %T", err)
61+
}
62+
err := tt.err.(*oauth2.RetrieveError)
63+
if !cmp.Equal(aErr.Body, err.Body) {
64+
t.Errorf("got %s, want %s", aErr.Body, err.Body)
65+
}
66+
if !cmp.Equal(aErr.Err, err) {
67+
t.Errorf("got %s, want %s", aErr.Err, err)
68+
}
69+
if !cmp.Equal(aErr.Response, err.Response) {
70+
t.Errorf("got %s, want %s", aErr.Err, err)
71+
}
72+
return
73+
}
74+
if tok.Value != tt.token {
75+
t.Errorf("got %q, want %q", tok.Value, tt.token)
76+
}
77+
})
78+
}
79+
}
80+
81+
func TestTokenSourceFromTokenProvider(t *testing.T) {
82+
tests := []struct {
83+
name string
84+
token string
85+
err error
86+
}{
87+
{
88+
name: "working token",
89+
token: "fakeToken",
90+
err: nil,
91+
},
92+
{
93+
name: "coverts err",
94+
err: &auth.Error{
95+
Body: []byte("some bytes"),
96+
Response: &http.Response{
97+
StatusCode: http.StatusTeapot,
98+
},
99+
},
100+
},
101+
}
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
ts := TokenSourceFromTokenProvider(tokenProvider{
105+
token: tt.token,
106+
err: tt.err,
107+
})
108+
tok, err := ts.Token()
109+
if tt.err != nil {
110+
// Should be able to be an auth.Error
111+
aErr := &auth.Error{}
112+
if !errors.As(err, &aErr) {
113+
t.Fatalf("error not of correct type: %T", err)
114+
}
115+
err := tt.err.(*auth.Error)
116+
if !cmp.Equal(aErr.Body, err.Body) {
117+
t.Errorf("got %s, want %s", aErr.Body, err.Body)
118+
}
119+
if !cmp.Equal(aErr.Response, err.Response) {
120+
t.Errorf("got %s, want %s", aErr.Err, err)
121+
}
122+
123+
// Should be able to be an oauth2.RetrieveError
124+
rErr := &oauth2.RetrieveError{}
125+
if !errors.As(err, &rErr) {
126+
t.Fatalf("error not of correct type: %T", err)
127+
}
128+
if !cmp.Equal(rErr.Body, err.Body) {
129+
t.Errorf("got %s, want %s", aErr.Body, err.Body)
130+
}
131+
if !cmp.Equal(rErr.Response, err.Response) {
132+
t.Errorf("got %s, want %s", aErr.Err, err)
133+
}
134+
return
135+
}
136+
if tok.AccessToken != tt.token {
137+
t.Errorf("got %q, want %q", tok.AccessToken, tt.token)
138+
}
139+
})
140+
}
141+
}
142+
143+
type tokenSource struct {
144+
token string
145+
err error
146+
}
147+
148+
func (ts tokenSource) Token() (*oauth2.Token, error) {
149+
if ts.err != nil {
150+
return nil, ts.err
151+
}
152+
return &oauth2.Token{
153+
AccessToken: ts.token,
154+
}, nil
155+
}
156+
157+
type tokenProvider struct {
158+
token string
159+
err error
160+
}
161+
162+
func (tp tokenProvider) Token(context.Context) (*auth.Token, error) {
163+
if tp.err != nil {
164+
return nil, tp.err
165+
}
166+
return &auth.Token{
167+
Value: tp.token,
168+
}, nil
169+
}

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use (
1919
./asset
2020
./assuredworkloads
2121
./auth
22+
./auth/oauth2adapt
2223
./automl
2324
./baremetalsolution
2425
./batch

internal/generated/snippets/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ require (
131131
cloud.google.com/go/workflows v1.11.1
132132
cloud.google.com/go/workstations v0.0.0-00010101000000-000000000000
133133
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
134-
google.golang.org/api v0.138.0
134+
google.golang.org/api v0.139.0
135135
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d
136136
)
137137

@@ -152,7 +152,7 @@ require (
152152
cloud.google.com/go/dataproc v1.12.0 // indirect
153153
github.com/golang/protobuf v1.5.3 // indirect
154154
github.com/google/go-cmp v0.5.9 // indirect
155-
github.com/google/s2a-go v0.1.5 // indirect
155+
github.com/google/s2a-go v0.1.7 // indirect
156156
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
157157
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
158158
go.opencensus.io v0.24.0 // indirect

0 commit comments

Comments
 (0)