Kevin Ledesma cf87f635c6
Implement Imposter mock server for CTI API (#661)
* Implement Imposter's mock for CTI endpoints

* Update CHANGELOG

* Add scripts to test the invalid scenarios

* Update README

* Fix typo

* Improve directory structure and files naming

Sanitized API references

Removed filtered endpoints

Add parametrization to servers urls

* Add instance/me endpoint mock

* Fix servers section

* Implement ssl support

* Simplify imposter environment, remove non-secure option

* Update documentation

* Add retries system to the Imposter environment

* Implement retry system for pending-granted/rejected scenarios

* Update catalog download example response to match real world

---------

Co-authored-by: Alex Ruiz <alejandro.ruiz.becerra@wazuh.com>
2025-11-20 17:11:05 +01:00

82 lines
3.1 KiB
Groovy

// Token Request Response Logic
// Returns different responses based on device_code parameter and request count
import java.util.concurrent.ConcurrentHashMap
// Use a static map to track request counts across invocations
@groovy.transform.Field
static ConcurrentHashMap<String, Integer> requestCounts = new ConcurrentHashMap<>()
def deviceCode = context.request.formParams.device_code?.toString()
// Expired token scenario
if (deviceCode == "expired") {
respond()
.withStatusCode(400)
.withHeader("Cache-Control", "no-store")
.withHeader("Pragma", "no-cache")
.withHeader("Content-Type", "application/json")
.withContent('{"error": "expired_token", "error_description": "The device code has expired"}')
}
// Pending authorization codes
else if (deviceCode == "pending") {
respond()
.withStatusCode(400)
.withHeader("Cache-Control", "no-store")
.withHeader("Pragma", "no-cache")
.withHeader("Content-Type", "application/json")
.withContent('{"error": "authorization_pending"}')
}
// Success scenario - immediate grant
else if (deviceCode == "granted") {
respond()
.withStatusCode(200)
.withHeader("Cache-Control", "no-store")
.withHeader("Content-Type", "application/json")
.withContent('{"access_token": "AYjcyMzY3ZDhiNmJkNTY", "refresh_token": "RjY2NjM5NzA2OWJjuE7c", "token_type": "Bearer", "expires_in": 3600}')
}
// Handle pending/rejected flow
else if (deviceCode == "pending_rejected") {
def count = requestCounts.getOrDefault(deviceCode, 0) + 1
requestCounts.put(deviceCode, count)
if (count <= 4) {
respond()
.withStatusCode(400)
.withHeader("Cache-Control", "no-store")
.withHeader("Pragma", "no-cache")
.withHeader("Content-Type", "application/json")
.withContent('{"error": "authorization_pending"}')
} else {
// Reject after 4 attempts
respond()
.withStatusCode(400)
.withHeader("Cache-Control", "no-store")
.withHeader("Pragma", "no-cache")
.withHeader("Content-Type", "application/json")
.withContent('{"error": "access_denied", "error_description": "Token authorization denied"}')
}
}
// Handle pending/granted flow
else {
def count = requestCounts.getOrDefault(deviceCode, 0) + 1
requestCounts.put(deviceCode, count)
// Return authorization_pending for first 4 attempts
if (count <= 4) {
respond()
.withStatusCode(400)
.withHeader("Cache-Control", "no-store")
.withHeader("Pragma", "no-cache")
.withHeader("Content-Type", "application/json")
.withContent('{"error": "authorization_pending"}')
} else {
// Grant token after 4 attempts
respond()
.withStatusCode(200)
.withHeader("Cache-Control", "no-store")
.withHeader("Content-Type", "application/json")
.withContent('{"access_token": "AYjcyMzY3ZDhiNmJkNTY", "refresh_token": "RjY2NjM5NzA2OWJjuE7c", "token_type": "Bearer", "expires_in": 3600}')
}
}