# WAF Rules Module - Implementation Plan

Migrate PHP backend functionality from `wpwafmanager` into `ultimate-security` as a new `WAFRules` module.

## Context

- **Frontend**: 10 JSX pages already exist in `src/pages/WAFRules/` managing settings via `withForm` HOC
- **Settings defaults**: Already defined in `php/src/Config/defaults/waf-rules.php`
- **Settings storage**: Handled by `OptionsController` + `SettingsRepository` (single `ultimate_security_options` blob)
- **What's missing**: PHP backend for Cloudflare API communication, rule expression generation, and REST endpoints for deploy/verify/preview operations

---

## Files to Create

### 1. `CloudflareApiClient.php`

**Source**: Adapted from `wpwafmanager/includes/class-cloudflare-api.php` (~594 lines)

**Namespace**: `WPUltimateSecurity\UltimateSecurity\Modules\WAFRules`

**Purpose**: Cloudflare API v4 wrapper supporting API Token and Email+Global API Key authentication.

**Constructor parameters**:
- `string $auth_method` - `'token'` or `'key'`
- `string $api_token` - Bearer token for API Token auth
- `string $email` - Email for Email+Key auth
- `string $api_key` - Global API Key for Email+Key auth

**Public methods**:

| Method | CF API Endpoint | Description |
|--------|----------------|-------------|
| `verify_credentials(): array` | `GET user/tokens/verify` or `GET user` | Validates credentials, returns user info |
| `list_zones(): array` | `GET zones` | Paginated listing of active zones |
| `list_rules(string $zone_id): array` | `GET zones/{id}/rulesets` then `GET zones/{id}/rulesets/{id}` | Lists WAF custom firewall rules |
| `deploy_rules(string $zone_id, array $rules): array` | `PUT zones/{id}/rulesets/{id}` or `POST zones/{id}/rulesets` | Deploys rules to zone, auto-retries on Free plan errors |
| `get_zone_analytics(string $zone_id, int $days): array` | `POST /graphql` | GraphQL query for httpRequests1dGroups |

**Private helpers**:
- `auth_headers(): array` - Returns headers based on auth method
- `request(string $method, string $endpoint, array $body = null): array` - Core HTTP request to `https://api.cloudflare.com/client/v4/`
- `first_error(array $result): string` - Extracts first error message from CF API response

**Key adaptations from source**:
- PSR-4 namespacing, PHP 8.2 type declarations
- No base64 credential encoding (handled by `ApiKeyCryptoService` at storage layer)
- Remove methods not needed: DNS, email routing, IP rules, security events, cache purge

---

### 2. `RuleBuilder.php`

**Source**: Adapted from `wpwafmanager/includes/class-rule-builder.php` (~480 lines)

**Namespace**: `WPUltimateSecurity\UltimateSecurity\Modules\WAFRules`

**Purpose**: Translates checkbox settings from `waf_rules.*` into Cloudflare WAF rule objects with Wirefilter expressions.

**Static methods**:

| Method | Returns | Description |
|--------|---------|-------------|
| `build_rules(array $settings): array` | `array` | Builds all enabled rules from settings, returns array of rule objects |
| `build_rule_1_allow_good_bots(array $settings): ?array` | `array\|null` | Skip rule for verified bots, UA matches, Let's Encrypt, custom IPs |
| `build_rule_2_block_crawlers(array $settings): ?array` | `array\|null` | Block rule for aggressive crawlers, exploit scanners, WP paths, attack patterns |
| `build_rule_3_block_web_hosts(array $settings): ?array` | `array\|null` | Block/challenge rule for hosting provider ASNs and TOR exit nodes |
| `build_rule_4_challenge_providers(array $settings): ?array` | `array\|null` | Managed challenge for AWS/GCP/Azure ASNs, optional country allowlist |
| `build_rule_5_challenge_vpn(array $settings): ?array` | `array\|null` | Managed challenge for VPN provider ASNs and wp-login.php |
| `get_default_settings(): array` | `array` | Returns full default settings array for all 5 rules |

**Settings read from** (via `SettingsRepository::getInstance()->get('waf_rules.*')`):

- `waf_rules.allow_good_bots` - 8 service categories with checkbox values
- `waf_rules.block_crawlers` - 6 pattern categories with checkbox values
- `waf_rules.block_web_hosts` - 15 hosting providers with checkbox values
- `waf_rules.challenge_providers` - 3 cloud providers + country restriction
- `waf_rules.challenge_vpn` - 11 VPN providers + wp-login protection

