Skip to content

Commit 2d07f02

Browse files
Copilotbbockelm
andcommitted
Add failed issuer lookup tracking and comprehensive test
Co-authored-by: bbockelm <1093447+bbockelm@users.noreply.github.com>
1 parent 95d91da commit 2d07f02

File tree

3 files changed

+157
-4
lines changed

3 files changed

+157
-4
lines changed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ target_link_libraries(scitokens-create SciTokens)
7878
add_executable(scitokens-test-monitoring src/test_monitoring.cpp)
7979
target_link_libraries(scitokens-test-monitoring SciTokens)
8080

81+
add_executable(scitokens-test-monitoring-comprehensive src/test_monitoring_comprehensive.cpp)
82+
target_link_libraries(scitokens-test-monitoring-comprehensive SciTokens)
83+
8184
get_directory_property(TARGETS BUILDSYSTEM_TARGETS)
8285
install(
8386
TARGETS ${TARGETS}

src/scitokens_internal.h

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,17 @@ class Validator {
502502
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
503503
end_time - start_time);
504504

505-
// Check if this is an expiration error
506505
std::string error_msg = e.what();
506+
507+
// Check if this is a failed issuer lookup (network/DNS errors)
508+
if (error_msg.find("resolve") != std::string::npos ||
509+
error_msg.find("host") != std::string::npos ||
510+
error_msg.find("network") != std::string::npos ||
511+
error_msg.find("Failed to retrieve") != std::string::npos) {
512+
internal::MonitoringStats::instance().record_failed_issuer_lookup(issuer);
513+
}
514+
515+
// Check if this is an expiration error
507516
if (error_msg.find("exp") != std::string::npos ||
508517
error_msg.find("expir") != std::string::npos) {
509518
internal::MonitoringStats::instance().record_expired_token(issuer);
@@ -517,9 +526,48 @@ class Validator {
517526
}
518527

519528
void verify(const jwt::decoded_jwt<jwt::traits::kazuho_picojson> &jwt) {
520-
auto result = verify_async(jwt);
521-
while (!result->m_done) {
522-
result = verify_async_continue(std::move(result));
529+
std::string issuer;
530+
auto start_time = std::chrono::steady_clock::now();
531+
bool has_issuer = false;
532+
533+
try {
534+
// Try to extract issuer for monitoring
535+
if (jwt.has_payload_claim("iss")) {
536+
issuer = jwt.get_issuer();
537+
has_issuer = true;
538+
}
539+
540+
auto result = verify_async(jwt);
541+
while (!result->m_done) {
542+
result = verify_async_continue(std::move(result));
543+
}
544+
} catch (const std::exception &e) {
545+
// Record failure if we have an issuer
546+
if (has_issuer) {
547+
auto end_time = std::chrono::steady_clock::now();
548+
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
549+
end_time - start_time);
550+
551+
std::string error_msg = e.what();
552+
553+
// Check if this is a failed issuer lookup (network/DNS errors)
554+
if (error_msg.find("resolve") != std::string::npos ||
555+
error_msg.find("host") != std::string::npos ||
556+
error_msg.find("network") != std::string::npos ||
557+
error_msg.find("Failed to retrieve") != std::string::npos) {
558+
internal::MonitoringStats::instance().record_failed_issuer_lookup(issuer);
559+
}
560+
561+
// Check if this is an expiration error
562+
if (error_msg.find("exp") != std::string::npos ||
563+
error_msg.find("expir") != std::string::npos) {
564+
internal::MonitoringStats::instance().record_expired_token(issuer);
565+
}
566+
567+
internal::MonitoringStats::instance().record_validation_failure(
568+
issuer, duration.count());
569+
}
570+
throw;
523571
}
524572
}
525573

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#include "scitokens.h"
2+
#include <iostream>
3+
#include <string>
4+
#include <cstring>
5+
6+
// Helper function to print monitoring JSON
7+
void print_monitoring_stats(const std::string &label) {
8+
char *json_out = nullptr;
9+
char *err_msg = nullptr;
10+
11+
int result = scitoken_get_monitoring_json(&json_out, &err_msg);
12+
if (result != 0) {
13+
std::cerr << "Error getting monitoring JSON: "
14+
<< (err_msg ? err_msg : "unknown error") << std::endl;
15+
if (err_msg)
16+
free(err_msg);
17+
return;
18+
}
19+
20+
std::cout << "\n=== " << label << " ===" << std::endl;
21+
std::cout << json_out << std::endl;
22+
free(json_out);
23+
}
24+
25+
int main() {
26+
char *err_msg = nullptr;
27+
28+
// Reset monitoring stats at start
29+
scitoken_reset_monitoring_stats(&err_msg);
30+
31+
std::cout << "Testing Monitoring API with Token Validation\n";
32+
std::cout << "=============================================\n";
33+
34+
// Test 1: Initial state
35+
print_monitoring_stats("Initial State (should be empty)");
36+
37+
// Test 2: Failed validation - expired token from demo.scitokens.org
38+
// This token is from the test.cpp and is expired
39+
std::cout << "\n--- Test 1: Validating an expired token ---\n";
40+
std::string expired_token =
41+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1yczI1NiJ9."
42+
"eyJpc3MiOiJodHRwczovL2RlbW8uc2NpdG9rZW5zLm9yZyIsImV4cCI6MTU0NjM5MjAwOS"
43+
"wiaWF0IjoxNTQ2MzkxNDA5LCJuYmYiOjE1NDYzOTE0MDksImp0aSI6ImFkYTk2MjdiLWEx"
44+
"MGYtNGMyYS05Nzc2LTE4ZThkN2JmN2M4NSJ9.cNMG5zI2-JHh7l_"
45+
"PUPUAxom5Vi6Q3akKmv6q57CoVKHtxZAZRc47Uoix_"
46+
"AH3Xzr42qohr2FPamRTxUMsfZjrAFDJ_4JhJ-kKjJ3cRXXF-"
47+
"gj7lbniCDGOBuPXeMsVmeED15nauZ3XKXUHTGLEsg5O6RjS7sGKM_"
48+
"e9YiYvcTvWXcdkrkxZ2dPPU-R3IxdK6PtE9OB2XOk85H670OAJT3qimKm8Dk_"
49+
"Ri6DEEty1Su_"
50+
"1Tov3ac5B19iZkbhhVPMVP0cRolR9UNLhMxQAsbgEmArQOcs046AOzqQz6osOkdYOrVVO7"
51+
"lO2owUyMol94mB_39y1M8jcf5WNq3ukMMIzMCAPwA";
52+
53+
SciToken token = nullptr;
54+
int result = scitoken_deserialize(expired_token.c_str(), &token, nullptr, &err_msg);
55+
if (result != 0) {
56+
std::cout << "Token deserialization/validation failed (expected): "
57+
<< (err_msg ? err_msg : "unknown error") << std::endl;
58+
if (err_msg) {
59+
free(err_msg);
60+
err_msg = nullptr;
61+
}
62+
} else {
63+
std::cout << "Token was valid (unexpected)" << std::endl;
64+
scitoken_destroy(token);
65+
}
66+
67+
print_monitoring_stats("After Expired Token Validation");
68+
69+
// Test 3: Invalid issuer (should not create unbounded entries)
70+
std::cout << "\n--- Test 2: Testing DDoS protection with multiple invalid issuers ---\n";
71+
72+
// Try to create many tokens with different invalid issuers
73+
// The monitoring system should limit tracking to MAX_FAILED_ISSUERS (100)
74+
for (int i = 0; i < 150; i++) {
75+
// These are malformed tokens that will fail early
76+
std::string fake_token = "invalid.token." + std::to_string(i);
77+
SciToken temp_token = nullptr;
78+
scitoken_deserialize(fake_token.c_str(), &temp_token, nullptr, &err_msg);
79+
if (err_msg) {
80+
free(err_msg);
81+
err_msg = nullptr;
82+
}
83+
}
84+
85+
print_monitoring_stats("After Multiple Invalid Token Attempts");
86+
87+
// Test 4: Reset stats
88+
std::cout << "\n--- Test 3: Testing reset functionality ---\n";
89+
result = scitoken_reset_monitoring_stats(&err_msg);
90+
if (result != 0) {
91+
std::cerr << "Error resetting stats: "
92+
<< (err_msg ? err_msg : "unknown error") << std::endl;
93+
if (err_msg)
94+
free(err_msg);
95+
return 1;
96+
}
97+
98+
print_monitoring_stats("After Reset");
99+
100+
std::cout << "\n=== All monitoring API tests completed successfully ===\n";
101+
return 0;
102+
}

0 commit comments

Comments
 (0)