Skip to content

Commit eba8f21

Browse files
authored
fix: Fixed ZPA SCIM/SAML search string to use plain search (#381)
* fix: Fixed ZPA SCIM/SAML search string to use plain search
1 parent cafd900 commit eba8f21

File tree

3 files changed

+48
-46
lines changed

3 files changed

+48
-46
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# Changelog
22

3-
# 3.8.1 (November 5, 2025)
3+
# 3.8.3 (November 6, 2025)
4+
5+
## Notes
6+
- Golang: **v1.24**
7+
8+
### Bug Fixes
9+
10+
- [PR #381](https://github.com/zscaler/zscaler-sdk-go/pull/381) - Fixed SCIM and SAML attribute endpoints to use plain search strings instead of filter format, and improved URL encoding for ZPA endpoints to use `%20` for spaces instead of `+` to match API requirements
11+
12+
# 3.8.2 (November 5, 2025)
413

514
## Notes
615
- Golang: **v1.24**

docs/guides/release-notes.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,29 @@ Track all Zscaler SDK GO releases. New resources, features, and bug fixes will b
1313

1414
---
1515

16-
``Last updated: v3.8.1``
16+
``Last updated: v3.8.3``
1717

1818
---
1919

20+
# 3.8.3 (November 6, 2025)
21+
22+
## Notes
23+
- Golang: **v1.24**
24+
25+
### Bug Fixes
26+
27+
- [PR #381](https://github.com/zscaler/zscaler-sdk-go/pull/381) - Fixed SCIM and SAML attribute endpoints to use plain search strings instead of filter format, and improved URL encoding for ZPA endpoints to use `%20` for spaces instead of `+` to match API requirements
28+
29+
# 3.8.2 (November 5, 2025)
30+
31+
## Notes
32+
- Golang: **v1.24**
33+
34+
### Bug Fixes
35+
36+
- [PR #380](https://github.com/zscaler/zscaler-sdk-go/pull/380) - Fixed ZPA search functionality to automatically convert simple search strings to API filter format (`name+EQ+<value>`) to prevent `filtering.input.invalid.operand` errors when searching for resources with multi-word names
37+
38+
2039
# 3.8.1 (November 5, 2025)
2140

2241
## Notes

zscaler/zpa/services/common/common.go

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -260,15 +260,15 @@ func getAllPagesGeneric[T any](ctx context.Context, client *zscaler.Client, rela
260260

261261
// GetAllPagesGeneric fetches all resources instead of just one single page
262262
func GetAllPagesGeneric[T any](ctx context.Context, client *zscaler.Client, relativeURL, searchQuery string) ([]T, *http.Response, error) {
263-
// Convert search query to filter format for ZPA endpoints
263+
// Convert search query to filter format for ZPA endpoints (except SCIM endpoints)
264264
// Don't pre-encode here - let the query parameter encoding handle it
265-
if isZPAEndpoint(relativeURL) {
265+
if isZPAEndpoint(relativeURL) && !isSCIMEndpoint(relativeURL) {
266266
searchQuery = convertZPASearchToFilter(searchQuery)
267267
} else {
268-
// For non-ZPA endpoints, sanitize the query
268+
// For non-ZPA endpoints or SCIM endpoints, sanitize the query
269269
searchQuery = sanitizeSearchQuery(searchQuery)
270270
}
271-
271+
272272
totalPages, result, resp, err := getAllPagesGeneric[T](ctx, client, relativeURL, 1, DefaultPageSize, Filter{Search: searchQuery})
273273
if err != nil {
274274
return nil, resp, err
@@ -319,9 +319,10 @@ func GetAllPagesGenericWithCustomFilters[T any](ctx context.Context, client *zsc
319319

320320
// Ensure Search term is sanitized correctly (prevent double encoding)
321321
// For ZPA, convert simple search strings to filter format (name+EQ+<value>)
322+
// Exception: SCIM endpoints use plain search strings
322323
if filters.Search != "" {
323-
// Check if this is a ZPA endpoint
324-
if isZPAEndpoint(relativeURL) {
324+
// Check if this is a ZPA endpoint (but not SCIM)
325+
if isZPAEndpoint(relativeURL) && !isSCIMEndpoint(relativeURL) {
325326
filters.Search = convertZPASearchToFilter(filters.Search)
326327
} else {
327328
filters.Search = sanitizeSearchQuery(filters.Search)
@@ -354,15 +355,15 @@ func GetAllPagesGenericWithCustomFilters[T any](ctx context.Context, client *zsc
354355
}
355356
}
356357
}
357-
358+
358359
// Only try partial search if we have multiple words in the value
359360
if strings.Count(searchValue, " ") > 0 {
360361
tokens := strings.Split(searchValue, " ")
361362
if len(tokens) >= 2 {
362363
// Use only the first two words for partial search
363364
partialValue := strings.Join(tokens[:2], " ")
364-
// Reconstruct filter format if needed
365-
if isZPAEndpoint(relativeURL) && (strings.Contains(filters.Search, "+EQ+") || strings.Contains(filters.Search, "%2BEQ%2B")) {
365+
// Reconstruct filter format if needed (but not for SCIM endpoints)
366+
if isZPAEndpoint(relativeURL) && !isSCIMEndpoint(relativeURL) && (strings.Contains(filters.Search, "+EQ+") || strings.Contains(filters.Search, "%2BEQ%2B")) {
366367
filters.Search = convertZPASearchToFilter(partialValue)
367368
} else {
368369
filters.Search = partialValue
@@ -453,9 +454,16 @@ func isZPAEndpoint(url string) bool {
453454
return strings.Contains(url, "/zpa/") || strings.Contains(url, "/mgmtconfig/")
454455
}
455456

457+
// isSCIMEndpoint checks if the given URL is a SCIM endpoint
458+
// SCIM endpoints use plain search strings, not the name+EQ+value filter format
459+
func isSCIMEndpoint(url string) bool {
460+
return strings.Contains(url, "/scimgroup") || strings.Contains(url, "/scimattributeheader")
461+
}
462+
456463
// convertZPASearchToFilter converts a simple search string to ZPA filter format
457464
// If the search string already contains filter operators, it returns it as-is
458465
// Otherwise, it converts to name+EQ+<value> format for exact name matching
466+
// Note: SCIM endpoints should not use this function as they expect plain search strings
459467
func convertZPASearchToFilter(search string) string {
460468
// Trim whitespace
461469
search = strings.TrimSpace(search)
@@ -504,41 +512,7 @@ func convertZPASearchToFilter(search string) string {
504512
}
505513

506514
func sanitizeSearchQuery(query string) string {
507-
// First, trim spaces
508-
query = strings.TrimSpace(query)
509-
510-
// If the query is already URL encoded (contains %), return as-is
511-
if strings.Contains(query, "%") {
512-
return query
513-
}
514-
515-
// For queries that appear to be complex (contain commas, dashes between words, etc.)
516-
// we'll preserve these characters and let URL encoding handle them
517-
if strings.Contains(query, ",") || (strings.Contains(query, "-") && strings.Contains(query, " ")) {
518-
// Replace multiple spaces with a single space
519-
reSpace := regexp.MustCompile(`\s+`)
520-
query = reSpace.ReplaceAllString(query, " ")
521-
return query
522-
}
523-
524-
// Check for email addresses or email-like patterns (contains @)
525-
// Preserve @ and other characters that are safe in URLs
526-
if strings.Contains(query, "@") {
527-
// Replace multiple spaces with a single space
528-
reSpace := regexp.MustCompile(`\s+`)
529-
query = reSpace.ReplaceAllString(query, " ")
530-
return query
531-
}
532-
533-
// Original behavior for backward compatibility
534-
// Remove special characters except spaces, alphanumeric characters, dashes, underscores, slashes, and dots
535-
re := regexp.MustCompile(`[^a-zA-Z0-9\s_/\-\.]`)
536-
query = re.ReplaceAllString(query, "")
537-
538-
// Replace multiple spaces with a single space
539-
reSpace := regexp.MustCompile(`\s+`)
540-
query = reSpace.ReplaceAllString(query, " ")
541-
515+
// Just trim and return - let URL encoding handle special characters
542516
return strings.TrimSpace(query)
543517
}
544518

0 commit comments

Comments
 (0)