**All checkbox values are strings**: `'1'` (enabled) or `'0'` (disabled).

**Rule output format** (for Cloudflare Rulesets API):
```php
[
    [
        'expression' => '(cf.bot_management.category in {"search_engine_crawler" ...})',
        'action' => 'skip',
        'description' => 'Allow Good Bots - Rule 1',
        'skip' => ['products' => ['zoneLockdown', 'uaBan', 'bic', 'hot', 'rateLimit', 'waf', 'products']],
    ],
    // ... more rules
]
```

---

### 3. `WAFRulesModule.php`

**Source**: New file (follows `SaltChange` module pattern)

**Namespace**: `WPUltimateSecurity\UltimateSecurity\Modules\WAFRules`

**Extends**: `AbstractModule`

**Properties**:
- `MODULE_ID = 'waf.cloudflare_waf'`
- `VERSION = '1.0.0'`

**Abstract method implementations**:
- `getId(): string` - Returns `'waf.cloudflare_waf'`
- `getName(): string` - Returns `__('Cloudflare WAF Rules', 'ultimate-security')`
- `getDomain(): string` - Returns `'waf'`
- `getConfigSchema(): array` - Returns schema with `enabled` boolean
- `isEnabled(): bool` - Always returns `true` (WAF rules are opt-in per-rule)
- `boot(): void` - No-op (REST routes registered via `RouteManager`)

**No dependencies** on other modules.

---

### 4. `WAFRulesController.php`

**Source**: New file (follows existing controller pattern with static methods)

**Namespace**: `WPUltimateSecurity\UltimateSecurity\Presentation\API\Controllers`

**Purpose**: REST API controller for WAF rule operations. Reads active account from settings, creates `CloudflareApiClient` instance, delegates operations.

**Static methods**:

| Method | Request Params | Description |
|--------|---------------|-------------|
| `verify_credentials(WP_REST_Request)` | `auth_method`, `api_token`, `email`, `api_key` | Verifies CF credentials, returns user info. Also verifies saved active account if no params given. |
| `list_zones(WP_REST_Request)` | None (reads active account) | Lists active CF zones for the currently active account |
| `deploy_rules(WP_REST_Request)` | `zone_id` (required) | Reads all `waf_rules.*` settings, builds rules via `RuleBuilder`, deploys to specified zone via `CloudflareApiClient` |
| `preview_rules(WP_REST_Request)` | None | Reads settings, builds rules via `RuleBuilder`, returns expressions and rule objects without deploying |
| `list_zone_rules(WP_REST_Request)` | `zone_id` (required) | Fetches existing WAF rules on a zone |
| `get_zone_analytics(WP_REST_Request)` | `zone_id`, `days` (optional, default 7) | Fetches zone analytics via GraphQL |

**Private helpers**:
- `get_active_account(): ?array` - Reads `waf_rules.cloudflare_connect` from `SettingsRepository`, returns active account array
- `create_api_client(array $account): CloudflareApiClient` - Factory method to create API client from account data
- `send_error(string $message, int $status): WP_Error` - Error response helper

**Permission**: All endpoints require `manage_options` capability.

---

### 5. `WAFRulesRoutes.php`

**Source**: New file (follows `SecurityRoutes` pattern)

**Namespace**: `WPUltimateSecurity\UltimateSecurity\Presentation\API\Routes`

**Extends**: `AbstractRouteRegistrar`

**Routes registered**:

| Route | Method | Callback | Permission |
|-------|--------|----------|------------|
| `/waf/verify-credentials` | POST | `WAFRulesController::verify_credentials` | `manage_options` |
| `/waf/zones` | GET | `WAFRulesController::list_zones` | `manage_options` |
| `/waf/deploy-rules` | POST | `WAFRulesController::deploy_rules` | `manage_options` |
| `/waf/preview-rules` | POST | `WAFRulesController::preview_rules` | `manage_options` |
| `/waf/zone-rules` | GET | `WAFRulesController::list_zone_rules` | `manage_options` |
| `/waf/zones/{zone_id}/analytics` | GET | `WAFRulesController::get_zone_analytics` | `manage_options` |

---

## Files to Modify

### 6. `php/src/Bootstrap/Providers/ModuleServiceProvider.php`

**Change**: Add `WAFRulesModule` to the module registry.

