PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.13
Yatra – Travel Booking & Tour Operator Software v3.0.13
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Security / Guard.php

Guard.php in Yatra – Travel Booking & Tour Operator Software 3.0.13, at app/Security/Guard.php

114 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Security;
6
7 use Yatra\Utils\Logger;
8
9 /**
10 * Yatra Security Guard — monitor-first rollout switch for hardening checks.
11 *
12 * Newly added ownership / CSRF / amount-integrity checks call into this guard so
13 * they can ship in **monitor mode** (the default): when a check would reject or
14 * override a request, it instead {@see self::flag()}s a log line and lets the
15 * pre-existing behaviour proceed unchanged. This guarantees zero behavioural
16 * change for existing Free/Pro installs on update.
17 *
18 * Once an operator has watched the logs on real traffic and confirmed that honest
19 * users never trip a guard, they flip to **enforce mode** and the checks start
20 * actually blocking tampered / cross-customer requests.
21 *
22 * Enforce mode is enabled by EITHER:
23 * - defining `YATRA_SECURITY_ENFORCE` truthy in wp-config.php, OR
24 * - setting the `yatra_security_enforce` option to a truthy value
25 * (Settings → Tools/Advanced toggle), OR
26 * - returning true from the `yatra_security_enforce` filter.
27 *
28 * Default: false (monitor mode).
29 */
30 class Guard
31 {
32 /** Option name backing the admin toggle. */
33 public const OPTION = 'yatra_security_enforce';
34
35 /** Prefix for every monitor-mode log line, so operators can grep one tag. */
36 private const LOG_TAG = '[Yatra Security]';
37
38 /**
39 * Are the hardening checks in ENFORCE mode (actually block) vs MONITOR (log only)?
40 */
41 public static function enforcing(): bool
42 {
43 // Constant wins so power users / staging can force-enable regardless of DB state.
44 if (defined('YATRA_SECURITY_ENFORCE')) {
45 $enforce = (bool) constant('YATRA_SECURITY_ENFORCE');
46 } else {
47 $enforce = (bool) get_option(self::OPTION, false);
48 }
49
50 /**
51 * Filter the security enforce mode.
52 *
53 * @param bool $enforce True to enforce (block), false to monitor (log only).
54 */
55 return (bool) apply_filters('yatra_security_enforce', $enforce);
56 }
57
58 /**
59 * Record a would-block event. Always safe to call; in monitor mode this is the
60 * only effect, in enforce mode it documents what was blocked.
61 *
62 * Keep $context PII-light — booking/user ids and a coarse client IP are fine,
63 * raw names / emails / card data are not.
64 *
65 * @param string $reason Short machine-ish reason, e.g. 'payment_complete_ownership'.
66 * @param array<string, mixed> $context Small id-only context map.
67 */
68 public static function flag(string $reason, array $context = []): void
69 {
70 $mode = self::enforcing() ? 'blocked' : 'would-block';
71
72 if (!isset($context['ip'])) {
73 $context['ip'] = self::clientIpHint();
74 }
75 $context['mode'] = $mode;
76
77 if (class_exists(Logger::class)) {
78 Logger::warning(self::LOG_TAG . ' ' . $mode . ': ' . $reason, $context);
79 } elseif (defined('WP_DEBUG') && WP_DEBUG) {
80 // Logger should always exist, but never let logging itself fatal a request.
81 error_log(self::LOG_TAG . ' ' . $mode . ': ' . $reason . ' ' . wp_json_encode($context));
82 }
83 }
84
85 /**
86 * Convenience: a check failed. Always logs; returns whether the caller should
87 * actually block (true only in enforce mode). Callers keep their own reject
88 * response so HTTP status / message stay route-appropriate.
89 *
90 * if (!$ownsBooking && Guard::denied('payment_complete_ownership', $ctx)) {
91 * return new WP_REST_Response([...], 403);
92 * }
93 *
94 * @param array<string, mixed> $context
95 */
96 public static function denied(string $reason, array $context = []): bool
97 {
98 self::flag($reason, $context);
99
100 return self::enforcing();
101 }
102
103 /**
104 * Coarse client IP for log correlation. Uses REMOTE_ADDR only (not forwarded
105 * headers) so it can't be trivially spoofed into the logs.
106 */
107 private static function clientIpHint(): string
108 {
109 $ip = isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '';
110
111 return $ip !== '' ? sanitize_text_field($ip) : 'unknown';
112 }
113 }
114