Skip to content

Commit 173d18b

Browse files
authored
Merge pull request #187 from deploymenttheory/dev
Added custom cookie support for http client where users wish to set specific cookies for their usecases
2 parents d3c7962 + 022b2ae commit 173d18b

File tree

6 files changed

+149
-30
lines changed

6 files changed

+149
-30
lines changed

README.md

+35-18
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,45 @@ Example configuration file (clientconfig.json):
8787

8888
```json
8989
{
90-
"Auth": {
91-
"ClientID": "client-id",
92-
"ClientSecret": "client-secret",
93-
"Username": "username",
94-
"Password": "password"
90+
"Auth": {
91+
"ClientID": "client-id", // set this for oauth2 based authentication
92+
"ClientSecret": "client-secret", // set this for oauth2 based authentication
93+
"Username": "username", // set this for basic auth
94+
"Password": "password" // set this for basic auth
9595
},
9696
"Environment": {
97-
"InstanceName": "yourinstance",
98-
"OverrideBaseDomain": "",
99-
"APIType": "" // "jamfpro" / "msgraph"
97+
"APIType": "", // define the api integration e.g "jamfpro" / "msgraph"
98+
"InstanceName": "yourinstance", // used for "jamfpro"
99+
"OverrideBaseDomain": "", // used for "jamfpro"
100+
"TenantID": "tenant-id", // used for "msgraph"h
101+
"TenantName ": "resource", // used for "msgraph"
100102
},
101103
"ClientOptions": {
102-
"LogLevel": "LogLevelDebug", // "LogLevelDebug" / "LogLevelInfo" / "LogLevelWarn" / "LogLevelError" / "LogLevelFatal" / "LogLevelPanic"
103-
"LogOutputFormat": "console", // "console" / "json"
104-
"LogConsoleSeparator": " ", // " " / "\t" / "," / etc.
105-
"LogExportPath": "/path/to/export/your/logs",
106-
"HideSensitiveData": true, // redacts sensitive data from logs
107-
"MaxRetryAttempts": 5, // set number of retry attempts
108-
"MaxConcurrentRequests": 3, // set number of concurrent requests
109-
"EnableCookieJar": false, // enable cookie jar support
110-
"FollowRedirects": true, // follow redirects
111-
"MaxRedirects": 5 // set number of redirects to follow
104+
"Logging": {
105+
"LogLevel": "LogLevelDebug", // "LogLevelDebug" / "LogLevelInfo" / "LogLevelWarn" / "LogLevelError" / "LogLevelFatal" / "LogLevelPanic"
106+
"LogOutputFormat": "console", // "console" / "json"
107+
"LogConsoleSeparator": " ", // " " / "\t" / "," / etc.
108+
"LogExportPath": "/your/log/path/folder",
109+
"HideSensitiveData": true // redacts sensitive data from logs
110+
},
111+
"Cookies": {
112+
"EnableCookieJar": true, // enable cookie jar support
113+
"CustomCookies": { // set custom cookies as an alternative to cookie jar
114+
"sessionId": "abc123",
115+
"authToken": "xyz789"
116+
}
117+
},
118+
"Retry": {
119+
"MaxRetryAttempts": 5, // set number of retry attempts
120+
"EnableDynamicRateLimiting": true // enable dynamic rate limiting
121+
},
122+
"Concurrency": {
123+
"MaxConcurrentRequests": 3 // set number of concurrent requests
124+
},
125+
"Redirect": {
126+
"FollowRedirects": true, // follow redirects
127+
"MaxRedirects": 5 // set number of redirects to follow
128+
}
112129
}
113130
}
114131
```

cookiejar/cookiejar.go

+62-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
11
// cookiejar/cookiejar.go
22

