diff --git a/build/ginxsom-fcgi b/build/ginxsom-fcgi index 209bbfb..023be5a 100755 Binary files a/build/ginxsom-fcgi and b/build/ginxsom-fcgi differ diff --git a/build/main.o b/build/main.o index a54a66b..682e3c9 100644 Binary files a/build/main.o and b/build/main.o differ diff --git a/build/request_validator.o b/build/request_validator.o index 57b5778..d55703d 100644 Binary files a/build/request_validator.o and b/build/request_validator.o differ diff --git a/db/ginxsom.db b/db/ginxsom.db index f284301..4adf3b1 100644 Binary files a/db/ginxsom.db and b/db/ginxsom.db differ diff --git a/db/schema.sql b/db/schema.sql index e15808d..44eb556 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -68,19 +68,6 @@ CREATE TABLE IF NOT EXISTS auth_rules ( UNIQUE(rule_type, rule_target, operation) ); --- Cache table for authentication decisions (5-minute TTL) -CREATE TABLE IF NOT EXISTS auth_rules_cache ( - cache_key TEXT PRIMARY KEY NOT NULL, -- SHA-256 hash of request parameters - decision INTEGER NOT NULL, -- 1 = allow, 0 = deny - reason TEXT, -- Reason for decision - pubkey TEXT, -- Public key from request - operation TEXT, -- Operation type - resource_hash TEXT, -- Resource hash (if applicable) - created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), - expires_at INTEGER NOT NULL, -- Expiration timestamp - - CHECK (decision IN (0, 1)) -); -- Indexes for performance optimization CREATE INDEX IF NOT EXISTS idx_auth_rules_type_target ON auth_rules(rule_type, rule_target); @@ -89,9 +76,6 @@ CREATE INDEX IF NOT EXISTS idx_auth_rules_enabled ON auth_rules(enabled); CREATE INDEX IF NOT EXISTS idx_auth_rules_priority ON auth_rules(priority); CREATE INDEX IF NOT EXISTS idx_auth_rules_type_operation ON auth_rules(rule_type, operation, enabled); --- Index for cache expiration cleanup -CREATE INDEX IF NOT EXISTS idx_auth_cache_expires ON auth_rules_cache(expires_at); -CREATE INDEX IF NOT EXISTS idx_auth_cache_pubkey ON auth_rules_cache(pubkey); -- View for storage statistics CREATE VIEW IF NOT EXISTS storage_stats AS diff --git a/src/ginxsom.h b/src/ginxsom.h index 1f40d7f..d2884c3 100644 --- a/src/ginxsom.h +++ b/src/ginxsom.h @@ -10,8 +10,8 @@ // Version information (auto-updated by build system) #define VERSION_MAJOR 0 #define VERSION_MINOR 1 -#define VERSION_PATCH 7 -#define VERSION "v0.1.7" +#define VERSION_PATCH 8 +#define VERSION "v0.1.8" #include #include diff --git a/src/request_validator.c b/src/request_validator.c index e43b498..b8ab73a 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -115,7 +115,7 @@ static int validate_nip42_event(cJSON *event, const char *relay_url, const char *challenge_id); static int validate_admin_event(cJSON *event, const char *method, const char *endpoint); static int check_database_auth_rules(const char *pubkey, const char *operation, - const char *resource_hash); + const char *resource_hash, const char *mime_type); void nostr_request_validator_clear_violation(void); // NIP-42 challenge management functions @@ -820,7 +820,7 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request, } int rules_result = check_database_auth_rules( - extracted_pubkey, request->operation, hash_for_rules); + extracted_pubkey, request->operation, hash_for_rules, request->mime_type); if (rules_result != NOSTR_SUCCESS) { validator_debug_log( "VALIDATOR_DEBUG: STEP 14 FAILED - Database rules denied request\n"); @@ -1316,7 +1316,7 @@ static int validate_blossom_event(cJSON *event, const char *expected_hash, * Implements the 6-step rule evaluation engine from AUTH_API.md */ static int check_database_auth_rules(const char *pubkey, const char *operation, - const char *resource_hash) { + const char *resource_hash, const char *mime_type) { sqlite3 *db = NULL; sqlite3_stmt *stmt = NULL; int rc; @@ -1330,8 +1330,8 @@ static int check_database_auth_rules(const char *pubkey, const char *operation, char rules_msg[256]; sprintf(rules_msg, "VALIDATOR_DEBUG: RULES ENGINE - Checking rules for pubkey=%.32s..., " - "operation=%s\n", - pubkey, operation ? operation : "NULL"); + "operation=%s, mime_type=%s\n", + pubkey, operation ? operation : "NULL", mime_type ? mime_type : "NULL"); validator_debug_log(rules_msg); // Open database @@ -1418,7 +1418,49 @@ static int check_database_auth_rules(const char *pubkey, const char *operation, "resource hash provided\n"); } - // Step 3: Check pubkey whitelist + // Step 3: Check MIME type blacklist + if (mime_type) { + // Match both exact MIME type and wildcard patterns (e.g., 'image/*') + const char *mime_blacklist_sql = + "SELECT rule_type, description FROM auth_rules WHERE rule_type = " + "'mime_blacklist' AND (rule_target = ? OR rule_target LIKE '%/*' AND ? LIKE REPLACE(rule_target, '*', '%')) AND (operation = ? OR operation = '*') AND enabled = " + "1 ORDER BY priority LIMIT 1"; + rc = sqlite3_prepare_v2(db, mime_blacklist_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, mime_type, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, mime_type, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, operation ? operation : "", -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) == SQLITE_ROW) { + const char *description = (const char *)sqlite3_column_text(stmt, 1); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - " + "MIME type blacklisted\n"); + char mime_blacklist_msg[256]; + sprintf( + mime_blacklist_msg, + "VALIDATOR_DEBUG: RULES ENGINE - MIME blacklist rule matched: %s\n", + description ? description : "Unknown"); + validator_debug_log(mime_blacklist_msg); + + // Set specific violation details for status code mapping + strcpy(g_last_rule_violation.violation_type, "mime_blacklist"); + sprintf(g_last_rule_violation.reason, "%s: MIME type blacklisted", + description ? description : "TEST_MIME_BLACKLIST"); + + sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_ERROR_AUTH_REQUIRED; + } + sqlite3_finalize(stmt); + } + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - MIME " + "type not blacklisted\n"); + } else { + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 SKIPPED - No " + "MIME type provided\n"); + } + + // Step 4: Check pubkey whitelist // Match both exact operation and wildcard '*' const char *whitelist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = " @@ -1447,7 +1489,72 @@ static int check_database_auth_rules(const char *pubkey, const char *operation, validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - Pubkey " "not whitelisted\n"); - // Step 4: Check if any whitelist rules exist - if yes, deny by default + // Step 5: Check MIME type whitelist (only if not already denied) + if (mime_type) { + // Match both exact MIME type and wildcard patterns (e.g., 'image/*') + const char *mime_whitelist_sql = + "SELECT rule_type, description FROM auth_rules WHERE rule_type = " + "'mime_whitelist' AND (rule_target = ? OR rule_target LIKE '%/*' AND ? LIKE REPLACE(rule_target, '*', '%')) AND (operation = ? OR operation = '*') AND enabled = " + "1 ORDER BY priority LIMIT 1"; + rc = sqlite3_prepare_v2(db, mime_whitelist_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, mime_type, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, mime_type, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, operation ? operation : "", -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) == SQLITE_ROW) { + const char *description = (const char *)sqlite3_column_text(stmt, 1); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - " + "MIME type whitelisted\n"); + char mime_whitelist_msg[256]; + sprintf(mime_whitelist_msg, + "VALIDATOR_DEBUG: RULES ENGINE - MIME whitelist rule matched: %s\n", + description ? description : "Unknown"); + validator_debug_log(mime_whitelist_msg); + sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_SUCCESS; // Allow whitelisted MIME type + } + sqlite3_finalize(stmt); + } + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 FAILED - MIME " + "type not whitelisted\n"); + } else { + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 SKIPPED - No " + "MIME type provided\n"); + } + + // Step 6: Check if any MIME whitelist rules exist - if yes, deny by default + // Match both exact operation and wildcard '*' + const char *mime_whitelist_exists_sql = + "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'mime_whitelist' " + "AND (operation = ? OR operation = '*') AND enabled = 1 LIMIT 1"; + rc = sqlite3_prepare_v2(db, mime_whitelist_exists_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, operation ? operation : "", -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) == SQLITE_ROW) { + int mime_whitelist_count = sqlite3_column_int(stmt, 0); + if (mime_whitelist_count > 0) { + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 6 FAILED - " + "MIME whitelist exists but type not in it\n"); + + // Set specific violation details for status code mapping + strcpy(g_last_rule_violation.violation_type, "mime_whitelist_violation"); + strcpy(g_last_rule_violation.reason, + "MIME type not whitelisted for this operation"); + + sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_ERROR_AUTH_REQUIRED; + } + } + sqlite3_finalize(stmt); + } + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 6 PASSED - No " + "MIME whitelist restrictions apply\n"); + + // Step 7: Check if any whitelist rules exist - if yes, deny by default // Match both exact operation and wildcard '*' const char *whitelist_exists_sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'pubkey_whitelist' " @@ -1478,7 +1585,7 @@ static int check_database_auth_rules(const char *pubkey, const char *operation, "whitelist restrictions apply\n"); sqlite3_close(db); - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - All " + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 7 PASSED - All " "rule checks completed, default ALLOW\n"); return NOSTR_SUCCESS; // Default allow if no restrictive rules matched } diff --git a/tests/white_black_list_test.sh b/tests/white_black_list_test.sh index 5e39a7d..7b2f14a 100755 --- a/tests/white_black_list_test.sh +++ b/tests/white_black_list_test.sh @@ -132,7 +132,6 @@ test_upload() { # Clean up any existing rules from previous tests echo "Cleaning up existing auth rules..." sqlite3 "$DB_PATH" "DELETE FROM auth_rules;" 2>/dev/null -sqlite3 "$DB_PATH" "DELETE FROM auth_rules_cache;" 2>/dev/null # Enable authentication rules echo "Enabling authentication rules..." @@ -180,7 +179,6 @@ echo # Clean rules sqlite3 "$DB_PATH" "DELETE FROM auth_rules;" -sqlite3 "$DB_PATH" "DELETE FROM auth_rules_cache;" # Test 3: Create a file and blacklist its hash test_file5=$(create_test_file "hash_blacklist_test.txt" "This specific file is blacklisted") @@ -299,39 +297,11 @@ test_upload "Test 9a: Disabled Rule Not Enforced" "$TEST_USER1_PRIVKEY" "$test_f # Test 9b: Enable the rule echo "Enabling the blacklist rule..." sqlite3 "$DB_PATH" "UPDATE auth_rules SET enabled = 1 WHERE rule_target = '$TEST_USER1_PUBKEY';" -sqlite3 "$DB_PATH" "DELETE FROM auth_rules_cache;" # Clear cache # Test 9c: Upload should now be denied test_file13=$(create_test_file "enabled_rule_test.txt" "Testing enabled rule") test_upload "Test 9c: Enabled Rule Enforced" "$TEST_USER1_PRIVKEY" "$test_file13" "403" -echo -echo "=== SECTION 10: CACHE FUNCTIONALITY ===" -echo - -# Clean rules -sqlite3 "$DB_PATH" "DELETE FROM auth_rules;" -sqlite3 "$DB_PATH" "DELETE FROM auth_rules_cache;" - -# Test 10: Add a blacklist rule and verify cache is populated -echo "Adding blacklist rule to test caching..." -sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, description) VALUES ('pubkey_blacklist', '$TEST_USER2_PUBKEY', 'upload', 10, 'Cache test');" - -# Test 10a: First request (cache miss) -test_file14=$(create_test_file "cache_test1.txt" "First request - cache miss") -test_upload "Test 10a: First Request (Cache Miss)" "$TEST_USER2_PRIVKEY" "$test_file14" "403" - -# Test 10b: Second request (should hit cache) -test_file15=$(create_test_file "cache_test2.txt" "Second request - cache hit") -test_upload "Test 10b: Second Request (Cache Hit)" "$TEST_USER2_PRIVKEY" "$test_file15" "403" - -# Test 10c: Verify cache entry exists -CACHE_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM auth_rules_cache WHERE pubkey = '$TEST_USER2_PUBKEY';" 2>/dev/null) -if [[ "$CACHE_COUNT" -gt 0 ]]; then - record_test_result "Test 10c: Cache Entry Created" "1" "1" -else - record_test_result "Test 10c: Cache Entry Created" "1" "0" -fi echo echo "=== SECTION 11: CLEANUP AND RESET ===" @@ -340,26 +310,18 @@ echo # Clean up all test rules echo "Cleaning up test rules..." sqlite3 "$DB_PATH" "DELETE FROM auth_rules;" -sqlite3 "$DB_PATH" "DELETE FROM auth_rules_cache;" # Verify cleanup RULE_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM auth_rules;" 2>/dev/null) if [[ "$RULE_COUNT" -eq 0 ]]; then - record_test_result "Test 11a: Rules Cleanup" "0" "0" + record_test_result "Test 10a: Rules Cleanup" "0" "0" else - record_test_result "Test 11a: Rules Cleanup" "0" "$RULE_COUNT" -fi - -CACHE_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM auth_rules_cache;" 2>/dev/null) -if [[ "$CACHE_COUNT" -eq 0 ]]; then - record_test_result "Test 11b: Cache Cleanup" "0" "0" -else - record_test_result "Test 11b: Cache Cleanup" "0" "$CACHE_COUNT" + record_test_result "Test 10a: Rules Cleanup" "0" "$RULE_COUNT" fi # Test that uploads work again after cleanup test_file16=$(create_test_file "cleanup_test.txt" "Testing after cleanup") -test_upload "Test 11c: Upload After Cleanup" "$TEST_USER1_PRIVKEY" "$test_file16" "200" +test_upload "Test 10b: Upload After Cleanup" "$TEST_USER1_PRIVKEY" "$test_file16" "200" echo echo "==========================================" @@ -382,7 +344,6 @@ if [[ $TESTS_FAILED -eq 0 ]]; then echo "- Operation-specific rules: Working" echo "- Wildcard operations: Working" echo "- Enable/disable rules: Working" - echo "- Cache functionality: Working" else echo "⚠️ Some tests failed. Check output above for details." echo "Success rate: $(( (TESTS_PASSED * 100) / TOTAL_TESTS ))%"