```php
// Add import at top:
use WPUltimateSecurity\UltimateSecurity\Modules\WAFRules\WAFRulesModule;

// Add to MODULES constant array (after TestModeModule or at end):
WAFRulesModule::class,
```

---

### 7. `php/src/Presentation/API/Routes/RouteManager.php`

**Change**: Register WAF routes in the route manager.

```php
// Add import at top:
use WPUltimateSecurity\UltimateSecurity\Presentation\API\Routes\WAFRulesRoutes;

// Add to loadDefaults() method (before do_action):
$this->add(new WAFRulesRoutes());
```

---

### 8. `src/shared/api/endpoints.js`

**Change**: Add `WAF_RULES` endpoint constants.

```js
export const WAF_RULES = {
    /** POST - Verify Cloudflare credentials */
    VERIFY_CREDENTIALS: buildEndpoint('/waf/verify-credentials'),
    /** GET - List active Cloudflare zones */
    ZONES: buildEndpoint('/waf/zones'),
    /** POST - Deploy WAF rules to a zone */
    DEPLOY: buildEndpoint('/waf/deploy-rules'),
    /** POST - Preview generated rules without deploying */
    PREVIEW: buildEndpoint('/waf/preview-rules'),
    /** GET - List existing WAF rules on a zone */
    ZONE_RULES: buildEndpoint('/waf/zone-rules'),
    /**
     * GET - Get zone analytics
     * @param zoneId
     * @param days
     */
    ANALYTICS: (zoneId, days = 7) =>
        buildEndpoint(`/waf/zones/${zoneId}/analytics?days=${days}`),
};
```

Also add `WAF_RULES` to the default export object.

---

## Optional: Check ApiKeyCryptoService

Verify that `ApiKeyCryptoService::encrypt()` and `::decrypt()` handle the `waf_rules.cloudflare_connect.accounts[].api_token` and `waf_rules.cloudflare_connect.accounts[].api_key` fields. If not, update the service to encrypt/decrypt these paths alongside existing API key fields.

---

## Implementation Order

| Step | File | Depends On | Status |
|------|------|-----------|--------|
| 1 | `CloudflareApiClient.php` | None | Pending |
| 2 | `RuleBuilder.php` | None | Pending |
| 3 | `WAFRulesModule.php` | None | Pending |
| 4 | `WAFRulesController.php` | Steps 1, 2 | Pending |
| 5 | `WAFRulesRoutes.php` | Step 4 | Pending |
| 6 | `ModuleServiceProvider.php` (edit) | Step 3 | Pending |
| 7 | `RouteManager.php` (edit) | Step 5 | Pending |
| 8 | `endpoints.js` (edit) | Step 5 | Pending |
| 9 | `ApiKeyCryptoService` (check) | None | Pending |

---

## Data Flow

```
Frontend (JSX)                    PHP Backend                        Cloudflare API
─────────────                     ───────────                        ──────────────

withForm HOC:
  GET /options?section=waf_rules.allow_good_bots
                    ──────────> OptionsController::get_options()
                                 SettingsRepository::get()
                                 returns checkbox settings
                    <----------

User clicks Save:
  POST /options { ultimate_security_options: { waf_rules: {...} } }
                    ──────────> OptionsController::update_options()
                                 deep_merge_options()
                                 ApiKeyCryptoService::encrypt()
                                 save to DB
                    <----------

User clicks Deploy:
  POST /waf/deploy-rules { zone_id: "..." }
                    ──────────> WAFRulesController::deploy_rules()
                                 SettingsRepository::get('waf_rules.*')
                                 RuleBuilder::build_rules($settings)
                                 CloudflareApiClient::deploy_rules()
                                                           ──────────> PUT zones/{id}/rulesets/{id}
                    <----------                              <----------
                                 returns deployment result
                    <----------

User clicks Verify:
  POST /waf/verify-credentials { auth_method, api_token, ... }
                    ──────────> WAFRulesController::verify_credentials()
                                 CloudflareApiClient::verify_credentials()
                                                           ──────────> GET user/tokens/verify
                    <----------                              <----------
                                 returns user info
                    <----------
```

---

## Notes

- All settings use string values `'1'`/`'0'` for booleans (matching frontend convention)
- The `CloudflareApiClient` is stateless - created per-request from stored credentials
- Expression generation uses Cloudflare Wirefilter syntax compatible with Rulesets API
- Free plan compatibility: auto-retry on error 20120 (skip phase not allowed)
- No admin menus needed - frontend React pages handle all UI