3-
/* The cookiejar package provides utility functions for managing cookies within an HTTP client
4-
context in Go. This package aims to enhance HTTP client functionalities by offering cookie
5-
handling capabilities, including initialization of a cookie jar, redaction of sensitive cookies,
6-
and parsing of cookies from HTTP headers. Below is an explanation of the core functionalities
7-
provided by this package*/
3+
/* When both the cookie jar is enabled and specific cookies are set for an HTTP client in
4+
your scenario, here’s what generally happens during the request processing:
5+
6+
Cookie Jar Initialization: If the cookie jar is enabled through your SetupCookieJar function,
7+
an instance of http.cookiejar.Jar is created and associated with your HTTP client. This
8+
cookie jar will automatically handle incoming and outgoing cookies for all requests made
9+
using this client. It manages storing cookies and automatically sending them with subsequent
10+
requests to the domains for which they're valid.
11+
12+
Setting Specific Cookies: The ApplyCustomCookies function checks for any user-defined specific
13+
cookies (from the CustomCookies map). If found, these cookies are explicitly added to the
14+
outgoing HTTP request headers via the SetSpecificCookies function.
15+
16+
Interaction between Cookie Jar and Specific Cookies:
17+
Cookie Precedence: When a specific cookie (added via SetSpecificCookies) shares the same name
18+
as a cookie already handled by the cookie jar for a given domain, the behavior depends on the
19+
implementation of the HTTP client's cookie handling and the server's cookie management rules.
20+
Generally, the explicitly set cookie in the HTTP request header (from SetSpecificCookies)
21+
should override any similar cookie managed by the cookie jar for that single request.
22+
23+
Subsequent Requests: For subsequent requests, if the specific cookies are not added again
24+
via ApplyCustomCookies, the cookies in the jar that were stored from previous responses
25+
will take precedence again, unless overwritten by subsequent responses or explicit setting
26+
again.
27+
28+
Practical Usage:
29+
30+
This setup allows flexibility:
31+
32+
Use Cookie Jar: For general session management where cookies are automatically managed across
33+
requests.
34+
Use Specific Cookies: For overriding or adding specific cookies for particular requests where
35+
customized control is necessary (such as testing scenarios, special authentication cookies,
36+
etc.).
37+
Logging and Debugging: Your setup also includes logging functionalities which can be very
38+
useful to debug and verify which cookies are being sent and managed. This is crucial for
39+
maintaining visibility into how cookies are influencing the behavior of your HTTP client
40+
interactions.*/
841

942
package cookiejar
1043

@@ -31,6 +64,30 @@ func SetupCookieJar(client *http.Client, enableCookieJar bool, log logger.Logger
3164
return nil
3265
}
3366

