Skip to content

Commit 246e597

Browse files
authored
chore: Deprecate remoteoauth.TokenSource (#2340)
This is still used by plugins, deprecate and fall back to `oauth2.StaticTokenSource` which is the default implementation.
1 parent 4418937 commit 246e597

File tree

4 files changed

+8
-434
lines changed

4 files changed

+8
-434
lines changed

helpers/remoteoauth/token.go

Lines changed: 4 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,19 @@
11
package remoteoauth
22

33
import (
4-
"context"
5-
"errors"
6-
"fmt"
7-
"net/http"
8-
"os"
9-
10-
cloudquery_api "github.com/cloudquery/cloudquery-api-go"
11-
"github.com/google/uuid"
124
"golang.org/x/oauth2"
135
)
146

7+
// NewTokenSource creates a new token source.
8+
// Deprecated: Use oauth2.StaticTokenSource directly instead.
159
func NewTokenSource(opts ...TokenSourceOption) (oauth2.TokenSource, error) {
1610
t := &tokenSource{}
1711
for _, opt := range opts {
1812
opt(t)
1913
}
20-
21-
if _, cloudEnabled := os.LookupEnv("CQ_CLOUD"); !cloudEnabled {
22-
return oauth2.StaticTokenSource(&t.currentToken), nil
23-
}
24-
25-
cloudToken, err := newCloudTokenSource(t.defaultContext)
26-
if err != nil {
27-
return nil, err
28-
}
29-
if t.noWrap {
30-
return cloudToken, nil
31-
}
32-
33-
return oauth2.ReuseTokenSource(nil, cloudToken), nil
14+
return oauth2.StaticTokenSource(&t.currentToken), nil
3415
}
3516

3617
type tokenSource struct {
37-
defaultContext context.Context
38-
currentToken oauth2.Token
39-
noWrap bool
40-
}
41-
42-
type cloudTokenSource struct {
43-
defaultContext context.Context
44-
apiClient *cloudquery_api.ClientWithResponses
45-
46-
apiURL string
47-
apiToken string
48-
teamName string
49-
syncName string
50-
testConnUUID uuid.UUID
51-
syncRunUUID uuid.UUID
52-
connectorUUID uuid.UUID
53-
isTestConnection bool
54-
}
55-
56-
var _ oauth2.TokenSource = (*cloudTokenSource)(nil)
57-
58-
func newCloudTokenSource(defaultContext context.Context) (oauth2.TokenSource, error) {
59-
t := &cloudTokenSource{
60-
defaultContext: defaultContext,
61-
}
62-
if t.defaultContext == nil {
63-
t.defaultContext = context.Background()
64-
}
65-
66-
err := t.initCloudOpts()
67-
if err != nil {
68-
return nil, err
69-
}
70-
71-
t.apiClient, err = cloudquery_api.NewClientWithResponses(t.apiURL,
72-
cloudquery_api.WithRequestEditorFn(func(_ context.Context, req *http.Request) error {
73-
req.Header.Set("Authorization", "Bearer "+t.apiToken)
74-
return nil
75-
}))
76-
if err != nil {
77-
return nil, fmt.Errorf("failed to create api client: %w", err)
78-
}
79-
80-
return t, nil
81-
}
82-
83-
// Token returns a new token from the remote source using the default context.
84-
func (t *cloudTokenSource) Token() (*oauth2.Token, error) {
85-
return t.retrieveToken(t.defaultContext)
86-
}
87-
88-
func (t *cloudTokenSource) retrieveToken(ctx context.Context) (*oauth2.Token, error) {
89-
var oauthResp *cloudquery_api.ConnectorCredentialsResponseOAuth
90-
if !t.isTestConnection {
91-
resp, err := t.apiClient.GetSyncRunConnectorCredentialsWithResponse(ctx, t.teamName, t.syncName, t.syncRunUUID, t.connectorUUID)
92-
if err != nil {
93-
return nil, fmt.Errorf("failed to get sync run connector credentials: %w", err)
94-
}
95-
if resp.StatusCode() != http.StatusOK {
96-
if resp.JSON422 != nil {
97-
return nil, fmt.Errorf("failed to get sync run connector credentials: %s", resp.JSON422.Message)
98-
}
99-
return nil, fmt.Errorf("failed to get sync run connector credentials: %s", resp.Status())
100-
}
101-
oauthResp = resp.JSON200.Oauth
102-
} else {
103-
resp, err := t.apiClient.GetTestConnectionConnectorCredentialsWithResponse(ctx, t.teamName, t.testConnUUID, t.connectorUUID)
104-
if err != nil {
105-
return nil, fmt.Errorf("failed to get test connection connector credentials: %w", err)
106-
}
107-
if resp.StatusCode() != http.StatusOK {
108-
if resp.JSON422 != nil {
109-
return nil, fmt.Errorf("failed to get test connection connector credentials: %s", resp.JSON422.Message)
110-
}
111-
return nil, fmt.Errorf("failed to get test connection connector credentials: %s", resp.Status())
112-
}
113-
oauthResp = resp.JSON200.Oauth
114-
}
115-
116-
if oauthResp == nil {
117-
return nil, errors.New("missing oauth credentials in response")
118-
}
119-
120-
tok := &oauth2.Token{
121-
AccessToken: oauthResp.AccessToken,
122-
}
123-
if oauthResp.Expires != nil {
124-
tok.Expiry = *oauthResp.Expires
125-
}
126-
return tok, nil
127-
}
128-
129-
func (t *cloudTokenSource) initCloudOpts() error {
130-
var allErr error
131-
132-
t.apiToken = os.Getenv("CLOUDQUERY_API_KEY")
133-
if t.apiToken == "" {
134-
allErr = errors.Join(allErr, errors.New("CLOUDQUERY_API_KEY missing"))
135-
}
136-
t.apiURL = os.Getenv("CLOUDQUERY_API_URL")
137-
if t.apiURL == "" {
138-
t.apiURL = "https://api.cloudquery.io"
139-
}
140-
141-
t.teamName = os.Getenv("_CQ_TEAM_NAME")
142-
if t.teamName == "" {
143-
allErr = errors.Join(allErr, errors.New("_CQ_TEAM_NAME missing"))
144-
}
145-
t.syncName = os.Getenv("_CQ_SYNC_NAME")
146-
syncRunID := os.Getenv("_CQ_SYNC_RUN_ID")
147-
testConnID := os.Getenv("_CQ_SYNC_TEST_CONNECTION_ID")
148-
if testConnID == "" && syncRunID == "" {
149-
allErr = errors.Join(allErr, errors.New("_CQ_SYNC_TEST_CONNECTION_ID or _CQ_SYNC_RUN_ID missing"))
150-
} else if testConnID != "" && syncRunID != "" {
151-
allErr = errors.Join(allErr, errors.New("_CQ_SYNC_TEST_CONNECTION_ID and _CQ_SYNC_RUN_ID are mutually exclusive"))
152-
}
153-
154-
var err error
155-
if syncRunID != "" {
156-
if t.syncName == "" {
157-
allErr = errors.Join(allErr, errors.New("_CQ_SYNC_NAME missing"))
158-
}
159-
160-
t.syncRunUUID, err = uuid.Parse(syncRunID)
161-
if err != nil {
162-
allErr = errors.Join(allErr, fmt.Errorf("_CQ_SYNC_RUN_ID is not a valid UUID: %w", err))
163-
}
164-
}
165-
if testConnID != "" {
166-
if t.syncName != "" {
167-
allErr = errors.Join(allErr, errors.New("_CQ_SYNC_NAME should be empty"))
168-
}
169-
170-
t.testConnUUID, err = uuid.Parse(testConnID)
171-
if err != nil {
172-
allErr = errors.Join(allErr, fmt.Errorf("_CQ_SYNC_TEST_CONNECTION_ID is not a valid UUID: %w", err))
173-
}
174-
t.isTestConnection = true
175-
}
176-
177-
connectorID := os.Getenv("_CQ_CONNECTOR_ID")
178-
if connectorID == "" {
179-
allErr = errors.Join(allErr, errors.New("_CQ_CONNECTOR_ID missing"))
180-
} else {
181-
t.connectorUUID, err = uuid.Parse(connectorID)
182-
if err != nil {
183-
allErr = errors.Join(allErr, fmt.Errorf("_CQ_CONNECTOR_ID is not a valid UUID: %w", err))
184-
}
185-
}
186-
return allErr
18+
currentToken oauth2.Token
18719
}

helpers/remoteoauth/token_test.go

Lines changed: 0 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
package remoteoauth
22

33
import (
4-
"net/http"
5-
"net/http/httptest"
64
"os"
75
"testing"
86
"time"
97

10-
"github.com/google/uuid"
118
"github.com/stretchr/testify/require"
129
"golang.org/x/oauth2"
1310
)
1411

15-
const testAPIKey = "test-key"
16-
1712
func TestLocalTokenAccess(t *testing.T) {
1813
r := require.New(t)
1914
_, cloud := os.LookupEnv("CQ_CLOUD")
@@ -37,126 +32,3 @@ func TestLocalTokenAccessWithDeprecatedTokenOpt(t *testing.T) {
3732
r.True(tk.Valid())
3833
r.Equal("token", tk.AccessToken)
3934
}
40-
41-
func TestFirstLocalTokenAccess(t *testing.T) {
42-
runID := uuid.NewString()
43-
connID := uuid.NewString()
44-
testURL := setupMockTokenServer(t, map[string]string{
45-
"/teams/the-team/syncs/the-sync/runs/" + runID + "/connector/" + connID + "/credentials": `{"oauth":{"access_token":"new-token"}}`,
46-
})
47-
setEnvs(t, map[string]string{
48-
"CQ_CLOUD": "1",
49-
"CLOUDQUERY_API_URL": testURL,
50-
"CLOUDQUERY_API_KEY": testAPIKey,
51-
"_CQ_TEAM_NAME": "the-team",
52-
"_CQ_SYNC_NAME": "the-sync",
53-
"_CQ_SYNC_RUN_ID": runID,
54-
"_CQ_CONNECTOR_ID": connID,
55-
})
56-
r := require.New(t)
57-
tok, err := NewTokenSource(WithToken(oauth2.Token{AccessToken: "token"}))
58-
r.NoError(err)
59-
tk, err := tok.Token()
60-
r.NoError(err)
61-
r.True(tk.Valid())
62-
r.Equal("new-token", tk.AccessToken)
63-
}
64-
65-
func TestInvalidAPIKeyTokenAccess(t *testing.T) {
66-
runID := uuid.NewString()
67-
connID := uuid.NewString()
68-
testURL := setupMockTokenServer(t, nil)
69-
setEnvs(t, map[string]string{
70-
"CQ_CLOUD": "1",
71-
"CLOUDQUERY_API_URL": testURL,
72-
"CLOUDQUERY_API_KEY": "invalid",
73-
"_CQ_TEAM_NAME": "the-team",
74-
"_CQ_SYNC_NAME": "the-sync",
75-
"_CQ_SYNC_RUN_ID": runID,
76-
"_CQ_CONNECTOR_ID": connID,
77-
})
78-
r := require.New(t)
79-
tok, err := NewTokenSource(WithToken(oauth2.Token{AccessToken: "token"}))
80-
r.NoError(err)
81-
tk, err := tok.Token()
82-
r.Nil(tk)
83-
r.False(tk.Valid())
84-
r.ErrorContains(err, "failed to get sync run connector credentials")
85-
}
86-
87-
func TestSyncRunTokenAccess(t *testing.T) {
88-
runID := uuid.NewString()
89-
connID := uuid.NewString()
90-
testURL := setupMockTokenServer(t, map[string]string{
91-
"/teams/the-team/syncs/the-sync/runs/" + runID + "/connector/" + connID + "/credentials": `{"oauth":{"access_token":"new-token"}}`,
92-
})
93-
setEnvs(t, map[string]string{
94-
"CQ_CLOUD": "1",
95-
"CLOUDQUERY_API_URL": testURL,
96-
"CLOUDQUERY_API_KEY": testAPIKey,
97-
"_CQ_TEAM_NAME": "the-team",
98-
"_CQ_SYNC_NAME": "the-sync",
99-
"_CQ_SYNC_RUN_ID": runID,
100-
"_CQ_CONNECTOR_ID": connID,
101-
})
102-
r := require.New(t)
103-
tok, err := NewTokenSource()
104-
r.NoError(err)
105-
tk, err := tok.Token()
106-
r.NoError(err)
107-
r.True(tk.Valid())
108-
r.Equal("new-token", tk.AccessToken)
109-
}
110-
111-
func TestTestConnectionTokenAccess(t *testing.T) {
112-
testID := uuid.NewString()
113-
connID := uuid.NewString()
114-
testURL := setupMockTokenServer(t, map[string]string{
115-
"/teams/the-team/syncs/test-connections/" + testID + "/connector/" + connID + "/credentials": `{"oauth":{"access_token":"new-token"}}`,
116-
})
117-
setEnvs(t, map[string]string{
118-
"CQ_CLOUD": "1",
119-
"CLOUDQUERY_API_URL": testURL,
120-
"CLOUDQUERY_API_KEY": testAPIKey,
121-
"_CQ_TEAM_NAME": "the-team",
122-
"_CQ_SYNC_TEST_CONNECTION_ID": testID,
123-
"_CQ_CONNECTOR_ID": connID,
124-
})
125-
r := require.New(t)
126-
tok, err := NewTokenSource(WithToken(oauth2.Token{AccessToken: "token"}))
127-
r.NoError(err)
128-
tk, err := tok.Token()
129-
r.NoError(err)
130-
r.True(tk.Valid())
131-
r.Equal("new-token", tk.AccessToken)
132-
}
133-
134-
func setEnvs(t *testing.T, envs map[string]string) {
135-
t.Helper()
136-
for k, v := range envs {
137-
t.Setenv(k, v)
138-
}
139-
}
140-
141-
func setupMockTokenServer(t *testing.T, responses map[string]string) string {
142-
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
143-
if a := r.Header.Get("Authorization"); a != "Bearer "+testAPIKey {
144-
w.WriteHeader(http.StatusUnauthorized)
145-
return
146-
}
147-
148-
resp, ok := responses[r.URL.Path]
149-
if !ok {
150-
w.WriteHeader(http.StatusNotFound)
151-
return
152-
}
153-
154-
w.Header().Set("Content-Type", "application/json")
155-
w.WriteHeader(http.StatusOK)
156-
_, _ = w.Write([]byte(resp))
157-
}))
158-
t.Cleanup(func() {
159-
ts.Close()
160-
})
161-
return ts.URL
162-
}

helpers/remoteoauth/tokenoptions.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,15 @@ func WithAccessToken(token, tokenType string, expiry time.Time) TokenSourceOptio
2222
}
2323

2424
// WithToken sets the default token for the token source.
25+
// Deprecated: Use oauth2.StaticTokenSource directly instead.
2526
func WithToken(token oauth2.Token) TokenSourceOption {
2627
return func(t *tokenSource) {
2728
t.currentToken = token
2829
}
2930
}
3031

3132
// WithDefaultContext sets the default context for the token source, used when creating a new token request.
32-
func WithDefaultContext(ctx context.Context) TokenSourceOption {
33-
return func(t *tokenSource) {
34-
t.defaultContext = ctx
35-
}
36-
}
37-
38-
func withNoWrap() TokenSourceOption {
39-
return func(t *tokenSource) {
40-
t.noWrap = true
41-
}
33+
// Deprecated: not used in the current implementation.
34+
func WithDefaultContext(_ context.Context) TokenSourceOption {
35+
return func(*tokenSource) {}
4236
}

0 commit comments

Comments
 (0)