#!/usr/bin/env python3
"""
Full security test suite for ads.txt Guru Connect plugin v1.1.2
Tests both happy paths (legitimate use) and sad paths (attacks)
"""

import requests
import re
import time
import sys
from urllib.parse import urljoin

# Configuration
BASE_URL = "http://localhost:8080"
ADMIN_USER = "admin"
ADMIN_PASS = "password123"
PLUGIN_PAGE = "/wp-admin/admin.php?page=adstxt-guru-connect"

class WordPressTest:
    def __init__(self):
        self.session = requests.Session()
        self.base_url = BASE_URL
        self.logged_in = False
        
    def setup_wordpress(self):
        """Install WordPress if needed"""
        print("1. Setting up WordPress...")
        
        # Check if already installed
        response = self.session.get(self.base_url)
        if "wp-admin/install.php" in response.url:
            print("   Installing WordPress...")
            
            # Get the install page
            install_url = urljoin(self.base_url, "/wp-admin/install.php")
            response = self.session.get(install_url)
            
            # Submit installation form
            install_data = {
                'weblog_title': 'Test Site',
                'user_name': ADMIN_USER,
                'admin_password': ADMIN_PASS,
                'admin_password2': ADMIN_PASS,
                'admin_email': 'admin@example.com',
                'Submit': 'Install WordPress',
                'language': ''
            }
            
            response = self.session.post(
                urljoin(self.base_url, "/wp-admin/install.php?step=2"),
                data=install_data
            )
            
            if "Success" in response.text or "WordPress has been installed" in response.text:
                print("   ✓ WordPress installed successfully")
            else:
                print("   ✗ Installation may have failed")
        else:
            print("   ✓ WordPress already installed")
    
    def login(self):
        """Login as admin"""
        print("\n2. Logging in as admin...")
        
        login_url = urljoin(self.base_url, "/wp-login.php")
        login_data = {
            'log': ADMIN_USER,
            'pwd': ADMIN_PASS,
            'wp-submit': 'Log In',
            'redirect_to': urljoin(self.base_url, '/wp-admin/'),
            'testcookie': '1'
        }
        
        # Get login page first (for cookies)
        self.session.get(login_url)
        
        # Submit login
        response = self.session.post(login_url, data=login_data)
        
        # Check if logged in
        if "dashboard" in response.text.lower() or response.status_code == 302:
            self.logged_in = True
            print("   ✓ Logged in successfully")
        else:
            print("   ✗ Login failed")
            return False
        
        return True
    
    def activate_plugin(self):
        """Activate the plugin"""
        print("\n3. Activating plugin...")
        
        # Get plugins page
        plugins_url = urljoin(self.base_url, "/wp-admin/plugins.php")
        response = self.session.get(plugins_url)
        
        # Find activation link
        match = re.search(r'action=activate&amp;plugin=adstxt-guru-connect[^"]*&amp;_wpnonce=([^"&]*)', response.text)
        if match:
            nonce = match.group(1)
            activate_url = urljoin(self.base_url, 
                f"/wp-admin/plugins.php?action=activate&plugin=adstxt-guru-connect%2Fadstxt_guru_connect.php&_wpnonce={nonce}")
            response = self.session.get(activate_url)
            print("   ✓ Plugin activated")
        else:
            print("   ℹ Plugin may already be active")
    
    def get_nonce(self, nonce_name):
        """Extract nonce from plugin page"""
        plugin_url = urljoin(self.base_url, PLUGIN_PAGE)
        response = self.session.get(plugin_url)
        
        # Look for the specific nonce field
        pattern = f'name="{nonce_name}"\\s+value="([^"]*)"'
        match = re.search(pattern, response.text)
        
        if match:
            return match.group(1)
        return None
    
    def test_happy_path(self):
        """Test legitimate operations with valid nonces"""
        print("\n4. HAPPY PATH TESTS - Legitimate Operations")
        print("=" * 50)
        
        # Test 1: Update custom ads.txt content with valid nonce
        print("\n   Test 1: Update custom ads.txt content WITH valid nonce")
        nonce = self.get_nonce("atg_connect_custom_nonce")
        
        if nonce:
            print(f"   Found nonce: {nonce[:10]}...")
            
            data = {
                'atg-connect-custom': 'example.com, 12345, DIRECT\ngoogle.com, pub-123, RESELLER',
                'atg_connect_custom_nonce': nonce
            }
            
            response = self.session.post(
                urljoin(self.base_url, PLUGIN_PAGE),
                data=data
            )
            
            if "Custom ads.txt records updated successfully" in response.text:
                print("   ✓ SUCCESS: Custom content updated with valid nonce")
            else:
                print("   ✗ FAILED: Update did not succeed")
        else:
            print("   ✗ Could not find nonce field")
        
        # Test 2: Reset connection data with valid nonce
        print("\n   Test 2: Test reset button WITH valid nonce")
        response = self.session.get(urljoin(self.base_url, PLUGIN_PAGE))
        
        # Check if reset form exists
        if "atg_connect_reset_nonce" in response.text:
            pattern = r'name="atg_connect_reset_nonce"\s+value="([^"]*)"'
            match = re.search(pattern, response.text)
            if match:
                reset_nonce = match.group(1)
                print(f"   Found reset nonce: {reset_nonce[:10]}...")
                print("   ℹ Reset form exists and has nonce protection")
            else:
                print("   ✗ Reset form exists but nonce not found")
        else:
            print("   ℹ No reset form shown (expected when URL matches)")
    
    def test_sad_paths(self):
        """Test attack scenarios that should be blocked"""
        print("\n5. SAD PATH TESTS - Attack Scenarios")
        print("=" * 50)
        
        # Test 1: CSRF Attack - No nonce
        print("\n   Test 1: CSRF Attack - Submit form WITHOUT nonce")
        data = {
            'atg-connect-custom': 'HACKED_CONTENT'
        }
        
        response = self.session.post(
            urljoin(self.base_url, PLUGIN_PAGE),
            data=data
        )
        
        if "Security error" in response.text or "Custom ads.txt records updated" not in response.text:
            print("   ✓ BLOCKED: CSRF attack without nonce was rejected")
        else:
            print("   ✗ VULNERABLE: Form accepted without nonce!")
        
        # Test 2: CSRF Attack with invalid nonce
        print("\n   Test 2: CSRF Attack - Submit form with INVALID nonce")
        data = {
            'atg-connect-custom': 'HACKED_CONTENT',
            'atg_connect_custom_nonce': 'invalid_nonce_12345'
        }
        
        response = self.session.post(
            urljoin(self.base_url, PLUGIN_PAGE),
            data=data
        )
        
        if "Security error" in response.text or "Custom ads.txt records updated" not in response.text:
            print("   ✓ BLOCKED: Invalid nonce was rejected")
        else:
            print("   ✗ VULNERABLE: Form accepted with invalid nonce!")
        
        # Test 3: Directory Traversal Attack
        print("\n   Test 3: Directory Traversal - Attempt to write to /etc/passwd")
        nonce = self.get_nonce("atg_connect_nonce")
        
        if nonce:
            data = {
                'atg-connect-path': '/etc/passwd',
                'atg_connect_nonce': nonce
            }
            
            response = self.session.post(
                urljoin(self.base_url, PLUGIN_PAGE),
                data=data
            )
            
            if "Security error" in response.text or "Outside WordPress directory" in response.text:
                print("   ✓ BLOCKED: Directory traversal attempt rejected")
            elif "Path to ads.txt file updated" in response.text:
                print("   ✗ VULNERABLE: Path change accepted!")
            else:
                print("   ℹ Path update may be disabled")
        else:
            print("   ℹ Path update form has nonce but may be disabled")
        
        # Test 4: Arbitrary file write (.htaccess)
        print("\n   Test 4: Arbitrary File Write - Attempt to write to .htaccess")
        nonce = self.get_nonce("atg_connect_nonce")
        
        if nonce:
            data = {
                'atg-connect-path': '/var/www/html/.htaccess',
                'atg_connect_nonce': nonce
            }
            
            response = self.session.post(
                urljoin(self.base_url, PLUGIN_PAGE),
                data=data
            )
            
            if "Only ads.txt files are allowed" in response.text or "Security error" in response.text:
                print("   ✓ BLOCKED: .htaccess write attempt rejected")
            elif "Path to ads.txt file updated" in response.text:
                print("   ✗ VULNERABLE: Arbitrary file write allowed!")
            else:
                print("   ℹ Path update may be disabled")
        else:
            print("   ℹ Path field exists but may be read-only")
        
        # Test 5: Path traversal with ../
        print("\n   Test 5: Path Traversal - Using ../ sequences")
        nonce = self.get_nonce("atg_connect_nonce")
        
        if nonce:
            data = {
                'atg-connect-path': '../../../etc/passwd',
                'atg_connect_nonce': nonce
            }
            
            response = self.session.post(
                urljoin(self.base_url, PLUGIN_PAGE),
                data=data
            )
            
            if "Security error" in response.text or "Outside WordPress directory" in response.text:
                print("   ✓ BLOCKED: ../ traversal attempt rejected")
            elif "Path to ads.txt file updated" in response.text:
                print("   ✗ VULNERABLE: Path traversal with ../ accepted!")
            else:
                print("   ℹ Path validation working")
    
    def check_security_features(self):
        """Check for presence of security features"""
        print("\n6. Security Feature Verification")
        print("=" * 50)
        
        plugin_url = urljoin(self.base_url, PLUGIN_PAGE)
        response = self.session.get(plugin_url)
        
        # Check for nonce fields
        nonces_found = []
        if 'atg_connect_nonce' in response.text:
            nonces_found.append('Path update nonce')
        if 'atg_connect_custom_nonce' in response.text:
            nonces_found.append('Custom content nonce')
        if 'atg_connect_reset_nonce' in response.text:
            nonces_found.append('Reset nonce')
        
        if nonces_found:
            print(f"   ✓ Nonce fields found: {', '.join(nonces_found)}")
        else:
            print("   ✗ No nonce fields found - plugin may be vulnerable!")
        
        # Check if path field is disabled
        if 'name="atg-connect-path"' in response.text:
            if 'readonly' in response.text[max(0, response.text.find('name="atg-connect-path"')-100):response.text.find('name="atg-connect-path"')+100]:
                print("   ✓ Path field is read-only (secure)")
            else:
                print("   ℹ Path field is editable (with validation)")
        
        # Check for error messages
        if any(x in response.text for x in ['Security error', 'Invalid security token']):
            print("   ℹ Security error messages present from previous attempts")

def main():
    print("=" * 60)
    print("ads.txt Guru Connect Plugin Security Test Suite")
    print("Version 1.1.2 - Testing CSRF and Directory Traversal Fixes")
    print("=" * 60)
    
    tester = WordPressTest()
    
    # Setup and login
    tester.setup_wordpress()
    if not tester.login():
        print("\n✗ Failed to login, aborting tests")
        return 1
    
    tester.activate_plugin()
    
    # Run tests
    tester.test_happy_path()
    tester.test_sad_paths()
    tester.check_security_features()
    
    print("\n" + "=" * 60)
    print("Test Summary:")
    print("- Happy path tests verify legitimate operations work")
    print("- Sad path tests verify attacks are blocked")
    print("- Security patches in v1.1.2 should block all attacks")
    print("=" * 60)
    
    return 0

if __name__ == "__main__":
    sys.exit(main())