67+
// ApplyCustomCookies checks and applies custom cookies to the HTTP request if any are configured.
68+
// It logs the names of the custom cookies being applied without exposing their values.
69+
func ApplyCustomCookies(req *http.Request, cookies map[string]string, log logger.Logger) {
70+
if len(cookies) > 0 {
71+
cookieNames := make([]string, 0, len(cookies))
72+
for name := range cookies {
73+
cookieNames = append(cookieNames, name)
74+
}
75+
log.Debug("Applying custom cookies", zap.Strings("Cookies", cookieNames))
76+
SetSpecificCookies(req, cookies)
77+
}
78+
}
79+
80+
// SetSpecificCookies sets specific cookies provided in the configuration on the HTTP request.
81+
func SetSpecificCookies(req *http.Request, cookies map[string]string) {
82+
for name, value := range cookies {
83+
cookie := &http.Cookie{
84+
Name: name,
85+
Value: value,
86+
}
87+
req.AddCookie(cookie)
88+
}
89+
}
90+
3491
// GetCookies is a middleware that extracts cookies from incoming requests and serializes them.
3592
func GetCookies(next http.Handler, log logger.Logger) http.Handler {
3693
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

httpclient/client.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type EnvironmentConfig struct {
6060
// ClientOptions holds optional configuration options for the HTTP Client.
6161
type ClientOptions struct {
6262
Logging LoggingConfig // Configuration related to logging
63-
Cookie CookieConfig // Cookie handling settings
63+
Cookies CookieConfig // Cookie handling settings
6464
Retry RetryConfig // Retry behavior configuration
6565
Concurrency ConcurrencyConfig // Concurrency configuration
6666
Timeout TimeoutConfig // Custom timeout settings
@@ -78,7 +78,8 @@ type LoggingConfig struct {
7878

7979
// CookieConfig holds configuration related to cookie handling.
8080
type CookieConfig struct {
81-
EnableCookieJar bool // Enable or disable cookie jar
81+
EnableCookieJar bool // Enable or disable cookie jar
82+
CustomCookies map[string]string `json:"CustomCookies,omitempty"` // Key-value pairs for setting specific cookies
8283
}
8384

8485
// RetryConfig holds configuration related to retry behavior.
@@ -155,7 +156,7 @@ func BuildClient(config ClientConfig) (*Client, error) {
155156
}
156157

157158
// Conditionally setup cookie jar
158-
if err := cookiejar.SetupCookieJar(httpClient, config.ClientOptions.Cookie.EnableCookieJar, log); err != nil {
159+
if err := cookiejar.SetupCookieJar(httpClient, config.ClientOptions.Cookies.EnableCookieJar, log); err != nil {
159160
log.Error("Error setting up cookie jar", zap.Error(err))
160161
return nil, err
161162
}
@@ -199,7 +200,7 @@ func BuildClient(config ClientConfig) (*Client, error) {
199200
zap.String("Log Encoding Format", config.ClientOptions.Logging.LogOutputFormat),
200201
zap.String("Log Separator", config.ClientOptions.Logging.LogConsoleSeparator),
201202
zap.Bool("Hide Sensitive Data In Logs", config.ClientOptions.Logging.HideSensitiveData),
202-
zap.Bool("Cookie Jar Enabled", config.ClientOptions.Cookie.EnableCookieJar),
203+
zap.Bool("Cookie Jar Enabled", config.ClientOptions.Cookies.EnableCookieJar),
203204
zap.Int("Max Retry Attempts", config.ClientOptions.Retry.MaxRetryAttempts),
204205
zap.Bool("Enable Dynamic Rate Limiting", config.ClientOptions.Retry.EnableDynamicRateLimiting),
205206
zap.Int("Max Concurrent Requests", config.ClientOptions.Concurrency.MaxConcurrentRequests),

httpclient/client_configuration.go

+39-2
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,24 @@ func LoadConfigFromEnv(config *ClientConfig) (*ClientConfig, error) {
103103
log.Printf("ClientSecret env value found and set")
104104

105105
// EnvironmentConfig
106+
config.Environment.APIType = getEnvOrDefault("API_TYPE", config.Environment.APIType)
107+
log.Printf("APIType env value found and set to: %s", config.Environment.APIType)
108+
106109
config.Environment.InstanceName = getEnvOrDefault("INSTANCE_NAME", config.Environment.InstanceName)
107110
log.Printf("InstanceName env value found and set to: %s", config.Environment.InstanceName)
108111

109112
config.Environment.OverrideBaseDomain = getEnvOrDefault("OVERRIDE_BASE_DOMAIN", config.Environment.OverrideBaseDomain)
110113
log.Printf("OverrideBaseDomain env value found and set to: %s", config.Environment.OverrideBaseDomain)
111114

112-
config.Environment.APIType = getEnvOrDefault("API_TYPE", config.Environment.APIType)
113-
log.Printf("APIType env value found and set to: %s", config.Environment.APIType)
115+
config.Environment.TenantID = getEnvOrDefault("TENANT_ID", config.Environment.TenantID)
116+
log.Printf("TenantID env value found and set to: %s", config.Environment.TenantID)
117+
118+
config.Environment.TenantName = getEnvOrDefault("TENANT_NAME", config.Environment.TenantName)
119+
log.Printf("TenantName env value found and set to: %s", config.Environment.TenantName)
114120

115121
// ClientOptions
122+
123+
// Logging
116124
config.ClientOptions.Logging.LogLevel = getEnvOrDefault("LOG_LEVEL", config.ClientOptions.Logging.LogLevel)
117125
log.Printf("LogLevel env value found and set to: %s", config.ClientOptions.Logging.LogLevel)
118126

@@ -128,15 +136,29 @@ func LoadConfigFromEnv(config *ClientConfig) (*ClientConfig, error) {
128136
config.ClientOptions.Logging.HideSensitiveData = parseBool(getEnvOrDefault("HIDE_SENSITIVE_DATA", strconv.FormatBool(config.ClientOptions.Logging.HideSensitiveData)))
129137
log.Printf("HideSensitiveData env value found and set to: %t", config.ClientOptions.Logging.HideSensitiveData)
130138

139+
// Cookies
140+
config.ClientOptions.Cookies.EnableCookieJar = parseBool(getEnvOrDefault("ENABLE_COOKIE_JAR", strconv.FormatBool(config.ClientOptions.Cookies.EnableCookieJar)))
141+
log.Printf("EnableCookieJar env value found and set to: %t", config.ClientOptions.Cookies.EnableCookieJar)
142+
143+
// Load specific cookies from environment variable
144+
cookieStr := getEnvOrDefault("CUSTOM_COOKIES", "")
145+
if cookieStr != "" {
146+
config.ClientOptions.Cookies.CustomCookies = parseCookiesFromString(cookieStr)
147+
log.Printf("CustomCookies env value found and set")
148+
}
149+
150+
// Retry
131151
config.ClientOptions.Retry.MaxRetryAttempts = parseInt(getEnvOrDefault("MAX_RETRY_ATTEMPTS", strconv.Itoa(config.ClientOptions.Retry.MaxRetryAttempts)), DefaultMaxRetryAttempts)
132152
log.Printf("MaxRetryAttempts env value found and set to: %d", config.ClientOptions.Retry.MaxRetryAttempts)
133153

134154
config.ClientOptions.Retry.EnableDynamicRateLimiting = parseBool(getEnvOrDefault("ENABLE_DYNAMIC_RATE_LIMITING", strconv.FormatBool(config.ClientOptions.Retry.EnableDynamicRateLimiting)))
135155
log.Printf("EnableDynamicRateLimiting env value found and set to: %t", config.ClientOptions.Retry.EnableDynamicRateLimiting)
136156

157+
// Concurrency
137158
config.ClientOptions.Concurrency.MaxConcurrentRequests = parseInt(getEnvOrDefault("MAX_CONCURRENT_REQUESTS", strconv.Itoa(config.ClientOptions.Concurrency.MaxConcurrentRequests)), DefaultMaxConcurrentRequests)
138159
log.Printf("MaxConcurrentRequests env value found and set to: %d", config.ClientOptions.Concurrency.MaxConcurrentRequests)
139160

161+
// timeouts
140162
config.ClientOptions.Timeout.TokenRefreshBufferPeriod = parseDuration(getEnvOrDefault("TOKEN_REFRESH_BUFFER_PERIOD", config.ClientOptions.Timeout.TokenRefreshBufferPeriod.String()), DefaultTokenBufferPeriod)
141163
log.Printf("TokenRefreshBufferPeriod env value found and set to: %s", config.ClientOptions.Timeout.TokenRefreshBufferPeriod)
142164

@@ -328,3 +350,18 @@ func setLoggerDefaultValues(config *ClientConfig) {
328350
// Log completion of setting default values
329351
log.Println("Default values set for logger configuration")
330352
}
353+
354+
// parseCookiesFromString parses a semi-colon separated string of key=value pairs into a map.
355+
func parseCookiesFromString(cookieStr string) map[string]string {
356+
cookies := make(map[string]string)
357+
pairs := strings.Split(cookieStr, ";")
358+
for _, pair := range pairs {
359+
kv := strings.SplitN(pair, "=", 2)
360+
if len(kv) == 2 {
361+
key := strings.TrimSpace(kv[0])
362+
value := strings.TrimSpace(kv[1])
363+
cookies[key] = value
364+
}
365+
}
366+
return cookies
367+
}

httpclient/client_configuration_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func TestLoadConfigFromFile(t *testing.T) {
5656
assert.True(t, config.ClientOptions.Retry.EnableDynamicRateLimiting)
5757
assert.Equal(t, 5, config.ClientOptions.Retry.MaxRetryAttempts)
5858
assert.Equal(t, 3, config.ClientOptions.Concurrency.MaxConcurrentRequests)
59-
assert.True(t, config.ClientOptions.Cookie.EnableCookieJar)
59+
assert.True(t, config.ClientOptions.Cookies.EnableCookieJar)
6060
assert.True(t, config.ClientOptions.Redirect.FollowRedirects)
6161
assert.Equal(t, 5, config.ClientOptions.Redirect.MaxRedirects)
6262
}

httpclient/request.go

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/deploymenttheory/go-api-http-client/authenticationhandler"
11+
"github.com/deploymenttheory/go-api-http-client/cookiejar"
1112
"github.com/deploymenttheory/go-api-http-client/headers"
1213
"github.com/deploymenttheory/go-api-http-client/httpmethod"
1314
"github.com/deploymenttheory/go-api-http-client/logger"
@@ -159,6 +160,9 @@ func (c *Client) executeRequestWithRetries(method, endpoint string, body, out in
159160
return nil, err
160161
}
161162

163+
// Apply custom cookies if configured
164+
cookiejar.ApplyCustomCookies(req, c.clientConfig.ClientOptions.Cookies.CustomCookies, log)
165+
162166
// Set request headers
163167
headerHandler := headers.NewHeaderHandler(req, c.Logger, c.APIHandler, c.AuthTokenHandler)
164168
headerHandler.SetRequestHeaders(endpoint)
@@ -320,6 +324,9 @@ func (c *Client) executeRequest(method, endpoint string, body, out interface{})
320324
return nil, err
321325
}
322326

327+
// Apply custom cookies if configured
328+
cookiejar.ApplyCustomCookies(req, c.clientConfig.ClientOptions.Cookies.CustomCookies, log)
329+
323330
// Set request headers
324331
headerHandler := headers.NewHeaderHandler(req, c.Logger, c.APIHandler, c.AuthTokenHandler)
325332
headerHandler.SetRequestHeaders(endpoint)

0 commit comments

Comments
 (0)