diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d62f0..2b5b81a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.12.0] - 2026-02-04 + +### Security + +- Completed comprehensive security audit (Phase 12) +- Verified WordPress best practices compliance across entire codebase +- Confirmed protection against SQL Injection: all database queries use `$wpdb->prepare()` or WP_Query +- Confirmed protection against XSS: all output properly escaped with `esc_html()`, `esc_attr()`, `esc_url()` +- Confirmed protection against CSRF: nonce verification on all forms and admin AJAX handlers +- Verified REST API endpoint security: proper permission callbacks, rate limiting, input sanitization +- Sensitive data (ID/passport numbers) properly encrypted and not exposed via API + +### Fixed + +- Fixed Calculator being called statically in API controllers (`BookingsController`, `RoomsController`, `PricingController`) +- Fixed EmailNotifier method names in BookingsController (`send_admin_new_booking`, `send_cancellation`, `send_guest_confirmation`) +- Fixed guest_id type casting in EmailNotifier (string to int from post meta) + +### Notes + +- Public AJAX endpoints (search, availability, calendar, price calculation) intentionally do not require nonce verification as they are read-only public APIs with proper input sanitization +- All admin AJAX endpoints properly protected with nonce verification and capability checks + ## [0.11.3] - 2026-02-03 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index cf6f629..1ce2205 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1268,3 +1268,70 @@ Admin features always work; frontend requires valid license. - v0.11.2: Calendar room column width and building name display - v0.11.3: Calendar filters side-by-side layout - Both versions tagged and pushed to origin + +### 2026-02-04 - Version 0.12.0 (Security Audit) + +**Completed:** + +- Comprehensive security audit (Phase 12) +- WordPress best practices compliance verification +- OWASP Top 10 vulnerability review +- Live API endpoint testing against localhost:9080 + +**Security Audit Findings:** + +1. **SQL Injection:** ✓ PROTECTED + - All `$wpdb` queries use `$wpdb->prepare()` with parameterized queries + - WP_Query used throughout for post queries (inherently safe) + - Format specifiers (`%s`, `%d`) properly used in `$wpdb->update()` calls + +2. **XSS (Cross-Site Scripting):** ✓ PROTECTED + - PHP output consistently uses `esc_html()`, `esc_attr()`, `esc_url()` + - JavaScript uses `escapeHtml()` function (textContent/innerHTML pattern) + - Form values properly escaped before output + +3. **CSRF (Cross-Site Request Forgery):** ✓ PROTECTED + - All forms use `wp_nonce_field()` + - Admin AJAX handlers use `check_ajax_referer()` or `wp_verify_nonce()` + - Capability checks with `current_user_can()` on privileged operations + - Public AJAX endpoints are read-only and don't modify data + +4. **REST API Security:** ✓ PROTECTED + - Permission callbacks on all admin endpoints (`admin_permission`) + - Rate limiting via transient-based `RateLimiter` class + - Input sanitization via `sanitize_callback` on all parameters + - Sensitive data (ID/passport) not exposed via API + +5. **Data Encryption:** ✓ IMPLEMENTED + - Guest ID/passport numbers encrypted with AES-256-CBC + - IV stored with encrypted data for secure decryption + +**Bugs Fixed During Audit:** + +- Fixed `Calculator::calculate()` being called statically (non-static method) + - `src/Api/Controllers/BookingsController.php` + - `src/Api/Controllers/RoomsController.php` + - `src/Api/Controllers/PricingController.php` +- Fixed incorrect EmailNotifier method names in BookingsController + - `send_admin_notification` → `send_admin_new_booking` + - `send_cancellation_email` → `send_cancellation` + - `send_confirmation_email` → `send_guest_confirmation` +- Fixed guest_id type casting in EmailNotifier (string from post meta → int) + +**Files Changed:** + +- `src/Api/Controllers/BookingsController.php` - Calculator instantiation, EmailNotifier method names +- `src/Api/Controllers/RoomsController.php` - Calculator instantiation +- `src/Api/Controllers/PricingController.php` - Calculator instantiation +- `src/Booking/EmailNotifier.php` - guest_id type casting +- `wp-bnb.php` - Version bump to 0.12.0 +- `CHANGELOG.md` - Added v0.12.0 security audit notes +- `PLAN.md` - Marked Phase 12 complete + +**Learnings:** + +- `get_post_meta()` always returns strings; cast to `(int)` when needed for type-hinted methods +- Static vs instance method calls must match the method declaration +- Public frontend AJAX endpoints can safely skip nonce verification if they're read-only +- Rate limiting headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) provide client feedback +- WordPress REST API permission callbacks should use capability checks, not user login status alone diff --git a/PLAN.md b/PLAN.md index 07424dd..767e3ae 100644 --- a/PLAN.md +++ b/PLAN.md @@ -211,11 +211,12 @@ This document outlines the implementation plan for the WP BnB Management plugin. - [x] Order management - [x] Refund handling -## Phase 12: Security Audit (v0.12.0) +## Phase 12: Security Audit (v0.12.0) - Complete -- [ ] Check for Wordpress best-practices -- [ ] Review the code for OWASP Top 10, including XSS, XSRF, SQLi and other critical threads -- [ ] Test the API-Endpoints against a local live system under for common vulnerabilities +- [x] Check for WordPress best-practices +- [x] Review the code for OWASP Top 10, including XSS, CSRF, SQLi and other critical threats +- [x] Test the API-Endpoints against a local live system under for common vulnerabilities +- [x] Fix bugs discovered during security audit ## Future Considerations (v1.0.0+) @@ -360,5 +361,5 @@ The plugin will provide extensive hooks for customization: | 0.9.0 | Prometheus Metrics | Complete | | 0.10.0 | API Endpoints | Complete | | 0.11.0 | WooCommerce Integration | Complete | -| 0.12.0 | Security Audit | TBD | +| 0.12.0 | Security Audit | Complete | | 1.0.0 | Stable Release | TBD | diff --git a/src/Api/Controllers/BookingsController.php b/src/Api/Controllers/BookingsController.php index a8e9915..e14d553 100644 --- a/src/Api/Controllers/BookingsController.php +++ b/src/Api/Controllers/BookingsController.php @@ -338,9 +338,9 @@ final class BookingsController extends AbstractController { $guest_id = $this->find_or_create_guest( $guest ); // Calculate price. - $price = Calculator::calculate( $room_id, $check_in, $check_out ); - $services = $request->get_param( 'services' ) ?? array(); - $room_price = $price['price'] ?? 0; + $calculator = new Calculator( $room_id, $check_in, $check_out ); + $room_price = $calculator->calculate(); + $services = $request->get_param( 'services' ) ?? array(); // Calculate services total. $services_total = 0; @@ -410,7 +410,7 @@ final class BookingsController extends AbstractController { // Send notification email. if ( class_exists( EmailNotifier::class ) ) { - EmailNotifier::send_admin_notification( $post_id ); + EmailNotifier::send_admin_new_booking( $post_id ); } // Prepare response. @@ -532,7 +532,7 @@ final class BookingsController extends AbstractController { // Send cancellation email. if ( class_exists( EmailNotifier::class ) ) { - EmailNotifier::send_cancellation_email( $id ); + EmailNotifier::send_cancellation( $id ); } return $this->formatter->no_content(); @@ -607,7 +607,7 @@ final class BookingsController extends AbstractController { if ( 'confirmed' === $new_status ) { update_post_meta( $id, '_bnb_booking_confirmed_at', current_time( 'mysql' ) ); if ( class_exists( EmailNotifier::class ) ) { - EmailNotifier::send_confirmation_email( $id ); + EmailNotifier::send_guest_confirmation( $id ); } } diff --git a/src/Api/Controllers/PricingController.php b/src/Api/Controllers/PricingController.php index f7cd6a4..53b0ba0 100644 --- a/src/Api/Controllers/PricingController.php +++ b/src/Api/Controllers/PricingController.php @@ -136,9 +136,9 @@ final class PricingController extends AbstractController { $nights = (int) $check_in_date->diff( $check_out_date )->days; // Calculate room price. - $price = Calculator::calculate( $room_id, $check_in, $check_out ); - $room_total = $price['price'] ?? 0; - $breakdown = $price['breakdown'] ?? array(); + $calculator = new Calculator( $room_id, $check_in, $check_out ); + $room_total = $calculator->calculate(); + $breakdown = $calculator->getBreakdown(); $currency = get_option( 'wp_bnb_currency', 'CHF' ); // Build night-by-night breakdown. diff --git a/src/Api/Controllers/RoomsController.php b/src/Api/Controllers/RoomsController.php index 92680fd..14ea363 100644 --- a/src/Api/Controllers/RoomsController.php +++ b/src/Api/Controllers/RoomsController.php @@ -400,14 +400,16 @@ final class RoomsController extends AbstractController { if ( $is_available ) { // Calculate pricing. - $price = Calculator::calculate( $room_id, $check_in, $check_out ); + $calculator = new Calculator( $room_id, $check_in, $check_out ); + $price = $calculator->calculate(); + $breakdown = $calculator->getBreakdown(); $data['pricing'] = array( - 'tier' => $price['breakdown']['tier']->value ?? 'short_term', - 'base_rate' => $price['breakdown']['base_price_per_night'] ?? 0, - 'total' => $price['price'] ?? 0, - 'formatted' => $price['price_formatted'] ?? '', + 'tier' => $breakdown['tier']->value ?? 'short_term', + 'base_rate' => $breakdown['base_price_per_night'] ?? 0, + 'total' => $price, + 'formatted' => Calculator::formatPrice( $price ), 'currency' => get_option( 'wp_bnb_currency', 'CHF' ), - 'breakdown' => $price['breakdown'] ?? array(), + 'breakdown' => $breakdown, ); } else { // Get conflicts. diff --git a/src/Booking/EmailNotifier.php b/src/Booking/EmailNotifier.php index 7fa0f87..eb7e9cf 100644 --- a/src/Booking/EmailNotifier.php +++ b/src/Booking/EmailNotifier.php @@ -263,10 +263,10 @@ final class EmailNotifier { * @return array Guest data with keys: name, first_name, last_name, email, phone, notes, full_address. */ private static function get_guest_data( int $booking_id ): array { - $guest_id = get_post_meta( $booking_id, '_bnb_booking_guest_id', true ); + $guest_id = (int) get_post_meta( $booking_id, '_bnb_booking_guest_id', true ); // Try to get data from Guest CPT. - if ( $guest_id ) { + if ( $guest_id > 0 ) { $guest = get_post( $guest_id ); if ( $guest && Guest::POST_TYPE === $guest->post_type ) { $first_name = get_post_meta( $guest_id, '_bnb_guest_first_name', true ); diff --git a/wp-bnb.php b/wp-bnb.php index 1331ff9..eb8dc8d 100644 --- a/wp-bnb.php +++ b/wp-bnb.php @@ -3,7 +3,7 @@ * Plugin Name: WP BnB Management * Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb * Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests. - * Version: 0.11.3 + * Version: 0.12.0 * Requires at least: 6.0 * Requires PHP: 8.3 * Author: Marco Graetsch @@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) { } // Plugin version constant - MUST match Version in header above. -define( 'WP_BNB_VERSION', '0.11.3' ); +define( 'WP_BNB_VERSION', '0.12.0' ); // Plugin path constants. define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );