Skip to content

Commit 245d53e

Browse files
Add proxy authentication support to UAA token fetching
1 parent 9ebd81c commit 245d53e

5 files changed

Lines changed: 260 additions & 15 deletions

File tree

example/proxy_basic_auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ func main() {
5252
},
5353
}
5454

55-
// Create access token
56-
token := pivnet.NewAccessTokenOrLegacyToken(apiToken, config.Host, config.SkipSSLValidation)
55+
// Create access token with proxy auth config
56+
token := pivnet.NewAccessTokenOrLegacyTokenWithProxy(apiToken, config.Host, config.SkipSSLValidation, config.ProxyAuthConfig)
5757

5858
// Create the client with proxy support
5959
client, err := pivnet.NewClientWithProxy(token, config, logger)

example/proxy_spnego_auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ func main() {
5656
},
5757
}
5858

59-
// Create access token
60-
token := pivnet.NewAccessTokenOrLegacyToken(apiToken, config.Host, config.SkipSSLValidation)
59+
// Create access token with proxy auth config
60+
token := pivnet.NewAccessTokenOrLegacyTokenWithProxy(apiToken, config.Host, config.SkipSSLValidation, config.ProxyAuthConfig)
6161

6262
// Create the client with proxy support
6363
fmt.Println("Initializing client with SPNEGO proxy authentication...")

pivnet.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type AccessTokenOrLegacyToken struct {
5757
refreshToken string
5858
skipSSLValidation bool
5959
userAgent string
60+
proxyAuthConfig ProxyAuthConfig
6061
}
6162

