Skip to content

Commit 4d80050

Browse files
authored
CLOUDP-330236: Adds NewServiceAccountTransport (#4075)
1 parent f9dd12d commit 4d80050

File tree

4 files changed

+108
-1
lines changed

4 files changed

+108
-1
lines changed

build/ci/library_owners.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"go.uber.org/mock": "apix-2",
5252
"golang.org/x/mod": "apix-2",
5353
"golang.org/x/net": "apix-2",
54+
"golang.org/x/oauth2": "apix-2",
5455
"golang.org/x/sys": "apix-2",
5556
"golang.org/x/tools": "apix-2",
5657
"google.golang.org/api": "apix-2",

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ require (
174174
go.uber.org/multierr v1.11.0 // indirect
175175
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
176176
golang.org/x/crypto v0.40.0 // indirect
177-
golang.org/x/oauth2 v0.30.0 // indirect
177+
golang.org/x/oauth2 v0.30.0
178178
golang.org/x/sync v0.16.0 // indirect
179179
golang.org/x/term v0.33.0 // indirect
180180
golang.org/x/text v0.27.0 // indirect

internal/transport/transport.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@
1515
package transport
1616

1717
import (
18+
"context"
1819
"net"
1920
"net/http"
2021
"time"
2122

2223
"github.com/mongodb-forks/digest"
2324
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config"
2425
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/oauth"
26+
"go.mongodb.org/atlas-sdk/v20250312005/auth/clientcredentials"
2527
atlasauth "go.mongodb.org/atlas/auth"
28+
"golang.org/x/oauth2"
2629
)
2730

2831
const (
@@ -110,3 +113,18 @@ func (tr *tokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
110113

111114
return tr.base.RoundTrip(req)
112115
}
116+
117+
func NewServiceAccountTransport(clientID, clientSecret string, base http.RoundTripper) (http.RoundTripper, error) {
118+
cfg := clientcredentials.NewConfig(clientID, clientSecret)
119+
if config.OpsManagerURL() != "" {
120+
cfg.RevokeURL = config.OpsManagerURL() + "api/oauth/revoke"
121+
cfg.TokenURL = config.OpsManagerURL() + "api/oauth/token"
122+
}
123+
124+
ctx := context.Background()
125+
126+
return &oauth2.Transport{
127+
Base: base,
128+
Source: cfg.TokenSource(ctx),
129+
}, nil
130+
}

internal/transport/transport_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2025 MongoDB Inc
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+
//go:build unit
16+
17+
package transport
18+
19+
import (
20+
"net/http"
21+
"net/http/httptest"
22+
"testing"
23+
24+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config"
25+
"github.com/stretchr/testify/require"
26+
"go.mongodb.org/atlas/auth"
27+
)
28+
29+
func TestNewAccessTokenTransport(t *testing.T) {
30+
mockToken := &auth.Token{
31+
AccessToken: "mock-access-token",
32+
RefreshToken: "mock-refresh-token",
33+
}
34+
35+
saveToken := func(_ *auth.Token) error { return nil }
36+
37+
base := Default()
38+
accessTokenTransport, err := NewAccessTokenTransport(mockToken, base, saveToken)
39+
require.NoError(t, err)
40+
require.NotNil(t, accessTokenTransport)
41+
42+
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
43+
resp, err := accessTokenTransport.RoundTrip(req)
44+
require.NoError(t, err)
45+
require.NotNil(t, resp)
46+
47+
authHeader := req.Header.Get("Authorization")
48+
expectedHeader := "Bearer " + mockToken.AccessToken
49+
require.Equal(t, expectedHeader, authHeader)
50+
}
51+
52+
func TestNewServiceAccountTransport(t *testing.T) {
53+
// Mock the token endpoint since the actual endpoint requires a valid client ID and secret.
54+
tokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
55+
w.Header().Set("Content-Type", "application/json")
56+
if _, err := w.Write([]byte(`{"access_token":"mock-token","token_type":"bearer","expires_in":3600}`)); err != nil {
57+
t.Errorf("Failed to write response: %v", err)
58+
}
59+
}))
60+
defer tokenServer.Close()
61+
62+
// Temporarily set OpsManagerURL to mock tokenServer URL
63+
originalURL := config.OpsManagerURL()
64+
config.SetOpsManagerURL(tokenServer.URL + "/")
65+
defer func() { config.SetOpsManagerURL(originalURL) }()
66+
67+
clientID := "mock-client-id"
68+
clientSecret := "mock-client-secret" //nolint:gosec
69+
base := http.DefaultTransport
70+
71+
tr, err := NewServiceAccountTransport(clientID, clientSecret, base)
72+
require.NoError(t, err)
73+
require.NotNil(t, tr)
74+
75+
// Create request to check authentication header
76+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
77+
if got := r.Header.Get("Authorization"); got != "Bearer mock-token" {
78+
t.Errorf("Expected Authorization header to be 'Bearer mock-token', but got: %v", got)
79+
}
80+
w.WriteHeader(http.StatusOK)
81+
}))
82+
defer server.Close()
83+
84+
req := httptest.NewRequest(http.MethodGet, server.URL, nil)
85+
resp, err := tr.RoundTrip(req)
86+
require.NoError(t, err)
87+
require.NotNil(t, resp)
88+
}

0 commit comments

Comments
 (0)