6263
type QueryParameter struct {
@@ -68,7 +69,7 @@ func (o AccessTokenOrLegacyToken) AccessToken() (string, error) {
6869
const legacyAPITokenLength = 20
6970
if len(o.refreshToken) > legacyAPITokenLength {
7071
baseURL := fmt.Sprintf("%s%s", o.host, apiVersion)
71-
tokenFetcher := NewTokenFetcher(baseURL, o.refreshToken, o.skipSSLValidation, o.userAgent)
72+
tokenFetcher := NewTokenFetcher(baseURL, o.refreshToken, o.skipSSLValidation, o.userAgent, o.proxyAuthConfig)
7273

7374
accessToken, err := tokenFetcher.GetToken()
7475
if err != nil {
@@ -121,6 +122,22 @@ func NewAccessTokenOrLegacyToken(token string, host string, skipSSLValidation bo
121122
host: host,
122123
skipSSLValidation: skipSSLValidation,
123124
userAgent: userAgent,
125+
proxyAuthConfig: ProxyAuthConfig{},
126+
}
127+
}
128+
129+
// NewAccessTokenOrLegacyTokenWithProxy creates an AccessTokenOrLegacyToken with proxy authentication support
130+
func NewAccessTokenOrLegacyTokenWithProxy(token string, host string, skipSSLValidation bool, proxyAuthConfig ProxyAuthConfig, userAgentOptional ...string) AccessTokenOrLegacyToken {
131+
var userAgent = ""
132+
if len(userAgentOptional) > 0 {
133+
userAgent = userAgentOptional[0]
134+
}
135+
return AccessTokenOrLegacyToken{
136+
refreshToken: token,
137+
host: host,
138+
skipSSLValidation: skipSSLValidation,
139+
userAgent: userAgent,
140+
proxyAuthConfig: proxyAuthConfig,
124141
}
125142
}
126143

uaa.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"net/http"
9+
"net/url"
910
"time"
1011
)
1112

@@ -18,22 +19,62 @@ type TokenFetcher struct {
1819
RefreshToken string
1920
SkipSSLValidation bool
2021
UserAgent string
22+
ProxyAuthConfig ProxyAuthConfig
2123
}
2224

23-
func NewTokenFetcher(endpoint, refreshToken string, skipSSLValidation bool, userAgent string) *TokenFetcher {
24-
return &TokenFetcher{endpoint, refreshToken, skipSSLValidation, userAgent }
25+
func NewTokenFetcher(endpoint, refreshToken string, skipSSLValidation bool, userAgent string, proxyAuthConfig ProxyAuthConfig) *TokenFetcher {
26+
return &TokenFetcher{endpoint, refreshToken, skipSSLValidation, userAgent, proxyAuthConfig}
2527
}
2628

2729
func (t TokenFetcher) GetToken() (string, error) {
28-
httpClient := &http.Client{
29-
Timeout: 60 * time.Second,
30-
Transport: &http.Transport{
30+
var transport http.RoundTripper
31+
var err error
32+
33+
// If proxy authentication is configured, use it; otherwise use standard transport
34+
if t.ProxyAuthConfig.AuthType != "" {
35+
// Create base transport
36+
baseTransport := &http.Transport{
37+
TLSClientConfig: &tls.Config{
38+
InsecureSkipVerify: t.SkipSSLValidation,
39+
},
40+
}
41+
42+
// Parse proxy URL
43+
if t.ProxyAuthConfig.ProxyURL == "" {
44+
return "", fmt.Errorf("proxy URL is required when proxy authentication is specified")
45+
}
46+
proxyURL, err := url.Parse(t.ProxyAuthConfig.ProxyURL)
47+
if err != nil {
48+
return "", fmt.Errorf("failed to parse proxy URL: %w", err)
49+
}
50+
baseTransport.Proxy = http.ProxyURL(proxyURL)
51+
52+
// Create authenticator
53+
authenticator, err := NewProxyAuthenticator(t.ProxyAuthConfig)
54+
if err != nil {
55+
return "", fmt.Errorf("failed to create proxy authenticator: %w", err)
56+
}
57+
58+
// Wrap transport with proxy authentication
59+
transport, err = NewProxyAuthTransport(baseTransport, authenticator)
60+
if err != nil {
61+
return "", fmt.Errorf("failed to initialize proxy authentication: %w", err)
62+
}
63+
} else {
64+
// Use standard transport with environment proxy support
65+
transport = &http.Transport{
3166
TLSClientConfig: &tls.Config{
3267
InsecureSkipVerify: t.SkipSSLValidation,
3368
},
3469
Proxy: http.ProxyFromEnvironment,
35-
},
70+
}
71+
}
72+
73+
httpClient := &http.Client{
74+
Timeout: 60 * time.Second,
75+
Transport: transport,
3676
}
77+
3778
body := AuthBody{RefreshToken: t.RefreshToken}
3879
b, err := json.Marshal(body)
3980
if err != nil {

uaa_test.go

Lines changed: 191 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package pivnet
22

33
import (
44
"errors"
5-
65
"net/http"
76

87
. "github.com/onsi/ginkgo"
@@ -19,7 +18,7 @@ var _ = Describe("UAA", func() {
1918

2019
BeforeEach(func() {
2120
server = ghttp.NewServer()
22-
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "")
21+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", ProxyAuthConfig{})
2322
})
2423

2524
AfterEach(func() {
@@ -43,7 +42,7 @@ var _ = Describe("UAA", func() {
4342

4443
It("passes on the user agent in the request header", func() {
4544
userAgent := "my_user_agent"
46-
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, userAgent)
45+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, userAgent, ProxyAuthConfig{})
4746
server.AppendHandlers(
4847
ghttp.CombineHandlers(
4948
ghttp.VerifyHeaderKV("User-Agent", userAgent),
@@ -69,7 +68,7 @@ var _ = Describe("UAA", func() {
6968
})
7069

7170
It("returns an error without endpoint", func() {
72-
tokenFetcher = NewTokenFetcher("", "some-refresh-token", false, "")
71+
tokenFetcher = NewTokenFetcher("", "some-refresh-token", false, "", ProxyAuthConfig{})
7372
server.AppendHandlers(
7473
ghttp.CombineHandlers(
7574
ghttp.VerifyRequest("POST", "/authentication/access_tokens"),
@@ -83,5 +82,193 @@ var _ = Describe("UAA", func() {
8382
})
8483
})
8584

85+
Context("when proxy authentication is configured", func() {
86+
Context("with Basic authentication", func() {
87+
It("returns an error when proxy URL is empty but auth type is set", func() {
88+
proxyAuthConfig := ProxyAuthConfig{
89+
AuthType: ProxyAuthTypeBasic,
90+
Username: "user",
91+
Password: "pass",
92+
ProxyURL: "",
93+
}
94+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
95+
96+
_, err := tokenFetcher.GetToken()
97+
Expect(err).To(HaveOccurred())
98+
Expect(err.Error()).To(ContainSubstring("proxy URL is required"))
99+
})
100+
101+
It("returns an error when proxy URL is invalid", func() {
102+
proxyAuthConfig := ProxyAuthConfig{
103+
AuthType: ProxyAuthTypeBasic,
104+
Username: "user",
105+
Password: "pass",
106+
ProxyURL: "://invalid-url",
107+
}
108+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
109+
110+
_, err := tokenFetcher.GetToken()
111+
Expect(err).To(HaveOccurred())
112+
Expect(err.Error()).To(ContainSubstring("failed to parse proxy URL"))
113+
})
114+
115+
It("accepts valid proxy auth config without error", func() {
116+
proxyAuthConfig := ProxyAuthConfig{
117+
AuthType: ProxyAuthTypeBasic,
118+
Username: "proxyuser",
119+
Password: "proxypass",
120+
ProxyURL: "http://proxy.example.com:8080",
121+
}
122+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
123+
124+
// TokenFetcher should be created successfully with proxy config
125+
Expect(tokenFetcher).NotTo(BeNil())
126+
Expect(tokenFetcher.ProxyAuthConfig.AuthType).To(Equal(ProxyAuthTypeBasic))
127+
Expect(tokenFetcher.ProxyAuthConfig.Username).To(Equal("proxyuser"))
128+
})
129+
130+
It("accepts empty username and password for Basic auth", func() {
131+
proxyAuthConfig := ProxyAuthConfig{
132+
AuthType: ProxyAuthTypeBasic,
133+
Username: "",
134+
Password: "",
135+
ProxyURL: "http://proxy.example.com:8080",
136+
}
137+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
138+
139+
Expect(tokenFetcher).NotTo(BeNil())
140+
Expect(tokenFetcher.ProxyAuthConfig.AuthType).To(Equal(ProxyAuthTypeBasic))
141+
})
142+
143+
It("handles special characters in username and password", func() {
144+
proxyAuthConfig := ProxyAuthConfig{
145+
AuthType: ProxyAuthTypeBasic,
146+
Username: "user@domain.com",
147+
Password: "p@$$w0rd!#%",
148+
ProxyURL: "http://proxy.example.com:8080",
149+
}
150+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
151+
152+
Expect(tokenFetcher).NotTo(BeNil())
153+
Expect(tokenFetcher.ProxyAuthConfig.Username).To(Equal("user@domain.com"))
154+
Expect(tokenFetcher.ProxyAuthConfig.Password).To(Equal("p@$$w0rd!#%"))
155+
})
156+
157+
It("supports HTTPS proxy URLs", func() {
158+
proxyAuthConfig := ProxyAuthConfig{
159+
AuthType: ProxyAuthTypeBasic,
160+
Username: "proxyuser",
161+
Password: "proxypass",
162+
ProxyURL: "https://secure-proxy.example.com:8443",
163+
}
164+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
165+
166+
Expect(tokenFetcher).NotTo(BeNil())
167+
Expect(tokenFetcher.ProxyAuthConfig.ProxyURL).To(Equal("https://secure-proxy.example.com:8443"))
168+
})
169+
170+
It("supports proxy URLs with custom ports", func() {
171+
proxyAuthConfig := ProxyAuthConfig{
172+
AuthType: ProxyAuthTypeBasic,
173+
Username: "proxyuser",
174+
Password: "proxypass",
175+
ProxyURL: "http://proxy.example.com:3128",
176+
}
177+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
178+
179+
Expect(tokenFetcher).NotTo(BeNil())
180+
Expect(tokenFetcher.ProxyAuthConfig.ProxyURL).To(ContainSubstring(":3128"))
181+
})
182+
})
183+
184+
Context("with SPNEGO authentication", func() {
185+
It("returns an error when username is empty", func() {
186+
proxyAuthConfig := ProxyAuthConfig{
187+
AuthType: ProxyAuthTypeSPNEGO,
188+
Username: "",
189+
Password: "password",
190+
ProxyURL: "http://proxy.example.com:8080",
191+
}
192+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
193+
194+
_, err := tokenFetcher.GetToken()
195+
Expect(err).To(HaveOccurred())
196+
Expect(err.Error()).To(ContainSubstring("username"))
197+
})
198+
199+
It("returns an error when password is empty", func() {
200+
proxyAuthConfig := ProxyAuthConfig{
201+
AuthType: ProxyAuthTypeSPNEGO,
202+
Username: "user@REALM.COM",
203+
Password: "",
204+
ProxyURL: "http://proxy.example.com:8080",
205+
}
206+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
207+
208+
_, err := tokenFetcher.GetToken()
209+
Expect(err).To(HaveOccurred())
210+
Expect(err.Error()).To(ContainSubstring("password"))
211+
})
212+
213+
It("returns an error when proxy URL is empty", func() {
214+
proxyAuthConfig := ProxyAuthConfig{
215+
AuthType: ProxyAuthTypeSPNEGO,
216+
Username: "user@REALM.COM",
217+
Password: "password",
218+
ProxyURL: "",
219+
}
220+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
221+
222+
_, err := tokenFetcher.GetToken()
223+
Expect(err).To(HaveOccurred())
224+
Expect(err.Error()).To(ContainSubstring("proxy URL is required"))
225+
})
226+
227+
It("accepts valid SPNEGO config with Kerberos realm", func() {
228+
proxyAuthConfig := ProxyAuthConfig{
229+
AuthType: ProxyAuthTypeSPNEGO,
230+
Username: "user@REALM.COM",
231+
Password: "password",
232+
ProxyURL: "http://proxy.example.com:8080",
233+
Krb5Config: "/etc/krb5.conf",
234+
}
235+
tokenFetcher = NewTokenFetcher(server.URL(), "some-refresh-token", false, "", proxyAuthConfig)
236+
237+
Expect(tokenFetcher).NotTo(BeNil())
238+
Expect(tokenFetcher.ProxyAuthConfig.AuthType).To(Equal(ProxyAuthTypeSPNEGO))
239+
Expect(tokenFetcher.ProxyAuthConfig.Krb5Config).To(Equal("/etc/krb5.conf"))
240+
})
241+
})
242+
243+
Context("with different refresh tokens", func() {
244+
It("handles long refresh tokens with proxy auth", func() {
245+
longRefreshToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
246+
proxyAuthConfig := ProxyAuthConfig{
247+
AuthType: ProxyAuthTypeBasic,
248+
Username: "proxyuser",
249+
Password: "proxypass",
250+
ProxyURL: "http://proxy.example.com:8080",
251+
}
252+
tokenFetcher = NewTokenFetcher(server.URL(), longRefreshToken, false, "", proxyAuthConfig)
253+
254+
Expect(tokenFetcher).NotTo(BeNil())
255+
Expect(tokenFetcher.RefreshToken).To(Equal(longRefreshToken))
256+
})
257+
258+
It("handles short refresh tokens with proxy auth", func() {
259+
shortRefreshToken := "short-token-123"
260+
proxyAuthConfig := ProxyAuthConfig{
261+
AuthType: ProxyAuthTypeBasic,
262+
Username: "proxyuser",
263+
Password: "proxypass",
264+
ProxyURL: "http://proxy.example.com:8080",
265+
}
266+
tokenFetcher = NewTokenFetcher(server.URL(), shortRefreshToken, false, "", proxyAuthConfig)
267+
268+
Expect(tokenFetcher).NotTo(BeNil())
269+
Expect(tokenFetcher.RefreshToken).To(Equal(shortRefreshToken))
270+
})
271+
})
272+
})
86273
})
87274
})

0 commit comments

Comments
 (0)